当前位置:   article > 正文

通俗易懂的自定义view详解

自定义view

自定义View的分类

1、继承View

当我们需要实现的效果是一个不规则效果的时候,那么这时就需要继承 View 来实现了,我们需要重写 onDraw 方法,在该方法里实现各种不规则的图形和效果。当我们使用这种方式的时候,需要自己去处理 warp_content 和 padding。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
 
        int width = 0;
        int height =0;
        if(widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        }else {
            //widthMode == MeasureSpec.AT_MOST模式 自己设置控件宽度
            //当是wrap_content或者给具体dp的时候会走这里
            width = mRadius * 2 +  getPaddingRight() + getPaddingLeft();
        }
        if(heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        }else {
            height = mRadius * 2 + getPaddingTop() + getPaddingBottom();
        }
        //注意最后 调用这个方法 让属性生效
        setMeasuredDimension(width,height);
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2、继承ViewGroup

当系统所提供的 LinearLayout、FrameLayout 等布局控件无法满足我们的需求时,这时我们就需要使用这种方式来实现自己想要的布局效果了。当我们使用这种方式的时候,需要重写 onLayout 方法来对子 View 进行布局,以及测量本身和子 View 宽高,还需要处理本身的 padding 和子 View 的 margin。

3、继承已有View

当我们需要基于已有的 View 进行扩展或修改的时候,那么就可以使用这种方式。比如说,我们需要一个圆角的 ImageView,那么这时就可以继承 ImageView 进行修改了。当我们使用这种方式的时候,一般不需要自己去处理 wrap_content 和 padding 等,因为系统控件已经帮我们做好了。

4、继承已有布局

这种方式也叫做:自定义组合 View。该方式比较简单,当我们需要将一组 View 组合在一起,方便后期复用的时候,就可以使用该方法。当我们使用这种方式的时候,不需要去处理 ViewGroup 的测量和布局流程,因为系统控件已经帮我们做好了。

view的绘制流程

每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()

1)、Activity的attach 方法里创建PhoneWindow。

2)、onCreate方法里的 setContentView 会调用PhoneWindow的setContentView方法,创建DecorView并且把xml布局解析然后添加到DecorView中。

3)、在onResume方法执行后,会创建ViewRootImpl,它是最顶级的View,是DecorView的parent,创建之后会调用setView方法。

4)、ViewRootImpl 的 setView方法,会将PhoneWindow添加到WMS中,通过 Session作为媒介。setView方法里面会调用requestLayout,发起绘制请求。

5)、requestLayout 一旦发起,最终会调用 performTraversals 方法,里面将会调用View的三个measure、layout、draw方法,其中View的draw 方法需要一个传一个Canvas参数。

6)、最后通过relayoutWindow 方法将Surface跟当前Window绑定,通过Surface的lockCanvas方法获取Surface的的Canvas,然后View的绘制就通过这个Canvas,最后通过Surface的unlockCanvasAndPost 方法提交绘制的数据,最终将绘制的数据交给SurfaceFlinger去提交给屏幕显示。

一、onMeasure()

onMeasure()是用于测量视图的大小的。

1、View系统的绘制流程会从ViewRootImpl的performTraversals()方法中开始,在其内部调用performMeasure()方法,
2、它里面又调用的View的measure()方法。

measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec【它俩是int类型32位,前2位表示specMode,后30位表示specSize,这么做是为了减少对象的创建】,这两个值分别用于确定视图的宽度和高度的规格和大小。

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。

specMode一共有三种类型

  1. EXACTLY==MATCH_PARENT
  2. AT_MOST==WRAP_CONTENT
  3. UNSPECIFIED

widthMeasureSpec和heightMeasureSpec这两个值,通常情况下,都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?

/*****desiredWindowWidth是window的宽,lp是WindowManager.LayoutParams***/
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
/*****getRootMeasureSpec()*****/
private int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

由以上代码可以看出
1)我们需要自己去处理 warp_content 和 padding。因为无论是match_parent还是warp_content ,size都是windowSize。总是充满全屏的。

2)根视图的widthMeasureSpec和heightMeasureSpec就是window的宽高

3、measure中调用onMeasure(widthMeasureSpec, heightMeasureSpec)方法

这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
  • 1
  • 2
  • 3
  • 4

