1. 程式人生 > >弧形 View 和弧形 ViewPager

弧形 View 和弧形 ViewPager

靈感來自於:Android 專案總結(一):弧形ViewPager 和弧形HeaderView

圖片也來自上面的連結。


最近看到了好多這樣的效果,身邊同事也有提起過這種弧形的 View,其實實現這種效果的方法很多,我也嘗試實現了一下,雖然方式不咋好,但好歹做出來效果。

1 思路

最開始看到這個效果第一反應就是用貝塞爾曲線去畫一個下面是弧形的封閉曲線,然後再呼叫 Paint 的setXfermode() 將其設定為 SRC_IN(講這個的文章網上倒是有很多,這個模式就是取兩個東西的交集,顯示上層影象的意思),然後再畫圖片不就行了嗎,嗯,我獲取圓形頭像就是這麼幹的,理論上來說完全 ojbk


2 ArcView

說幹就幹,先自定義一個 ArcView:

public class ArcView extends android.support.v7.widget.AppCompatImageView {
    private Paint mPaint;
    private Path mPath; //下面為弧形的封閉曲線
    private Bitmap mBitmap; //圖片
    private int width;  //控制元件寬
    private int height; //控制元件高
    private int arcWidth;   //弧形寬
    private int arcHeight;  //弧形高
    private Rect srcRect;   //圖片繪製區域
    private Rect dstRect;   //圖片顯示區域

    public ArcView(Context context) {
        this(context, null);
    }

