神奇的水滴效果導航欄-BezierIndicator

晨鳴的博客--神奇的水滴效果導航欄-BezierIndicator

很早之前就看見過這樣一個特效


心怡很久,卻一直恐于自定義View這座大山。最近在突擊自定義View的技能,學習貝塞爾曲線的繪制,前面搞了個很簡單的MagicButton,甚是興奮?? 所以斗膽來試試看實現這個特效。

分析

找了半天終于找到當初看見的這個特效的原博客 --三次貝塞爾曲線練習之彈性的圓

另外在評論中發現竟然有人已經實現了這個自定義View了--自定義View之炫酷的水滴ViewPageIndicator,效果很不錯,借鑒之??

關于最核心的貝塞爾小球動效的繪制,博主進行了很詳細的解析及描述,并且提供了一個demo,萬分感謝??

這里簡單回顧一下這個小球的繪制過程:

為了控制小球的不同形態,我們這里使用三階貝塞爾曲線cubicTo來繪制小球。

貝塞爾小球

而小球一共可以分成5個狀態來繪制

狀態1

狀態1

狀態2

狀態2

狀態3

狀態3

狀態4

狀態4

狀態5

狀態5

繪制

計算控件寬高

作為一個導航控件,我暫時不考慮寬度設置為warp_content的狀態,設置wrap_content一律計算為屏幕的最大寬高.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        /**
         * 獲得此ViewGroup上級容器為其推薦的寬和高,以及計算模式
         */
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            width = sizeWidth;
        } else {
            width = wm.getDefaultDisplay().getWidth();
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = sizeHeight;
        } else {
            height = wm.getDefaultDisplay().getHeight();
        }

        if (getChildCount() != 0) {
            childSideLength = (width - getPaddingRight() - getPaddingLeft()) / getChildCount() > height - getPaddingBottom() - getPaddingTop() ? height - getPaddingBottom() - getPaddingTop() : (width - getPaddingLeft() - getPaddingRight()) / getChildCount();
//        //計算出所有的ChildView的寬和高
//            measureChildren(widthMeasureSpec, heightMeasureSpec);
            bezierCircular = new BezierCircular(childSideLength / 2);
        }

        setMeasuredDimension(width, height);
    }

計算子控件的位置

為了方便管理,子View的大小統一計算為一個正方形區域,設置一個子View的padding值childPadding,可以通過childPadding值控制我們添加的子view呈現出的大小,也就是效果圖中小圖標在白色圓環中的大小。

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        if (childCount == 0) {
            return;
        }
        //相鄰兩個子View中心點的間距
        float childDis = (width - getPaddingLeft() - getPaddingRight() - 2 * defaultLeftRightGap - childSideLength) / (childCount - 1);
        float cWidth = childSideLength - 2 * childPadding;
        float cHeight = cWidth;

        anchorList.clear();
        //計算子控件的位置,強制將子View控制繪制在均分的幾個錨點上
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            PointF anchorPoint = new PointF((childDis * i + defaultLeftRightGap + childSideLength / 2 + getPaddingLeft()), getPaddingTop() + childSideLength / 2);
            anchorList.add(anchorPoint);
            childView.layout((int) (anchorPoint.x - cWidth / 2), (int) (anchorPoint.y - cHeight / 2), (int) (anchorPoint.x + cWidth / 2), (int) (anchorPoint.y + cHeight / 2));
        }
        PointF pointF = anchorList.get(0);
        bezierCircular.setCenter(pointF.x, pointF.y);
        bezierCircular.initControlPoint();
    }

繪制貝塞爾小球

將貝塞爾小球的一些參數及計算封裝成一個對象BezierCircular,因為剛開始只是看了原博客的思路就動手了,繪制貝塞爾小球使用了最原始的方法,定義了4個數據點和8個控制點,在進行五個狀態的繪制計算的時候太麻煩了,后面看了博客中的Demo,發現自己的計算太原始笨重了,博客中的demo中關于小球的繪制更加面向對象,更加簡潔。不過既然是原創,還是要貼出自己的代碼,僅供參考??

public class BezierCircular {
    private static final String TAG = "BezierCircular";

    private static final float C = 0.551915024494f; //常量


