自己撸一个StepView

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容