    public ArcView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        width = context.getResources().getDisplayMetrics().widthPixels;
        height = context.getResources().getDisplayMetrics().heightPixels / 2;
        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);   //畫筆設定為填充,這個別忘了
        mPaint.setAntiAlias(true);
        mPath = new Path();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = getContext().getResources().getDisplayMetrics().widthPixels;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = getContext().getResources().getDisplayMetrics().heightPixels / 2;
        }
        arcWidth = width;
        arcHeight = height / 5; //取弧形的高度為整個控制元件的五分之一
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPath.moveTo(0, 0);
        mPath.moveTo(0, height - arcHeight);
        mPath.quadTo(width / 2, height + arcHeight, width, height - arcHeight);
        mPath.lineTo(width, 0);
        mPath.lineTo(0, 0);
        canvas.drawPath(mPath, mPaint);
        if (mBitmap != null) {
            srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());  //繪製區域
            dstRect = new Rect(0, 0, width, height);    //顯示區域
            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
            canvas.drawBitmap(mBitmap, srcRect, dstRect, mPaint);
        }
    }

    @Override
    public void setImageBitmap(Bitmap bitmap) {
        this.mBitmap = bitmap;
        invalidate();
    }

    @Override
    public void setImageResource(int resourceId) {
        this.mBitmap = BitmapFactory.decodeResource(getContext().getResources(), resourceId);
        invalidate();
    }

    /**
     * Description:載入完成後獲取圖片
     * Date:2018/1/22
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getDrawable() != null) {
            mBitmap = ((BitmapDrawable) getDrawable()).getBitmap();
        }
    }
}

老夫擼程式碼就是一口氣寫到底,一路寫下來頭腦清晰,毫無阻礙,佈局很簡單:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.qinshou.arcdemo.activity.ArcViewActivity">

    <com.qinshou.arcdemo.widget.ArcView
        android:layout_width="wrap_content"
        android:src="@drawable/a"
        android:id="@+id/av_test"
        android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>


滿懷希望的跑起來一看:



WTF,why!難道是這一行程式碼出了問題?

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

可是我之前畫圓形圖片就是這樣的,毫無破綻啊,我把 drawPath() 換成 drawCircle() 了仍是如此,嗯,這裡面一定有什麼不可告人的祕密,等我定下心來的時候把它好好解剖一下,當務之急是要實現弧形效果。All the roads to the Rome,下面顯示弧形,是不是先畫圖片,再畫一個白色的弧形 View來遮住它下半部分即可?即一個白色的這樣的 View:



嗯,試一下,把 onDraw() 方法改一下:

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBitmap != null) {
            srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
            dstRect = new Rect(0, 0, width, height);
            canvas.drawBitmap(mBitmap, srcRect, dstRect, mPaint);
        }
        mPath.moveTo(0, height - arcHeight);
        mPath.quadTo(width / 2, height + arcHeight, width, height - arcHeight);
        mPath.lineTo(width, height);
        mPath.lineTo(0, height);
        canvas.drawPath(mPath, mPaint);
    }


雖然這種方法有投機取巧之嫌,不過也不失為一種思路,等後面小弟找出 setXfermode() 方法不好使是誰在作祟時,再捉它來給大家問罪。


3 ArcViewPager

既然 ArcView 弧形控制元件可以實現,那麼弧形的 ViewPager 應該就不難了,把 ArcView 作為 ViewPager 的每一個頁卡即可:
public class ArcViewPagerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_arc_view_pager);
        ViewPager viewPager = (ViewPager) findViewById(R.id.vp_test);
        int[] resource = new int[]{R.drawable.a, R.drawable.b, R.drawable.c
                , R.drawable.d, R.drawable.e, R.drawable.f};
        List<View> viewList = new ArrayList<>();
        for (int i = 0; i < resource.length; i++) {
            ArcView mArcView = new ArcView(this);
            mArcView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            mArcView.setImageResource(resource[i]);
            viewList.add(mArcView);
        }
        viewPager.setAdapter(new MyViewPagerAdapter(viewList));
    }

    private class MyViewPagerAdapter extends PagerAdapter {
        private List<View> viewList = new ArrayList<>();

        public MyViewPagerAdapter(List<View> viewList) {
            this.viewList = viewList;
        }

        @Override
        public int getCount() {
            return viewList.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            container.addView(viewList.get(position));
            return viewList.get(position);
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView(viewList.get(position));
        }
    }
}


但是如果這麼簡單的實現的話,看起來總有點彆扭,我是希望頁卡滑動時只是中間的圖片切換,但是下面的弧形不要跟著動,根據剛才 ArcView的思路,我決定自定義一個容器使用相對佈局,先新增一個 ViewPager,然後繼續在下面新增一個弧形遮擋 View:
public class ArcViewPager extends RelativeLayout {

    private ViewPager mViewPager;

    public ArcViewPager(Context context) {
        this(context, null);
    }

    public ArcViewPager(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ArcViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ArcCoverView mArcCoverView = new ArcCoverView(getContext());
        mArcCoverView.setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 200));
        mViewPager = new ViewPager(getContext());   //建立 ViewPager
        addView(mViewPager);    //新增 ViewPager
        addView(mArcCoverView); //新增弧形遮擋 View
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //遍歷子 View,將弧形遮擋 View 放在底部
        for (int i = 0; i < getChildCount(); i++) {
            if (getChildAt(i) instanceof ArcCoverView) {
                ArcCoverView arcCoverView = (ArcCoverView) getChildAt(i);
                arcCoverView.layout(0, getMeasuredHeight() - arcCoverView.getMeasuredHeight(), arcCoverView.getMeasuredWidth(), getMeasuredHeight());
            }
        }
    }

    /**
     * Description:將介面卡設定給 ViewPager
     * Date:2018/1/22
     */
    public void setAdapter(PagerAdapter adapter) {
        mViewPager.setAdapter(adapter);
    }

    /**
     * Description:弧形遮擋 View
     * Date:2018/1/22
     */
    public class ArcCoverView extends View {

        private Paint mPaint;
        private Path mPath;
        private int width;
        private int height;

        public ArcCoverView(Context context) {
            this(context, null);
        }

        public ArcCoverView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }

        public ArcCoverView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            width = context.getResources().getDisplayMetrics().widthPixels;
            height = 200;
            mPaint = new Paint();
            mPaint.setColor(Color.WHITE);
            mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
            mPaint.setAntiAlias(true);
            mPath = new Path();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPath.moveTo(0, 0);
            mPath.quadTo(width / 2, height * 2, width, 0);
            mPath.lineTo(width, height);
            mPath.lineTo(0, height);
            canvas.drawPath(mPath, mPaint);
        }
    }
}

然後 Activity 中修改一下,可以直接新增 ImageView 為 ViewPager 的頁卡了:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_arc_view_pager);
        ArcViewPager arcViewPager = (ArcViewPager) findViewById(R.id.avp_test);
        int[] resource = new int[]{R.drawable.a, R.drawable.b, R.drawable.c
                , R.drawable.d, R.drawable.e, R.drawable.f};
        List<View> viewList = new ArrayList<>();
        for (int i = 0; i < resource.length; i++) {
            ImageView mImageView = new ImageView(this);
            mImageView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            mImageView.setImageResource(resource[i]);
            viewList.add(mImageView);
        }
        arcViewPager.setAdapter(new MyViewPagerAdapter(viewList));
    }


現在看起來順眼多了。

4 總結

這次的目標雖然是實現了,但是總覺得實現方式不太優雅,這裡只是提供了一種思路,大家不喜勿噴,如果有其他思路的,歡迎大家留言,或者可以給我解惑的,小弟不勝感激。好久沒寫部落格,換了公司後需要一段時間適應,再加上在學習 python,實在是沒有時間,總歸要堅持的,2018 年第一篇,但必定不是最後一篇。希望自己可以走得更遠。