    //圓中心坐標
    float centerX;
    float centerY;

    //圓半徑
    float radius;

    private PointF currentPoint;
    private PointF targetPoint;
    private float mDifference;

    private float stretchDistance;
    private float cDistance;

    private float moveDistance;

    private float[] mData = new float[8];  //順時針記錄繪制圓形的四個數據點
    private float[] mCtrl = new float[16];  //順時針記錄繪制圓形的八個控制點

    public BezierCircular(float radius) {
        this.radius = radius;
        stretchDistance = radius / 3 * 2;
        mDifference = radius * C;
        cDistance = mDifference * 0.45f;
    }

    public void setCenter(float centerX, float centerY) {
        this.centerX = centerX;
        this.centerY = centerY;

    }


 
    public void initControlPoint() {

        //初始化數據點
        mData[0] = centerX;
        mData[1] = centerY + radius;

        mData[2] = centerX + radius;
        mData[3] = centerY;

        mData[4] = centerX;
        mData[5] = centerY - radius;

        mData[6] = centerX - radius;
        mData[7] = centerY;

        //初始化控制點
        mCtrl[0] = mData[0] + mDifference;
        mCtrl[1] = mData[1];

        mCtrl[2] = mData[2];
        mCtrl[3] = mData[3] + mDifference;

        mCtrl[4] = mData[2];
        mCtrl[5] = mData[3] - mDifference;

        mCtrl[6] = mData[4] + mDifference;
        mCtrl[7] = mData[5];

        mCtrl[8] = mData[4] - mDifference;
        mCtrl[9] = mData[5];

        mCtrl[10] = mData[6];
        mCtrl[11] = mData[7] - mDifference;

        mCtrl[12] = mData[6];
        mCtrl[13] = mData[7] + mDifference;

        mCtrl[14] = mData[0] - mDifference;
        mCtrl[15] = mData[1];
    }


    public void setCurrentAndTarget(PointF currentPoint, PointF targetPoint) {
        this.currentPoint = currentPoint;
        this.targetPoint = targetPoint;
        float distance = targetPoint.x - currentPoint.x;
        moveDistance = distance > 0 ? distance - 2 * stretchDistance : distance + 2 * stretchDistance;
    }

    public void setProgress(float progress) {
        if ((progress > 0 && progress <= 0.2) || (progress < 0 && progress >= -0.2)) {
            model1(progress);
        } else if ((progress > 0.2 && progress <= 0.5) || (progress < -0.2 && progress >= -0.5)) {
            model2(progress);
        } else if ((progress > 0.5 && progress <= 0.8) || (progress < -0.5 && progress >= -0.8)) {
            model3(progress);
        } else if ((progress > 0.8 && progress <= 0.9) || (progress < -0.8 && progress >= -0.9)) {
            model4(progress);
        } else if ((progress > 0.9 && progress < 1) || (progress < -0.9 && progress > -1)) {
            model5(progress);
        }
//        } else if (progress >= 1 || progress <= -1) {
//            Log.i(TAG,"-------------------------------------------");
////            centerX = targetPoint.x;
////            centerY = targetPoint.y;
////            initControlPoint();
//        }
    }


    public void model1(float progress) {
        if (progress > 0)
            mData[2] = centerX + radius + stretchDistance * progress * 5;

        if (progress < 0)
            mData[6] = centerX - radius + stretchDistance * progress * 5;

        mCtrl[2] = mData[2];
        if (progress > 0)
            mCtrl[3] = mData[3] + mDifference + cDistance * progress * 5;

        mCtrl[4] = mData[2];
        if (progress > 0)
            mCtrl[5] = mData[3] - mDifference - cDistance * progress * 5;

        mCtrl[10] = mData[6];
        if (progress < 0)
            mCtrl[11] = mData[7] - mDifference + cDistance * progress * 5;

        mCtrl[12] = mData[6];
        if (progress < 0)
            mCtrl[13] = mData[7] + mDifference - cDistance * progress * 5;
    }

