Android 項目總結(一):弧形ViewPager 和弧形HeaderView

1、前言

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

技術的沉澱來自於總結,感受項目中又有好多的東西能夠總結了,後續我會將其中用到的一些技術總結出來分享給你們,歡迎你們持續關注。好了,閒話很少說,今天就開始第一篇,關於弧形HeaderView。android

2、需求

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

掌盟的Profile頁面:github

LifeSum 中的部分頁面:canvas

弧形view
弧形view

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

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

思路: 框架

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

弧形ViewPager,咱們能夠把它拆成2部分:oop

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

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

由兩部分組成:矩形+弧形,上面的矩形繪製很簡單,很少說。下面的弧形,能夠經過二階貝塞爾曲線來繪製。

3、自定義 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);
    }
}複製代碼

4、弧形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技術雜貨鋪】

相關文章
相關標籤/搜索