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

一、前言

有一个月左右没有更新博客了,最近在做一个新项目,真的是忙得不可开交,已经进入了996模式,没有什么空余的时间来写博客。虽然开始一个新的项目很忙很累,但是确实也是一个提升自己的机会,在之前的文章《Android开发者的进阶之路》中我也说过,其实最好的进阶来自于实战,将学到的这些新技术、新框架用于项目中,不断的采坑、填坑。这样我们能收获更多,也就成长的更快。


技术的沉淀来自于总结,感觉项目中又有好多的东西可以总结了,后续我会将其中用到的一些技术总结出来分享给大家,欢迎大家持续关注。好了,闲话不多说,今天就开始第一篇,关于弧形HeaderView。

二、需求

这种弧形的HeaderView界面最近很流行,在几个APP中都有看到,先来看两张图。

掌盟的Profile页面:


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


LifeSum 中的部分页面:


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


好了,就是上面这两种效果:

1, 静态的弧形headerView
2, 弧形ViewPager


思路:

怎么做出上面的两种效果呢?其实说是两种效果,其实只有一种效果,那就是静态弧形Header View ,搞定了这个,后面的ViewPager效果就简单了。

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


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


重点来了,这个弧形的Header View 怎么来做呢?我们通过自定义View 来做,我们再来仔细看一下这个View:


由两部分组成:矩形+弧形,上面的矩形绘制很简单,不多说。下面的弧形,可以通过二阶贝塞尔曲线来绘制。

三、自定义 ArcHeaderView

通过上面的分析,我们知道它就是一个 矩形 + 一条弧线,我们可以通过Path 来组合两种形状。最终绘制出我们所要的形状。


二阶贝塞尔曲线:


首先我们来看一下二阶贝塞尔曲线。补充一下二阶贝塞尔曲线的知识点:


二阶曲线由两个数据点(A 和 C),一个控制点(B)来描述曲线状态,大致如下:

上图中红色曲线部分就是传说中的二阶贝塞尔曲线,那么这条红色曲线是如何生成的呢?接下来我们就以其中的一个状态分析一下:

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

连接AB BC,并在AB上取点D,BC上取点E,使其满足条件:
Android 项目总结(一):弧形ViewPager 和弧形HeaderView_java_05

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

连接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);    }

最终效果如下:


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

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;    } }

最终效果如下:


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