    public void model2(float progress) {
        model1(progress > 0 ? 0.2f : -0.2f);

        progress = progress > 0 ? (progress - 0.2f) * (10f / 3) : (progress + 0.2f) * (10f / 3);
        //初始化數據點
        mData[0] = centerX + stretchDistance * progress;

        if (progress > 0)
            mData[2] = centerX + radius + stretchDistance * (1 + progress);
        else
            mData[2] = centerX + radius;


        mData[4] = centerX + stretchDistance * progress;

        if (progress < 0)
            mData[6] = centerX - radius - stretchDistance + stretchDistance * progress;
        else
            mData[6] = centerX - radius;


        //初始化控制點
        mCtrl[0] = mData[0] + mDifference;

        mCtrl[2] = mData[2];
        if (progress > 0)
            mCtrl[3] = mData[3] + mDifference + cDistance;
        else
            mCtrl[3] = mData[3] + mDifference - cDistance * progress;

        mCtrl[4] = mData[2];
        if (progress > 0)
            mCtrl[5] = mData[3] - mDifference - cDistance;
        else
            mCtrl[5] = mData[3] - mDifference + cDistance * progress;


        mCtrl[6] = mData[4] + mDifference;

        mCtrl[8] = mData[4] - mDifference;

        mCtrl[10] = mData[6];
        if (progress > 0)
            mCtrl[11] = mData[7] - mDifference - cDistance * progress;
        else
            mCtrl[11] = mData[7] - mDifference - cDistance;

        mCtrl[12] = mData[6];
        if (progress > 0)
            mCtrl[13] = mData[7] + mDifference + cDistance * progress;
        else
            mCtrl[13] = mData[7] + mDifference + cDistance;


        mCtrl[14] = mData[0] - mDifference;
    }

    public void model3(float progress) {
        model2(progress > 0 ? 0.5f : -0.5f);
        progress = progress > 0 ? (progress - 0.5f) * (10f / 3) : (progress + 0.5f) * (10f / 3);

        //初始化數據點
        if (progress > 0)
            mData[0] = centerX + moveDistance * progress + stretchDistance;
        else
            mData[0] = centerX - moveDistance * progress - stretchDistance;


        if (progress > 0)
            mData[2] = centerX + moveDistance * progress + radius + 2 * stretchDistance;
        else
            mData[2] = centerX - moveDistance * progress + radius;


        if (progress > 0)
            mData[4] = centerX + moveDistance * progress + stretchDistance;
        else
            mData[4] = centerX - moveDistance * progress - stretchDistance;


        if (progress > 0)
            mData[6] = centerX + moveDistance * progress - radius;
        else
            mData[6] = centerX - moveDistance * progress - radius - 2 * stretchDistance;

        //初始化控制點
        mCtrl[0] = mData[0] + mDifference;

        mCtrl[2] = mData[2];
        mCtrl[3] = mData[3] + mDifference + cDistance;

        mCtrl[4] = mData[2];
        mCtrl[5] = mData[3] - mDifference - cDistance;

        mCtrl[6] = mData[4] + mDifference;

        mCtrl[8] = mData[4] - mDifference;

        mCtrl[10] = mData[6];
        mCtrl[11] = mData[7] - mDifference - cDistance;

        mCtrl[12] = mData[6];
        mCtrl[13] = mData[7] + mDifference + cDistance;

        mCtrl[14] = mData[0] - mDifference;
    }

