自己撸一个StepView

作者: bogerLiu | 来源:发表于2017-02-22 16:54 被阅读225次

    自定义StepViews
    就是模仿网上那个挺热门的一系列步骤的view(看了一下图片效果,自己尝试写一个,锻炼自己的自定义view)
    先上图


    device-2017-02-22-171533.png

    首先明确一下这个view是干嘛的

    1、用于显示步骤
    2、分为横向、纵向
    3、我定义的存在点击交互
    4、这个view主要是用画笔画出来的,画圆,路径,虚线
    5、涉及到view的测绘
    6、涉及到path,以及DashPathEffect
    目前想到的就是这么多,等开始写了遇到了在网上补充

    开工
    首先新建一个view叫做 StepViews ->extends View
    实现他的构造方法

    public StepViews(Context context) {
        this(context, null);
    }
    
    public StepViews(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public StepViews(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }
    

    一个参数的是在java代码中new出来的
    两个参数的是在布局中,第二个是样式,
    通过this,让所有构造方法都调用第三种,这样方便管理,查看view的源码,view也是通过这种方式,方便了Google的工
    程师不断更新,添加view的构造方法而不会影响之前的代码

    创建一个init方法用于初始化

    private void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.StepViews);
        mOrientation = ta.getInt(R.styleable.StepViews_orientation, 1);
        ta.recycle();
    
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mLinePath = new Path();
    }
    

    这里进行获取view的属性,以及对画笔,路径的初始化,画笔最好在这里初始化,不要在onDraw方法中(会导致内存抖动)

    接下来就是onmeasure方法了,因为这是我第一次写自定义view的文章,这里讲的会比较细,也很有可能出错,非常欢迎您指出,共同成长
    首先要明确onMeasure是要干什么
    onMeasure是要对view进行测量设置view的宽高
    对于传递过来的参数有两个widthMeasureSpec,heightMeasureSpec
    这是1个32位整型,高两位表示的测量模式(为什么是两位?因为有三种测量模式。后面30位是测量值)
    这两个参数传递过来,告诉view的测量模式是什么,他自己测绘的宽高是多少
    但是有人就会说了 这不是view都给我们测绘好了,我们还测量什么?
    其实不是这样的,通过查看view的源码发现

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    
    
     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;
    }
    

    这里发现view 对AT_MOST和EXACTLY进行穿透处理,所以AT_MOST和EXACTLY得到的结果是一样,这样就不行了,我们自己设置自己的view是wrap_content,但是得出来的却是match_parent,肯定不可以的
    这时我们就需要处理AT_MOST这种测绘模式,
    对于其他的测绘模式,大家肯定也都在别的博客中了解,也很明白。我开始自定义view时最不明白就是AT_MOST,这里就着重讲解一下我理解的AT_MOST
    AT_MOST
    我理解是view要多大 就给多大,但是只要包裹住view就好,就像是给view覆层修身膜一样,这样 就明确了,在处理的AT_MOST的时候就是要一个刚好包裹上view的膜就可以了
    所以我们就可以开工了,首先是你要明白你这个view心中最小是多少minValue,然后是跟测绘出来的ViewSize,取出最小值就行了
    下面就是我onmeasure的过程(过程中处理了一下对当minValue值大于屏幕宽度的时候对minValue进行一个缩小),至于是否带上padding(我认为在处理测绘的时候,Padding是处理里面的偏向,不应该放在onMeasure中处理,应该放在onDraw中)

        @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mSteps.size() == 0) {
            setMeasuredDimension(0, 0);
        } else {
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
            if (heightMode == MeasureSpec.EXACTLY) {
                mHeight = heightSize;
            } else if (heightMode == MeasureSpec.AT_MOST) {
                mHeight = Math.min(mCircleRadius * 2, heightSize);
            } else {
                mHeight = heightSize;
            }
    
            int desireWidth = (mCircleRadius * mSteps.size() + mLineLenth * (mSteps.size() - 1)) * 2;
            if (widthMode == MeasureSpec.EXACTLY) {
                if (desireWidth > widthSize) {
                    float v = desireWidth * 1f / widthSize;
                    mCircleRadius = (int) (mCircleRadius * 1f / v);
                    mLineLenth = (int) (mLineLenth * 1f / v);
                }
                mWidth = widthSize;
            } else if (widthMode == MeasureSpec.AT_MOST) {
                if (desireWidth > mScreenWidth) {
                    float v = desireWidth * 1f / mScreenWidth;
                    mCircleRadius = (int) (mCircleRadius * 1f / v);
                    mLineLenth = (int) (mLineLenth * 1f / v);
                    desireWidth = (mCircleRadius * mSteps.size() + mLineLenth * (mSteps.size() - 1)) * 2;
                }
                mWidth = Math.min(desireWidth, widthSize);
            } else {
                mWidth = widthSize;
            }
            setMeasuredDimension(mWidth, mHeight);
        }
    
    }
    

    接下来就是onDraw方法 onDraw就是让你在这个view的图纸上画图,它给你提供一个canvas
    先上代码,这个是画这个view的方法

    @Override
    protected void onDraw(Canvas canvas) {
    
        if (mStepCount == 0) {
            super.onDraw(canvas);
        } else {
            int realWidth = mWidth - getPaddingLeft() - getPaddingRight();
            int averageWidth = realWidth / mStepCount;
            int realHeight = mHeight - getPaddingTop() - getPaddingBottom();
            float x = averageWidth / 2;
            float y = mCircleRadius + 4;
    
            float textX = 0;
            float textY = mCircleRadius * 2 + 8;
            for (int i = 0; i < mStepCount; i++) {
                Rect rect = new Rect();
                mPaint.getTextBounds(mSteps.get(i), 0, mSteps.get(i).length(), rect);
    
                if (i < mCurrentSteps) {
                    mPaint.setColor(FINISH_COLOR);
                    mPaint.setPathEffect(null);
                } else if (i == mCurrentSteps) {
                    mPaint.setColor(CURRENT_COLOR);
                    mPaint.setPathEffect(mDashPathEffect);
                } else {
                    mPaint.setColor(LAST_COLOR);
                    mPaint.setPathEffect(mDashPathEffect);
                }
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(x, y, mCircleRadius, mPaint);
    
                mPaint.setColor(Color.WHITE);
                canvas.drawText(mSteps.get(i), x - rect.width() / 2, textY + rect.height(), mPaint);
                mPaint.setStyle(Paint.Style.STROKE);
    
                if (i < mStepCount - 1) {
                    float moveStart = x + mCircleRadius;
                    float lineStart = x + averageWidth - mCircleRadius;
                    mLinePath.reset();
                    mLinePath.moveTo(moveStart, y);
                    mLinePath.lineTo(lineStart, y);
                    mPaint.setColor(Color.WHITE);
                    canvas.drawPath(mLinePath, mPaint);
                }
                x = x + averageWidth;
                textX = textX + averageWidth / 2;
            }
        }
    
    }
    

    首先明白 你要画什么?(画圆圈,直线,虚线,文字) 要怎么画?(我直接就先横着画了,先打算完成这一版,然后开始写支持竖着画的)
    明白了自己的目的那么就开工
    首先要学会偷懒,你发现你传来的list里面什么都没有,那么你就什么也不用画了,这就是一个if判断的作用。
    然后你就开始正式的画
    第一:首先你要知道你画布的宽高,注意这里是画布的宽高,所以你要去除你上下左右的padding,所以就是
    int realWidth = mWidth - getPaddingLeft() - getPaddingRight();
    int averageWidth = realWidth / mStepCount;
    int realHeight = mHeight - getPaddingTop() - getPaddingBottom();

    然后你需要将你的宽度按照有多少步来进行分块,这样你画的每个圈 就是在这个块里,这样画起来比较简单。
    接下来就是画圈 文字

    for (int i = 0; i < mStepCount; i++) {
                Rect rect = new Rect();
                mPaint.getTextBounds(mSteps.get(i), 0, mSteps.get(i).length(), rect);
    
                if (i < mCurrentSteps) {
                    mPaint.setColor(FINISH_COLOR);
                    mPaint.setPathEffect(null);
                } else if (i == mCurrentSteps) {
                    mPaint.setColor(CURRENT_COLOR);
                    mPaint.setPathEffect(mDashPathEffect);
                } else {
                    mPaint.setColor(LAST_COLOR);
                    mPaint.setPathEffect(mDashPathEffect);
                }
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(x, y, mCircleRadius, mPaint);
    
                mPaint.setColor(Color.WHITE);
                canvas.drawText(mSteps.get(i), x - rect.width() / 2, textY + rect.height(), mPaint);
                mPaint.setStyle(Paint.Style.STROKE);
    
                if (i < mStepCount - 1) {
                    float moveStart = x + mCircleRadius;
                    float lineStart = x + averageWidth - mCircleRadius;
                    mLinePath.reset();
                    mLinePath.moveTo(moveStart, y);
                    mLinePath.lineTo(lineStart, y);
                    mPaint.setColor(Color.WHITE);
                    canvas.drawPath(mLinePath, mPaint);
                }
                x = x + averageWidth;
                textX = textX + averageWidth / 2;
    }
    

    这里要说明一下虚线的实现,new DashPathEffect(new float[]{8, 4}, 0); 前面的数组的意思是实线多长,虚线多长,第二个参数是第一条实线的偏移量。
    这样就实现了虚线

    这就是我stepView的实现过程.
    接下来我要实现stepview里那样 有对号的,我之前的想法是画出来对号,但是我发现太他么的墨迹了,所以后来我就无耻的查看了一下stepview的代码,嘿嘿嘿,发现是图片做的,所以接下里我也打算
    1.写一版本图片做成的StepView (已经写好了地址是https://github.com/bolevw/LBViews/blob/master/app/src/main/java/com/test/lbviews/views/ImageStepViews.java
    2.然后是发现了一个 DashPathEffect的动画效果,也写一个文章讲一下
    3.属性动画实现的圆形进度条

    未来的打算,
    1.写一些git的教程
    2.一定写一些自定义view的教程,viewgroup教程,把这个吃透,向着中级android开发进军
    3.写一些颈椎保养的文章,

    最后感谢各位读完我这么挫的第一篇文章
    这是这个view的git的地址 https://github.com/bolevw/LBViews/blob/master/app/src/main/java/com/test/lbviews/views/StepViews.java
    最后真的希望大家多给意见,指导共同成长,QQ 634109509, 邮箱bolevw@gmail.com

    相关文章

      网友评论

      本文标题:自己撸一个StepView

      本文链接:https://www.haomeiwen.com/subject/maemwttx.html