一、前言
有一個(gè)月左右沒有更新博客了,最近在做一個(gè)新項(xiàng)目,真的是忙得不可開交,已經(jīng)進(jìn)入了996模式,沒有什么空余的時(shí)間來寫博客。雖然開始一個(gè)新的項(xiàng)目很忙很累,但是確實(shí)也是一個(gè)提升自己的機(jī)會(huì),在之前的文章《Android開發(fā)者的進(jìn)階之路》中我也說過,其實(shí)最好的進(jìn)階來自于實(shí)戰(zhàn),將學(xué)到的這些新技術(shù)、新框架用于項(xiàng)目中,不斷的采坑、填坑。這樣我們能收獲更多,也就成長(zhǎng)的更快。
技術(shù)的沉淀來自于總結(jié),感覺項(xiàng)目中又有好多的東西可以總結(jié)了,后續(xù)我會(huì)將其中用到的一些技術(shù)總結(jié)出來分享給大家,歡迎大家持續(xù)關(guān)注。好了,閑話不多說,今天就開始第一篇,關(guān)于弧形HeaderView。
二、需求
這種弧形的Header界面最近很流行,在幾個(gè)APP中都有看到,先來看兩張圖。
掌盟的Profile頁(yè)面:

LifeSum 中的部分頁(yè)面:

好了,就是上面這兩種效果:
1, 靜態(tài)的弧形header
2, 弧形ViewPager
思路:
怎么做出上面的兩種效果呢?其實(shí)說是兩種效果,其實(shí)只有一種效果,那就是靜態(tài)弧形Header View ,搞定了這個(gè),后面的ViewPager效果就簡(jiǎn)單了。
弧形ViewPager,我們可以把它拆成2部分:
1 . 弧形Header View 做為背景。
2 . 透明的普通ViewPager.
重點(diǎn)來了,這個(gè)弧形的Header View 怎么來做呢?我們通過自定義View 來做,我們?cè)賮碜屑?xì)看一下這個(gè)View:

由兩部分組成:矩形+弧形,上面的矩形繪制很簡(jiǎn)單,不多說。下面的弧形,可以通過二階貝塞爾曲線來繪制。
三、自定義 ArcHeaderView
通過上面的分析,我們知道它就是一個(gè) 矩形 + 一條弧線,我們可以通過Path 來組合兩種形狀。最終繪制出我們所要的形狀。
二階貝塞爾曲線:
首先我們來看一下二階貝塞爾曲線。補(bǔ)充一下二階貝塞爾曲線的知識(shí)點(diǎn):
二階曲線由兩個(gè)數(shù)據(jù)點(diǎn)(A 和 C),一個(gè)控制點(diǎn)(B)來描述曲線狀態(tài),大致如下:

上圖中紅色曲線部分就是傳說中的二階貝塞爾曲線,那么這條紅色曲線是如何生成的呢?接下來我們就以其中的一個(gè)狀態(tài)分析一下:

連接AB BC,并在AB上取點(diǎn)D,BC上取點(diǎn)E,使其滿足條件:



這樣獲取到的點(diǎn)F就是貝塞爾曲線上的一個(gè)點(diǎn),動(dòng)態(tài)過程如下:
繪制一段二階貝塞爾曲線需要三個(gè)點(diǎn):一個(gè)起始點(diǎn),一個(gè)終點(diǎn)和一個(gè)控制點(diǎn)。控制點(diǎn)的位置不同,決定了曲線的彎曲程度不同。繪制二階貝塞爾曲線的方法為:quadTo(float x1, float y1, float x2, float y2)
繪制代碼如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mPath.reset();
// 上半部分矩形
mPath.moveTo(0, 0);
mPath.addRect(0, 0, mWidth, mHeight - mArcHeight, Path.Direction.CCW);
// 起始點(diǎn)
mStartPoint.x = 0;
mStartPoint.y = mHeight - mArcHeight;
// 終點(diǎn)
mEndPoint.x = mWidth;
mEndPoint.y = mHeight - mArcHeight;
// 控制點(diǎn)
mControlPoint.x = mWidth / 2 - 50;
mControlPoint.y = mHeight + 100;
// 初始化shader
mLinearGradient = new LinearGradient(mWidth / 2, 0, mWidth / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
invalidate();
}
最后再onDraw 方法中,直接調(diào)用方法繪制就好。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setShader(mLinearGradient);
mPath.moveTo(mStartPoint.x, mStartPoint.y);
// 添加貝塞爾曲線
mPath.quadTo(mControlPoint.x, mControlPoint.y, mEndPoint.x, mEndPoint.y);
// 繪制
canvas.drawPath(mPath, mPaint);
}
最終效果如下:

ArcHeaderView 的全部代碼如下:
public class ArcHeaderView extends View {
private Paint mPaint;
private PointF mStartPoint, mEndPoint, mControlPoint;
private int mWidth;
private int mHeight;
private Path mPath = new Path();
private int mArcHeight = 100;// 圓弧高度
private int mStartColor;
private int mEndColor;
private LinearGradient mLinearGradient;
public ArcHeaderView(Context context) {
super(context);
init();
}
public ArcHeaderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ArcHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public ArcHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
mPaint = new Paint();
// mPaint.setColor(Color.parseColor("#37B99F"));
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(10);
mPaint.setStyle(Paint.Style.FILL);
mStartPoint = new PointF(0, 0);
mEndPoint = new PointF(0, 0);
mControlPoint = new PointF(0, 0);
mStartColor = Color.parseColor("#FF3A80");
mEndColor = Color.parseColor("#FF3745");
}
/**
*
* @param startColor
* @param endColor
*/
public void setColor(@ColorInt int startColor, @ColorInt int endColor) {
mStartColor = startColor;
mEndColor = endColor;
mLinearGradient = new LinearGradient(mWidth / 2, 0, mWidth / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mPath.reset();
mPath.moveTo(0, 0);
mPath.addRect(0, 0, mWidth, mHeight - mArcHeight, Path.Direction.CCW);
mStartPoint.x = 0;
mStartPoint.y = mHeight - mArcHeight;
mEndPoint.x = mWidth;
mEndPoint.y = mHeight - mArcHeight;
mControlPoint.x = mWidth / 2 - 50;
mControlPoint.y = mHeight + 100;
// 初始化shader
mLinearGradient = new LinearGradient(mWidth / 2, 0, mWidth / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
///SweepGradient sweepGradient = new SweepGradient(mEndPoint.x / 2,mEndPoint.y / 2,mStartColor,mEndColor);
//mPaint.setShader(sweepGradient);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setShader(mLinearGradient);
mPath.moveTo(mStartPoint.x, mStartPoint.y);
mPath.quadTo(mControlPoint.x, mControlPoint.y, mEndPoint.x, mEndPoint.y);
canvas.drawPath(mPath, mPaint);
}
}
四、弧形ViewPager 效果
有了上面的自定義ArcHeaderView ,要做這個(gè)ViewPager 的效果比較簡(jiǎn)單。前面已經(jīng)分析,這里不贅述。直接上代碼:
布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zhouwei.androidtrainingsimples.arc_header_view.ArcHeaderView
android:id="@+id/header_view"
android:layout_width="match_parent"
android:layout_height="300dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="50dp"
>
<com.zhouwei.mzbanner.MZBannerView
android:id="@+id/arc_view_pager"
android:layout_width="match_parent"
android:layout_height="250dp"
app:canLoop="false"
app:open_mz_mode="false"
>
</com.zhouwei.mzbanner.MZBannerView>
</LinearLayout>
</FrameLayout>
Activity 代碼如下:
public class ArcHeaderViewPagerActivity extends AppCompatActivity {
private ArcHeaderView mArcHeaderView;
private MZBannerView mMZBannerView;
public int mStartColor[] ;
public int mEndColor[] ;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.arc_header_viewpager_layout_activity);
StatusBarUtils.setColor(this,getResources().getColor(R.color.start_color),0);
initView();
}
private void initView() {
mStartColor = new int[]{getResources().getColor(R.color.start_color)
,getResources().getColor(R.color.page1_start_color)
,getResources().getColor(R.color.page2_start_color)
,getResources().getColor(R.color.page3_start_color)};
mEndColor = new int[]{getResources().getColor(R.color.end_color)
,getResources().getColor(R.color.page1_end_color)
,getResources().getColor(R.color.page2_end_color)
,getResources().getColor(R.color.page3_end_color)};
mMZBannerView = (MZBannerView) findViewById(R.id.arc_view_pager);
mArcHeaderView = (ArcHeaderView) findViewById(R.id.header_view);
mArcHeaderView.setColor(getResources().getColor(R.color.start_color),getResources().getColor(R.color.end_color));
mMZBannerView.addPageChangeLisnter(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
StatusBarUtils.setColor(ArcHeaderViewPagerActivity.this,mStartColor[position],0);
mArcHeaderView.setColor(mStartColor[position],mEndColor[position]);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
mMZBannerView.setPages(mockData(), new MZHolderCreator() {
@Override
public MZViewHolder createViewHolder() {
return new BannerViewHolder();
}
});
}
public static class BannerViewHolder implements MZViewHolder<MyData> {
private TextView text1,text2;
@Override
public View createView(Context context) {
// 返回頁(yè)面布局
View view = LayoutInflater.from(context).inflate(R.layout.arc_view_pager_item,null);
text1 = (TextView) view.findViewById(R.id.arc_title);
text2 = (TextView) view.findViewById(R.id.arc_content);
return view;
}
@Override
public void onBind(Context context, int position, MyData data) {
// 數(shù)據(jù)綁定
// mImageView.setImageResource(data);
text1.setText(data.title);
text2.setText(data.content);
}
}
public List<MyData> mockData(){
List<MyData> datas = new ArrayList<>();
for(int i=0;i<4;i++){
MyData myData = new MyData();
myData.title = "公眾號(hào): Android技術(shù)雜貨鋪";
myData.content = "Find your\n"+"Plan"+i;
datas.add(myData);
}
return datas;
}
public static class MyData{
public String title;
public String content;
}
}
最終效果如下:

最后
以上就是關(guān)于本期總結(jié)的全部?jī)?nèi)容,Demo代碼已經(jīng)上傳Github:https://github.com/pinguo-zhouwei/AndroidTrainingSimples,歡迎討論。