可以看到mBackground == null 为没有设置背景,那么返回mMinWidth ,也就是android:minWidth 这个属性所指定的值,这个值可以是0 ;如果View 设置了背景,则返回mMinWidth 与背景的最小宽度这两者的最大值。

getSuggestedMinimumWidth() 的返回值就是View 在UNSPECIFIED 情况下的测量宽。

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
  • 1
  • 2
  • 3

调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

如果是ViewGroup,ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小

首先会去遍历当前布局下的所有子视图,然后逐个调用measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)方法来测量相应子视图的大小

然后分别调用了getChildMeasureSpec()方法来去计算子视图的width/heightMeasureSpec

最后调用子视图的measure()方法,并把计算出的width/heightMeasureSpec传递进去,之后的流程就和前面所介绍的一样了

需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。

二、onLayout()

measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。这个方法是用于给视图进行布局的,也就是确定视图的位置。ViewRootImpl的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法来执行此过程

host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
  • 1

layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。可以看到,这里还把刚才测量出的宽度和高度传到了layout()方法中。

在layout()方法中,首先会调用setFrame()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘,同时还会在这里把传递过来的四个参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量。接下来会调用onLayout()方法

View中的onLayout()方法就是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图(ViewGroup)决定子视图的显示位置

ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法

/**如果你想改变View显示的位置,只需要改变childView.layout()方法的四个参数就行了**/
@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (getChildCount() > 0) {
			View childView = getChildAt(0);
			childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

问:getWidth()方法和getMeasureWidth()方法到底有什么区别?
答:首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

//getWidth()=childView.getMeasuredWidth()-0 == childView.getMeasuredWidth()
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
//getWidth()=200-0 != childView.getMeasuredWidth()
childView.layout(0, 0, 200, 200);
  • 1
  • 2
  • 3
  • 4

三、onDraw()

measure和layout的过程都结束后,接下来就进入到draw的过程了。这里才真正地开始对视图进行绘制。ViewRootImpl中会继续执行并创建出一个Canvas对象,然后调用View的draw()方法并传递过来Canvas,来执行具体的绘制工作

在draw()中去调用了一下onDraw()方法,那么onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的。

然后对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码。

view重绘invalidate()

view的invalidate()—>view的invalidateInternal()—>ViewParent接口的invalidateChild()—>ViewGroup的invalidateChild()【因为ViewGroup实现了ViewParent接口】—>ViewRootImpl的invalidateChildInParent()—>最终在scheduleTraversals()方法中postCallback()

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

执行doTraversal();

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

又重新执行了performTraversals();

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
			//重点在这
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
invalidate与postinvalidate、requestLayout区别

invalidate
view调用invalidate将导致当前view的重绘(draw调用),view的父类将不会执行draw方法;viewGroup调用invalidate会使viewGroup的子view调用draw,也就是viewGroup内部的子view进行重绘;

postinvalidate
在非UI线程调用,将非UI线程切换到UI线程,最后也是调用invalidate

requestLayout
只会导致当前view的measure和layout,而draw不一定被执行,只有当view的位置发生改变才会执行draw方法,因此如果要使当前view重绘需要调用invalidate

自定义view支付输入框

1、继承EditView,首先画一个圆角矩形
2、再画五个竖线
3、在画圆点,按输入的字符长度画
在这里插入图片描述

@SuppressLint("AppCompatCustomView")
public class PayEditTextView extends EditText {

    public PayEditTextView(Context context) {
        super(context);
    }

    public PayEditTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
        initPaint();
    }

    private int maxCount = 6;
    private int borderColor = Color.GRAY;
    private int circleColor = Color.BLACK;
    private Paint mPaint;
    private Paint borderPaint;
    private Paint divideLinePaint;
    private Paint circlePaint;
    private RectF rectF = new RectF();

    private void initView() {
        setBackgroundColor(Color.TRANSPARENT);//将背景变透明,去掉EditView的下横线
        setCursorVisible(false);//隐藏光标

        setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxCount)});//设置输入过滤器,输入的最大长度
    }


    private void initPaint() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);

        borderPaint = getPaint(3, Paint.Style.STROKE, borderColor);
        divideLinePaint = getPaint(3, Paint.Style.FILL, borderColor);
        circlePaint = getPaint(5, Paint.Style.FILL, circleColor);

    }

    /**
     * 设置画笔
     *
     * @param strokeWidth 画笔宽度
     * @param style       画笔风格
     * @param color       画笔颜色
     * @return
     */
    private Paint getPaint(int strokeWidth, Paint.Style style, int color) {
        Paint paint = new Paint(ANTI_ALIAS_FLAG);
        paint.setStrokeWidth(strokeWidth);
        paint.setStyle(style);
        paint.setColor(color);
        paint.setAntiAlias(true);

        return paint;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);//禁止EditView的默认实现

        rectF.left = 0;
        rectF.top = 0;
        rectF.right = getWidth();
        rectF.bottom = getHeight();
        canvas.drawRoundRect(rectF, 12, 12, borderPaint);

        int divideLineWStartX = getWidth() / maxCount;
        for (int i = 0; i < maxCount - 1; i++) {
            canvas.drawLine((i + 1) * divideLineWStartX, 0, (i + 1) * divideLineWStartX, getHeight(), divideLinePaint);
        }

        int startX = getWidth() / maxCount / 2;
        LogU.Companion.d("textLength==" + textLength);
        for (int i = 1; i <= textLength; i++) {
            canvas.drawCircle(getWidth() / maxCount * i - startX, getHeight() / 2, getHeight() / 4, circlePaint);
        }

    }

    private int textLength = 0;

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        textLength = text.toString().length();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

