自定义StepViews
就是模仿网上那个挺热门的一系列步骤的view(看了一下图片效果,自己尝试写一个,锻炼自己的自定义view)
先上图
首先明确一下这个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