Android 專案總結(一):弧形ViewPager 和弧形HeaderView

依然範特稀西發表於2017-10-15

一、前言

有一個月左右沒有更新部落格了,最近在做一個新專案,真的是忙得不可開交,已經進入了996模式,沒有什麼空餘的時間來寫部落格。雖然開始一個新的專案很忙很累,但是確實也是一個提升自己的機會,在之前的文章《Android開發者的進階之路》中我也說過,其實最好的進階來自於實戰,將學到的這些新技術、新框架用於專案中,不斷的採坑、填坑。這樣我們能收穫更多,也就成長的更快。

技術的沉澱來自於總結,感覺專案中又有好多的東西可以總結了,後續我會將其中用到的一些技術總結出來分享給大家,歡迎大家持續關注。好了,閒話不多說,今天就開始第一篇,關於弧形HeaderView。

二、需求

這種弧形的Header介面最近很流行,在幾個APP中都有看到,先來看兩張圖。

掌盟的Profile頁面:

LifeSum 中的部分頁面:

弧形view
弧形view

好了,就是上面這兩種效果:

1, 靜態的弧形header
2, 弧形ViewPager

思路:

怎麼做出上面的兩種效果呢?其實說是兩種效果,其實只有一種效果,那就是靜態弧形Header View ,搞定了這個,後面的ViewPager效果就簡單了。

弧形ViewPager,我們可以把它拆成2部分:

1 . 弧形Header View 做為背景。
2 . 透明的普通ViewPager.

重點來了,這個弧形的Header View 怎麼來做呢?我們通過自定義View 來做,我們再來仔細看一下這個View:

由兩部分組成:矩形+弧形,上面的矩形繪製很簡單,不多說。下面的弧形,可以通過二階貝塞爾曲線來繪製。

三、自定義 ArcHeaderView

通過上面的分析,我們知道它就是一個 矩形 + 一條弧線,我們可以通過Path 來組合兩種形狀。最終繪製出我們所要的形狀。

二階貝塞爾曲線:

首先我們來看一下二階貝塞爾曲線。補充一下二階貝塞爾曲線的知識點:

二階曲線由兩個資料點(A 和 C),一個控制點(B)來描述曲線狀態,大致如下:

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

連線AB BC,並在AB上取點D,BC上取點E,使其滿足條件:

連線DE,取點F,使得:

這樣獲取到的點F就是貝塞爾曲線上的一個點,動態過程如下:

繪製一段二階貝塞爾曲線需要三個點:一個起始點,一個終點和一個控制點。控制點的位置不同,決定了曲線的彎曲程度不同。繪製二階貝塞爾曲線的方法為: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);

        // 起始點
        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);


        invalidate();

    }複製程式碼

最後再onDraw 方法中,直接呼叫方法繪製就好。

 @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 ,要做這個ViewPager 的效果比較簡單。前面已經分析,這裡不贅述。直接上程式碼:

佈局檔案如下:

<?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) {
            // 返回頁面佈局
            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) {
            // 資料繫結
           // 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 = "公眾號: Android技術雜貨鋪";
            myData.content = "Find your\n"+"Plan"+i;
            datas.add(myData);
        }
        return datas;
    }

    public static class MyData{
        public  String title;
        public String content;
    }
}複製程式碼

最終效果如下:

弧形viewpager
弧形viewpager

最後

以上就是關於本期總結的全部內容,Demo程式碼已經上傳Github:github.com/pinguo-zhou…
,歡迎討論。

更多Android乾貨文章,關注公眾號【Android技術雜貨鋪】

相關文章