Android 项目总结(一):弧形ViewPager 和弧形HeaderView

一、前言

有一个月左右没有更新博客了,最近在做一个新项目,真的是忙得不可开交,已经进入了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:https://github.com/pinguo-zhouwei/AndroidTrainingSimples,欢迎讨论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,464评论 25 707
  • 贝塞尔曲线开发的艺术 一句话概括贝塞尔曲线:将任意一条曲线转化为精确的数学公式。 很多绘图工具中的钢笔工具,就是典...
    eclipse_xu阅读 27,675评论 38 370
  • 前言 扯来扯去,前面三篇自定义 View 文章,终于扯完了一些知识点,有些枯燥,所以我也是讲下核心点,没有细分析,...
    justCode_阅读 523评论 0 1
  • 前两天晚上,难得不用晚办公,就想着捯饬一下我的狗窝。偏偏站在凳子上又够不到放在上面的东西,索性踩到桌子上收拾东西,...
    桃宜阅读 1,899评论 17 6