Android 弧形ViewPager 和弧形HeaderView(升級版)

前段時間寫了一篇項目總結的文章,總結了項目中使用的弧形View 和弧形ViewPager 效果,採用的是自定義View 的方法,而後繪製弧形採用的是二階貝塞爾曲線,具體的思路和詳情請看文章Android 項目總結(一):弧形ViewPager 和弧形HeaderView ,最後效果以下:java

雖然效果還不錯,可是有瑕疵,有兩個明顯的缺陷:git

  • 底部的圓弧不是正圓弧:如上圖所示,弧形有點歪,特別是在小屏幕手機上表現尤其明顯,由於是用二階貝塞爾曲線繪製的圓弧,無論怎麼調整控制點,都不會是一個正圓弧,以下圖: github

  • 圓弧不能設置圖片背景:前面的這個版本,弧形背景只能設置顏色,不能設置背景圖canvas

1. 升級版ArcView實現思路

既然有了上面說的2個缺點,咱們就要想辦法解決它,2個問題咱們逐個分析一下:api

1. 圓弧問題:網絡

版本1的弧形使用二階貝塞爾曲線繪製,既然這種方式不能繪製一個正圓弧,那麼咱們不妨換個思路,哪些圖形有正圓弧?首先就想到了圓,咱們能夠繪製一個很大的圓,而後用手機的屏幕去截取,重疊的部分就是咱們想要View了,畫了一個草圖,看得比較直觀:ide

如上圖所示,圓形和屏幕的重疊區域就是咱們的View區域,圓形重疊以外的區域在屏幕外。這樣截取出來的弧形確定是正圓弧。ui

2 . 弧形View設置圖片背景this

咱們採用的是自定義View,顯示圖片仍是很簡單的,canvasdrawBitmap 就能實現,可是有一個點,圖片要顯示成咱們定義的弧形,這就須要用到 PorterDuffXfermode,PorterDuff.Mode,關於PorterDuffXfermode這裏不過多的講,網上講它的博客不少,看一下這張經典的圖就行白了: url

具體實現:先繪製圓,再繪製圖片,設置 PorterDuffXfermodePorterDuff.Mode.SRC_IN 就ok了。

2. 具體實現

前面說了思路,那麼代碼就很簡單了,看一下實現的代碼:

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mHeight = getHeight();
        int width = getWidth();
        mWidth = width;
        // 半徑
        mRadius = width * 2;
        // 矩形
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = width;
        mRect.bottom = mHeight;
        // 圓心座標
        mCircleCenter.x = width / 2;
        mCircleCenter.y = mHeight - width * 2;
        // 繪製漸變色
        mLinearGradient = new LinearGradient(width / 2, 0, width / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
    }
複製代碼

解釋:圓的半徑爲屏幕寬度2倍,矩形的高度就是整個自定義View的高度,圓心座標的y 爲 mHeight - mRadius 。

@Override
    protected void onDraw(Canvas canvas) {
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
        canvas.drawCircle(mCircleCenter.x, mCircleCenter.y, mRadius, mPaint);
        //設置PorterDuffXfermode
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        // 經過變量mIsShowImage 來控制是顯示圖片仍是顏色
        if (mIsShowImage) {
            if (mBitmap != null) {
                canvas.drawBitmap(mBitmap, null, mRect, mPaint);
            }

        } else {
            mPaint.setShader(mLinearGradient);//繪製漸變色
            canvas.drawRect(mRect, mPaint);
        }

        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }
複製代碼

就是這麼簡單,最後效果以下:

效果是否是好了不少?

3. 完整源碼

PerfectArcView.java

public class PerfectArcView extends View implements Target {
    private Paint mPaint;
    private Bitmap mBitmap;
    private int mHeight;
    private int mWidth;
    private RectF mRect = new RectF();
    private Point mCircleCenter;
    private float mRadius;
    private int mStartColor;
    private int mEndColor;
    private LinearGradient mLinearGradient;
    /** * 顯示圖片仍是顯示色值 */
    private boolean mIsShowImage = true;

    public PerfectArcView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        readAttr(attrs);
        init();
    }

    private void init() {
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
        // mBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.splash);
        mCircleCenter = new Point();
    }

    private void readAttr(AttributeSet set) {
        TypedArray typedArray = getContext().obtainStyledAttributes(set, R.styleable.PerfectArcView);
        mStartColor = typedArray.getColor(R.styleable.PerfectArcView_p_arc_startColor, Color.parseColor("#FF3A80"));
        mEndColor = typedArray.getColor(R.styleable.PerfectArcView_p_arc_endColor, Color.parseColor("#FF3745"));
        mIsShowImage = typedArray.getBoolean(R.styleable.PerfectArcView_p_arc_showImage, false);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mHeight = getHeight();
        int width = getWidth();
        mWidth = width;
        // 半徑
        mRadius = width * 2;
        // 矩形
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = width;
        mRect.bottom = mHeight;
        // 圓心座標
        mCircleCenter.x = width / 2;
        mCircleCenter.y = mHeight - width * 2;

        mLinearGradient = new LinearGradient(width / 2, 0, width / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
    }

    /** * 加載網絡圖片 * * @param url */
    public void setImageUrl(String url) {
        Picasso.with(getContext()).load(url).into(this);
    }

    /** * @param startColor * @param endColor */
    public void setColor(@ColorInt int startColor, @ColorInt int endColor) {
        mStartColor = startColor;
        mEndColor = endColor;
        mIsShowImage = false;
        mLinearGradient = new LinearGradient(mWidth / 2, 0, mWidth / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
        invalidate();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onDraw(Canvas canvas) {
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
        canvas.drawCircle(mCircleCenter.x, mCircleCenter.y, mRadius, mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        if (mIsShowImage) {
            if (mBitmap != null) {
                canvas.drawBitmap(mBitmap, null, mRect, mPaint);
            }

        } else {
            mPaint.setShader(mLinearGradient);//繪製漸變色
            canvas.drawRect(mRect, mPaint);
        }

        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }

    @Override
    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
        Log.e("TAG", "onBitmapLoaded....");
        mBitmap = bitmap;
        invalidate();
    }

    @Override
    public void onBitmapFailed(Drawable errorDrawable) {
        Log.e("TAG", "onBitmapFailed....");
    }

    @Override
    public void onPrepareLoad(Drawable placeHolderDrawable) {
        Log.e("TAG", "onPrepareLoad....");
    }
}

複製代碼

4. 最後

條條大路通羅馬,本文講了弧形View的另外一種實現思路,固然了,可能還有不少種實現方法,上一篇文章的留言區裏,有人同窗提到能夠在矩形區域的地步覆蓋一個白色的弧形圖片,這個白色的能夠找UI設計師切圖,這種應該也是能夠實現效果的,可是擴展性不是很強,若是項目中有多個地方用到,仍是挺麻煩的。若是你還有其餘方法,歡迎交流。 源碼訪問Github:https://github.com/pinguo-zhouwei/AndroidTrainingSimples

相關文章
相關標籤/搜索