自定义view,button提交动画

主要是通过ValueAnimator.addUpdateListener监听动画执行过程改变值来重绘view的大小、样式

ValueAnimator.addUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                two_circle_distance = (int) animation.getAnimatedValue();
                //default_two_circle_distance=(w-h)/2,意思是椭圆变成圆的所需的总值
                int alpha = 255 - (two_circle_distance * 255) / default_two_circle_distance;
                textPaint.setAlpha(alpha);

                invalidate();
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

public class AnimationButton extends View {

    /**
     * view的宽度
     */
    private int width;
    /**
     * view的高度
     */
    private int height;
    /**
     * 圆角半径
     */
    private int circleAngle;
    /**
     * 默认两圆圆心之间的距离=需要移动的距离
     */
    private int default_two_circle_distance;
    /**
     * 两圆圆心之间的距离
     */
    private int two_circle_distance;
    /**
     * 背景颜色
     */
    private int bg_color = 0xffbc7d53;
    /**
     * 按钮文字字符串
     */
    private String buttonString = "确认完成";
    /**
     * 动画执行时间
     */
    private int duration = 1000;
    /**
     * view向上移动距离
     */
    private int move_distance = 300;

    /**
     * 圆角矩形画笔
     */
    private Paint paint;
    /**
     * 文字画笔
     */
    private Paint textPaint;
    /**
     * 对勾(√)画笔
     */
    private Paint okPaint;
    /**
     * 文字绘制所在矩形
     */
    private Rect textRect = new Rect();

    /**
     * 动画集
     */
    private AnimatorSet animatorSet = new AnimatorSet();

    /**
     * 矩形到圆角矩形过度的动画
     */
    private ValueAnimator animator_rect_to_angle;
    /**
     * 矩形到正方形过度的动画
     */
    private ValueAnimator animator_rect_to_square;
    /**
     * view上移的动画
     */
    private ObjectAnimator animator_move_to_up;
    /**
     * 绘制对勾(√)的动画
     */
    private ValueAnimator animator_draw_ok;

    /**
     * 是否开始绘制对勾
     */
    private boolean startDrawOk = false;

    /**
     * 根据view的大小设置成矩形
     */
    private RectF rectf = new RectF();

    /**
     * 路径--用来获取对勾的路径
     */
    private Path path = new Path();
    /**
     * 取路径的长度
     */
    private PathMeasure pathMeasure;
    /**
     * 对路径处理实现绘制动画效果
     */
    private PathEffect effect;
   

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

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

