Android 項(xiàng)目總結(jié)(一):弧形ViewPager 和弧形HeaderView

一、前言

有一個(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è)面:

弧形view
弧形view

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

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,使其滿足條件:


連接DE,取點(diǎn)F,使得:

這樣獲取到的點(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;
    }
}

最終效果如下:

弧形viewpager
弧形viewpager

最后

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,029評(píng)論 25 709
  • 貝塞爾曲線開發(fā)的藝術(shù) 一句話概括貝塞爾曲線:將任意一條曲線轉(zhuǎn)化為精確的數(shù)學(xué)公式。 很多繪圖工具中的鋼筆工具,就是典...
    eclipse_xu閱讀 28,003評(píng)論 38 370
  • 前言 扯來扯去,前面三篇自定義 View 文章,終于扯完了一些知識(shí)點(diǎn),有些枯燥,所以我也是講下核心點(diǎn),沒有細(xì)分析,...
    justCode_閱讀 641評(píng)論 0 1
  • 前兩天晚上,難得不用晚辦公,就想著捯飭一下我的狗窩。偏偏站在凳子上又夠不到放在上面的東西,索性踩到桌子上收拾東西,...
    桃宜閱讀 2,144評(píng)論 17 6

友情鏈接更多精彩內(nèi)容