    public void model4(float progress) {


        model3(progress > 0 ? 0.8f : -0.8f);

        progress = progress > 0 ? (progress - 0.8f) * 10 : (progress + 0.8f) * 10;

        //初始化數據點
        if (progress > 0)
            mData[0] = centerX + moveDistance + stretchDistance + stretchDistance * progress;
        else
            mData[0] = centerX + moveDistance - stretchDistance + stretchDistance * progress;

        if (progress > 0)
            mData[2] = centerX + moveDistance + radius + 2 * stretchDistance;
        else
            mData[2] = centerX + moveDistance + radius + stretchDistance * progress;

        if (progress > 0)
            mData[4] = centerX + moveDistance + stretchDistance + stretchDistance * progress;
        else
            mData[4] = centerX + moveDistance - stretchDistance + stretchDistance * progress;

        if (progress > 0)
            mData[6] = centerX + moveDistance - radius + stretchDistance * progress;
        else
            mData[6] = centerX + moveDistance - radius - 2 * stretchDistance;

        //初始化控制點
        mCtrl[0] = mData[0] + mDifference;

        mCtrl[2] = mData[2];
        if (progress > 0)
            mCtrl[3] = mData[3] + mDifference + cDistance - cDistance * progress;
        else
            mCtrl[3] = mData[3] + mDifference + cDistance;

        mCtrl[4] = mData[2];
        if (progress > 0)
            mCtrl[5] = mData[3] - mDifference - cDistance + cDistance * progress;
        else
            mCtrl[5] = mData[3] - mDifference - cDistance;

        mCtrl[6] = mData[4] + mDifference;

        mCtrl[8] = mData[4] - mDifference;

        mCtrl[10] = mData[6];
        if (progress > 0)
            mCtrl[11] = mData[7] - mDifference - cDistance;
        else
            mCtrl[11] = mData[7] - mDifference - cDistance - cDistance * progress;

        mCtrl[12] = mData[6];
        if (progress > 0)
            mCtrl[13] = mData[7] + mDifference + cDistance;
        else
            mCtrl[13] = mData[7] + mDifference + cDistance + cDistance * progress;

        mCtrl[14] = mData[0] - mDifference;
    }

    public void model5(float progress) {
        model4(progress > 0 ? 0.9f : -0.9f);

        progress = progress > 0 ? (progress - 0.9f) * 10 : (progress + 0.9f) * 10;

        //初始化數據點
        if (progress > 0)
            mData[0] = centerX + moveDistance + 2 * stretchDistance;
        else
            mData[0] = centerX + moveDistance - 2 * stretchDistance;

        if (progress > 0)
            mData[2] = centerX + moveDistance + radius + 2 * stretchDistance;
        else
            mData[2] = (float) (centerX + moveDistance + radius - stretchDistance - (Math.sin(Math.PI * 3 / 2 * Math.abs(progress) - Math.PI / 2) + 1) * stretchDistance);


        if (progress > 0)
            mData[4] = centerX + moveDistance + 2 * stretchDistance;
        else
            mData[4] = centerX + moveDistance - 2 * stretchDistance;

        if (progress > 0)
            mData[6] = (float) (centerX + moveDistance - radius + stretchDistance + (Math.sin(Math.PI * 3 / 2 * progress - Math.PI / 2) + 1) * stretchDistance);
        else
            mData[6] = centerX + moveDistance - radius - 2 * stretchDistance;


        //初始化控制點
        mCtrl[0] = mData[0] + mDifference;

        mCtrl[2] = mData[2];
        if (progress < 0)
            mCtrl[3] = mData[3] + mDifference + cDistance + cDistance * progress;

        mCtrl[4] = mData[2];
        if (progress < 0)
            mCtrl[5] = mData[3] - mDifference - cDistance - cDistance * progress;

        mCtrl[6] = mData[4] + mDifference;

        mCtrl[8] = mData[4] - mDifference;

        mCtrl[10] = mData[6];
        if (progress > 0)
            mCtrl[11] = mData[7] - mDifference - cDistance + cDistance * progress;

        mCtrl[12] = mData[6];
        if (progress > 0)
            mCtrl[13] = mData[7] + mDifference + cDistance - cDistance * progress;

        mCtrl[14] = mData[0] - mDifference;
    }



    public void drawCircle(Canvas canvas, Paint mPaint) {
        Path path = new Path();
        path.moveTo(mData[0], mData[1]);

        path.cubicTo(mCtrl[0], mCtrl[1], mCtrl[2], mCtrl[3], mData[2], mData[3]);
        path.cubicTo(mCtrl[4], mCtrl[5], mCtrl[6], mCtrl[7], mData[4], mData[5]);
        path.cubicTo(mCtrl[8], mCtrl[9], mCtrl[10], mCtrl[11], mData[6], mData[7]);
        path.cubicTo(mCtrl[12], mCtrl[13], mCtrl[14], mCtrl[15], mData[0], mData[1]);

        canvas.drawPath(path, mPaint);
    }


    public void resetCircular(PointF pointF) {
        setCenter(pointF.x, pointF.y);
        initControlPoint();
    }

}

確定子View點擊位置

