Android自定义控件Android自定义ViewAndroid

自己撸一个StepView

2017-02-22  本文已影响225人  bogerLiu

自定义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

上一篇下一篇

猜你喜欢

热点阅读