    public AnimationButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LogU.Companion.d("AnimationButton");
        initPaint();

        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                start();
            }
        });
        animatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                reset();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

    /**
     * 初始化所有动画
     */
    private void initAnimation() {
        set_rect_to_angle_animation();
        set_rect_to_circle_animation();
        set_move_to_up_animation();
        set_draw_ok_animation();

        animatorSet
                .play(animator_move_to_up)
                .before(animator_draw_ok)//在play动画之后执行
                .after(animator_rect_to_square)//在play动画之前执行
                .after(animator_rect_to_angle);//在play动画之前执行

    }


    /**
     * 设置矩形过度到圆角矩形的动画
     */
    private void set_rect_to_angle_animation() {
        animator_rect_to_angle = ValueAnimator.ofInt(0, height / 2);
        animator_rect_to_angle.setDuration(duration);
        animator_rect_to_angle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                circleAngle = (int) animation.getAnimatedValue();
                invalidate();
            }

        });

    }


    /**
     * 设置圆角矩形过度到圆的动画
     */
    private void set_rect_to_circle_animation() {
        animator_rect_to_square = ValueAnimator.ofInt(0, default_two_circle_distance);
        animator_rect_to_square.setDuration(duration);
        animator_rect_to_square.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                two_circle_distance = (int) animation.getAnimatedValue();
                //default_two_circle_distance=(w-h)/2,意思是椭圆变成圆的所需的总值
                int alpha = 255 - (two_circle_distance * 255) / default_two_circle_distance;
                textPaint.setAlpha(alpha);

                invalidate();
            }
        });

    }


    /**
     * 设置view上移的动画
     */
    private void set_move_to_up_animation() {
        final float curTranslationY = this.getTranslationY();
        animator_move_to_up = ObjectAnimator.ofFloat(this, "translationY", curTranslationY, curTranslationY - move_distance);
        animator_move_to_up.setDuration(duration);
        animator_move_to_up.setInterpolator(new AccelerateDecelerateInterpolator());
    }

    /**
     * 绘制对勾的动画
     */
    private void set_draw_ok_animation() {
        animator_draw_ok = ValueAnimator.ofFloat(1, 0);
        animator_draw_ok.setDuration(duration);
        animator_draw_ok.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                startDrawOk = true;
                float value = (Float) animation.getAnimatedValue();

                effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
                okPaint.setPathEffect(effect);
                invalidate();
            }
        });
    }

    private void initPaint() {

        paint = new Paint();
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        paint.setColor(bg_color);

        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(40);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setAntiAlias(true);

        okPaint = new Paint();
        okPaint.setStrokeWidth(10);
        okPaint.setStyle(Paint.Style.STROKE);
        okPaint.setAntiAlias(true);
        okPaint.setColor(Color.WHITE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        LogU.Companion.d("onSizeChanged");
        width = w;
        height = h;

        default_two_circle_distance = (w - h) / 2;

        initOk();
        initAnimation();

    }
    private int mWidth;
    private int mHeight;
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);
        int heightSize=MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            mWidth=widthSize;
        }else {
            mWidth=80*2+getPaddingLeft()+getPaddingRight();
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            mHeight=heightSize;
        }else {
            mHeight=80*2+getPaddingTop()+getPaddingBottom();
        }

        setMeasuredDimension(mWidth,mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        draw_oval_to_circle(canvas);
        drawText(canvas);

        if (startDrawOk) {
            canvas.drawPath(path, okPaint);
        }

    }


    /**
     * 绘制长方形变成圆形
     *
     * @param canvas 画布
     */
    private void draw_oval_to_circle(Canvas canvas) {
        rectf.left = two_circle_distance;
        rectf.top = 0;
        rectf.right = width - two_circle_distance;
        rectf.bottom = height;

        //画圆角矩形
        canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint);

    }


    /**
     * 绘制文字
     *
     * @param canvas 画布
     */
    private void drawText(Canvas canvas) {
        textRect.left = 0;
        textRect.top = 0;
        textRect.right = width;
        textRect.bottom = height;
        Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
        int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        //文字绘制到整个布局的中心位置
        canvas.drawText(buttonString, textRect.centerX(), baseline, textPaint);
    }

    /**
     * 绘制对勾
     */
    private void initOk() {
        //对勾的路径
        path.moveTo(default_two_circle_distance + height / 8 * 3, height / 2);
        path.lineTo(default_two_circle_distance + height / 2, height / 5 * 3);
        path.lineTo(default_two_circle_distance + height / 3 * 2, height / 5 * 2);

        pathMeasure = new PathMeasure(path, true);

    }

    /**
     * 启动动画
     */
    public void start() {
        animatorSet.start();
    }

    /**
     * 动画还原
     */
    public void reset() {
//        startDrawOk = false;
//        circleAngle = 0;
//        two_circle_distance = 0;
//        default_two_circle_distance = (width - height) / 2;
//        textPaint.setAlpha(255);
//        setTranslationY(getTranslationY() + move_distance);
//        invalidate();
    }
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/759839
推荐阅读
相关标签
  

闽ICP备14008679号