通過OnTouchEvent 方法計算觸摸點在哪個子View的繪制范圍內,確定點擊位置

    float touchX = 0;
    float touchY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchX = event.getX();
                touchY = event.getY();
                break;

            case MotionEvent.ACTION_UP:
                Log.i(TAG, "touchX: " + touchX + "  touchY: " + touchY);
                for (int i = 0; i < anchorList.size(); i++) {
                    PointF pointF = anchorList.get(i);
                    if (touchX > (pointF.x - childSideLength / 2) && touchX < (pointF.x + childSideLength / 2) && touchY > (pointF.y - childSideLength / 2) && touchY < (pointF.y + childSideLength / 2)) {
                        onClickIndex(i);
                    }
                }
                break;
        }
        return true;
    }


    private void onClickIndex(int position) {
        if (!isAnimatorStart && !isViewPagerScoll && position != currentPosition) {
            targetPosition = position;
            isAnimatorStart = true;
            startAnimator(); //開始動畫
            clickAnimator(); //點擊效果
            if (viewPager != null) {
                viewPager.setCurrentItem(position);
            }
//            currentPosition = position;
            Log.i(TAG, "點擊了第 " + position + " 項!");
        }
    }

點擊切換動畫

通過ValueAnimator動態更改貝塞爾小球的繪制進度

  /**
     * 切換動畫
     */
    private void startAnimator() {
        bezierCircular.setCurrentAndTarget(anchorList.get(currentPosition), anchorList.get(targetPosition));
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, targetPosition > currentPosition ? 1 : -1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                bezierCircular.setProgress((Float) animation.getAnimatedValue());
                bezierPaint.setColor(circularColors.size() > 0 ? setCircularColor(Math.abs((Float) animation.getAnimatedValue())) : circularColor);
                postInvalidate();
            }
        });

        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                currentPosition = targetPosition;
                bezierPaint.setColor(circularColors.size() > 0 ? circularColors.get(currentPosition) : circularColor);
                bezierCircular.resetCircular(anchorList.get(currentPosition));
                isAnimatorStart = false;
                postInvalidate();
                super.onAnimationEnd(animation);
            }
        });

        int count = Math.abs(targetPosition - currentPosition);
        if (count == 0) {
            return;
        }
        int duration = 600;
        valueAnimator.setDuration(duration);
        valueAnimator.start();
    }

與ViewPager聯動

與ViewPager的聯動這一塊挺頭疼的,ViewPager滾動過程中設置滑動監聽 void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) 回調方法中的 positionOffset 參數,在從左往右滑是0~1逐漸增大,但是最后又會突變到0。而且 void onPageSelected(int position)回調方法并不是在ViewPager滑動結束的時候調用,而是在你的手指離開時調用,有可能ViewPager還在慣性滑動的時候void onPageSelected(int position)方法已經調用了,所以也沒辦法通過這個回調來確定 currentPositontargetPosition

通過觀察,ViewPager的滑動監聽 void onPageScrollStateChanged(int state)回調方法中有三個狀態

  1. state == 1 表示正在滑動
  2. state == 2 表示滑動結束
  3. state == 0 表示什么都沒有做

這里的滑動指的是手指在屏幕上的滑動,而當ViewPager慣性滑動結束時 state == 0,所以最后決定在void onPageScrollStateChanged(int state)方法中進行相關處理。

 public void setViewPager(ViewPager viewPager) {
        this.viewPager = viewPager;

        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                if (anchorList != null && anchorList.size() > 0 && !isAnimatorStart) {
                    isViewPagerScoll = true;
                    updateDrop(position, positionOffset, positionOffsetPixels);
                }
                // 頁面正在滾動時不斷調用
                Log.d(TAG, "onPageScrolled————>" + "    position:" + position + "    positionOffest:" + positionOffset + "    positionOffsetPixels:" + positionOffsetPixels);
            }

            @Override
            public void onPageSelected(int position) {
                Log.e(TAG, "onPagerSelected————>    position:" + position);
                isSelected = true;
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                if (state == 0 && isSelected && !isAnimatorStart) {
//                    Log.e(TAG, "onPageScrollStateChanged————>    設置狀態:");
                    isSelected = false;
                    isViewPagerScoll = false;
                    bezierCircular.setProgress(direction ? 1.0f : -1.0f);
                    currentPosition = targetPosition;

//                    Log.i(TAG, "currentPosition::::" + currentPosition);
                    bezierPaint.setColor(circularColors.size() > 0 ? circularColors.get(currentPosition) : circularColor);
                    bezierCircular.resetCircular(anchorList.get(currentPosition));
                    postInvalidate();
                }
                Log.i(TAG, "onPageScrollStateChanged————>    state:" + state);
            }
        });
    }


    float lastProgress = 0;
    float currentProgress = 0;


    //滑動ViewPager時更新指示器的動畫
    private void updateDrop(int position, float positionOffset, int positionOffsetPixels) {

        if ((position + positionOffset) - currentPosition > 0) {
            direction = true;
        } else if ((position + positionOffset) - currentPosition < 0) {
            direction = false;
        }

        //防止數組越界
        if ((!direction && currentPosition - 1 < 0) || (direction && currentPosition + 1 > getChildCount() - 1)) {
            return;
        }

        if (direction) targetPosition = currentPosition + 1;
        else targetPosition = currentPosition - 1;

        currentProgress = positionOffset;

//        Log.e(TAG, "direction:::" + direction + "     currentPosition:::" + currentPosition + "     targetPosition:::" + targetPosition);
        bezierCircular.setCurrentAndTarget(anchorList.get(currentPosition), anchorList.get(targetPosition));

        if (currentProgress == 0 && lastProgress > 0.9) {
            if (lastProgress > 0.9) {
                currentProgress = 1;
            }
            if (lastProgress < 0.1) {
                currentProgress = 0;
            }
        }

        bezierCircular.setProgress(direction ? currentProgress : currentProgress - 1);
        bezierPaint.setColor(circularColors.size() > 0 ? setCircularColor(direction ? currentProgress : 1 - currentProgress) : circularColor);
        invalidate();
        lastProgress = currentProgress;
    }

onDraw(Canvas canvas)

onDraw方法中代碼就很少了

    @Override
    protected void onDraw(Canvas canvas) {

        drawChildBg(canvas);
        bezierCircular.drawCircle(canvas, bezierPaint);
        drawClick(canvas);
        super.onDraw(canvas);
    }

附上子View背景繪制,及點擊效果繪制代碼

   //繪制子View的背景
    private void drawChildBg(Canvas canvas) {
        if (anchorList == null || anchorList.size() == 0) {
            Log.i(TAG, "錨點位置為空");
            return;
        }

        for (int i = 0; i < anchorList.size(); i++) {
            PointF pointF = anchorList.get(i);
            canvas.drawCircle(pointF.x, pointF.y, (childSideLength - 4) / 2, childBgPaint);
        }
    }

    //繪制點擊效果
    private void drawClick(Canvas canvas) {
        PointF pointF = anchorList.get(targetPosition);

        canvas.drawCircle(pointF.x, pointF.y, clickRadius, clickPaint);
    }

效果

最終效果如下,可能與原概念圖有些差距,但也算小有成就吧??

附上github地址:https://github.com/lichenming0516/BezierIndicator

效果圖

小結

通過這兩次自定義View的學習嘗試,讓自己對自定義View的繪制流程有了更深刻的了解,一些常見方法onMeasure()onLayout()onDraw()以及自定義屬性的解析理解的更清晰一點。對于自定義View這座大山應該能算的上爬上半山腰了吧 ??

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,761評論 25 708
  • 內容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 46,868評論 22 665
  • 寂寞是聽見某個熟悉的名字,不小心想起某些事;孤獨是路過我身邊的影子,笑著對我說似曾相識。……
    獨孤那點事閱讀 223評論 0 0
  • 燕燕于飛,差池其羽。之子于歸,遠送于野。瞻望弗及,泣涕如雨。 燕燕于飛,頡之頏之。之子于歸,遠于將之。瞻望弗及,佇...
    綠蔭眠柳閱讀 922評論 0 2
  • 高傲地把頭仰起來把軟弱藏在黑夜里鎧甲召喚神龍的白天與一切無關的人斗爭 她要當生活中的強者所有被她使喚的陛下都像一條...
    彥子楓閱讀 223評論 2 8