1,项目中要用到个人信息验证的在网上找了一下,好像有封装好了的StepView,首先感谢一下作者,这是作者的地址,效果图如下:
2,正准备撸起袖子就是一顿复制粘贴的时候,发现效果图成这个样子了(其实这里我们可以改变作者提供的方法改变文字的大小来解决这个问题,但是ui就是这个大的文字设计,我们猿拉不下脸来要别人改东西,所以就自己改了)
西坝,这要是给项目经理看到了那中午加鸡腿这件事又泡汤了啊,so,让我们自己先看看作者源码,再自己重新搞一个呗
- 准备阶段
既然是自定义view,让我们先来热身一下,结合我们想要实现的,我们先来画一个空心矩形,线,虚线
首先我们来画一个矩形
//绘制矩形Paint paint = new Paint();//设置实心paint.setStyle(Paint.Style.STROKE);//设置消除锯齿paint.setAntiAlias(true);//设置画笔颜色paint.setColor(Color.RED);//设置paint的外边框宽度paint.setStrokeWidth(40);//绘制矩形canvas.drawRect(200, 200, 800, 420, paint);
效果图如下:
绘制虚线
Paint paint = new Paint();paint.setAntiAlias(true);paint.setColor(Color.RED);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(2);/**** 构造函数为DashPathEffect(float[] intervals, float offset),其中intervals为虚线的ON和OFF数组,该数组的length必须大于等于2,phase为绘制时的偏移量。本代码中先绘制8长度的实现,再绘制8长度的虚线,再绘制8长度的实现,再绘制8长度的虚线*/DashPathEffect mEffect = new DashPathEffect(new float[]{8, 8, 8, 8,}, 1);Path path = new Path();//移动画笔到坐标200,600 这个点path.moveTo(200, 600);//绘制直线path.lineTo(800, 600);paint.setPathEffect(mEffect);canvas.drawPath(path, paint);
这里要注意下DashPathEffect,具体的作用就是帮助绘制虚线,绘制效果图如下:
ok,简单的绘制图形说完了就来说说开始我们控件的绘制吧,先说一下我们的思路,我们打算把我们的控件的绘制分成两步,第一步:绘制上面的图形并记录位置,第二步:在对应每一步的图形下绘制对应的步骤名称,那我们先来将我们的图形的绘制。
- 绘制图形
先来看看我们要实现的效果图,如下图所示:
分析难点,首先我们主要的问题是,怎么样将里面的图形控件放置在整个大的view的最正中间,这关系着我们在OnDraw()方法中绘制具体的view的起点坐标和重点坐标的计算,最主要的是找到第一个控件距离最左端的距离,那么下面我们在具体情况下来分析吧,假设到最左端的距离是mLeftPadding
①假设只存在一个“接单”这一个步骤,那么我们圆形控件最右端的,如图下红线标识的地方
那么我们的是这样计算的
mLeftPadding= (getWidth() - 1*(mCircleRadius*2))/2;
②假设存在个“接单”和“打包”这两个步骤,那么我们圆形控件最右端的该怎么计算呢
其实也很简单,只需要将中间的两个圆的直径减去,再减去虚线距离再除以而就搞定了(这里假设两个圆的半径都相同,每天线的长度也相同),所以公式变成了下面这种
mLeftPadding= (getWidth() - 2*(mCircleRadius*2)-1*mLineLength)/2;
③假设存在个“接单”和“打包”和“发出”这三个步骤,那么我们圆形控件最右端的该怎么计算呢
其实也很简单,只需要将中间的三个圆的直径减去,再减去两虚线距离再除以而就搞定了(这里假设两个圆的半径都相同,每天线的长度也相同),所以公式变成了下面这种
mLeftPadding= (getWidth() - 3*(mCircleRadius*2)-2*mLineLength)/2;
ok,我们其实感觉是不是有点规律了,每一个都是减去n个小圆的直径再减去n-1个直线距离,所以我们可以这样写
mLeftPadding = (getWidth() - n * mCircleRadius * 2 - (n - 1) * mLinePadding) / 2;
然后我们可以通过mLeftPadding这个参数,将所有的图表的X坐标计算出来了,我们这里用一个集合来记录所有绘制的X坐标(方便我们绘制线和图标),具体代码如下:
for(int i = 0; i
第一步:到了这里我们基本上把难点都解决了,现在我们创建一个StepViewIndicator,继承View,首先重写三个构造函数,再在创建构造函数的同时初始化类似于画笔、数组之类的属性,关键代码如下:
//下面这是重写构造函数
public StepViewIndicator(Context context) {this(context, null);}public StepViewIndicator(Context context, &#64;Nullable AttributeSet attrs) {this(context, attrs, 0);}public StepViewIndicator(Context context, &#64;Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}//这里是初始化属性/*** 初始化常见属性*/private void init() {mStepBeanList &#61; new ArrayList<>();mPath &#61; new Path();mEffect &#61; new DashPathEffect(new float[]{8, 8, 8, 8}, 1);//设置已完成的初始化基本设置mComplectedXPosition &#61; new ArrayList();mComplectedPaint &#61; new Paint();mComplectedPaint.setAntiAlias(true);mComplectedPaint.setColor(mCompletedLineColor);mComplectedPaint.setStyle(Paint.Style.FILL);mComplectedPaint.setStrokeWidth(2);//设置未完成的初始化基本设置mUnComplectedPaint &#61; new Paint();mUnComplectedPaint.setAntiAlias(true);mUnComplectedPaint.setColor(mUnCompletedLineColor);mUnComplectedPaint.setStyle(Paint.Style.STROKE);mUnComplectedPaint.setStrokeWidth(2);mUnComplectedPaint.setPathEffect(mEffect);//圆的半径、线的长度、线的高度基本值设置mCompletedLineHeight &#61; 0.05f * defaultStepIndicatorNum;mCircleRadius &#61; 0.28f * defaultStepIndicatorNum;mLinePadding &#61; 1.5f * defaultStepIndicatorNum;//初始化三种状态下的图片mCompleteIcon &#61; ContextCompat.getDrawable(getContext(), R.drawable.complted);mAttentionIcon &#61; ContextCompat.getDrawable(getContext(), R.drawable.attention);mDefaultIcon &#61; ContextCompat.getDrawable(getContext(), R.drawable.default_icon);}
再来看看我们这个view中要用到的所有属性&#xff0c;代码里面都注释好了所有属性的含义了&#xff0c;我就不再给大家一一解释了
//定义默认的高度private int defaultStepIndicatorNum &#61; (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());//直线private Path mPath;//虚线绘制函数private DashPathEffect mEffect;//完成的下标集合private List
第二步&#xff1a;重写OnMeasuer&#xff08;&#xff09;方法&#xff0c;这里只是丈量了一下控件的宽高
/*** 测量自身的高度** &#64;param widthMeasureSpec* &#64;param heightMeasureSpec*/&#64;Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width &#61; defaultStepIndicatorNum * 2;if (MeasureSpec.UNSPECIFIED !&#61; MeasureSpec.getMode(widthMeasureSpec)) {width &#61; MeasureSpec.getSize(widthMeasureSpec);}int height &#61; defaultStepIndicatorNum;if (MeasureSpec.UNSPECIFIED !&#61; MeasureSpec.getMode(heightMeasureSpec)) {height &#61; Math.min(height, MeasureSpec.getSize(heightMeasureSpec));}setMeasuredDimension(width, height);}
这里要注意一下MeasureSpec的三种测量模式&#xff0c;具体解释如下&#xff08;这点自定义view经常用到&#xff0c;在这里还是给大家普及一下&#xff09;&#xff1a;
mode共有三种情况&#xff0c;取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。MeasureSpec.EXACTLY:精确尺寸&#xff0c;当我们将控件的layout_width或layout_height指定为具体数值时andorid:layout_width&#61;"50dip"&#xff0c;或者为FILL_PARENT是&#xff0c;都是控件大小已经确定的情况&#xff0c;都是精确尺寸。MeasureSpec.AT_MOST:最大尺寸&#xff0c;当控件的layout_width或layout_height指定为WRAP_CONTENT时&#xff0c;控件大小一般随着控件的子空间或内容进行变化&#xff0c;此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此&#xff0c;此时的mode是AT_MOST&#xff0c;size给出了父控件允许的最大尺寸。MeasureSpec.UNSPECIFIED:未指定尺寸&#xff0c;这种情况不多&#xff0c;一般都是父控件是AdapterView&#xff0c;通过measure方法传入的模式,例如在ScrollView里面嵌套ListView&#xff0c;里面的listview的高度不确定&#xff0c;这时候要重写listview&#xff0c;将我们的布局撑到最大&#xff0c;代码如下&#xff1a;
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int expandSpec &#61; MeasureSpec.makeMeasureSpec(1000>> 2,MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, expandSpec);}
1000的二进制&#xff1a;1111101000
右移2位后&#xff1a;11111010&#xff0c;十进制为&#xff1a;250
这样就指定了listview的高度为250px以内的最大允许值&#xff08;一般就是250&#xff09;。
第三步&#xff1a;重写OnSizeChange&#xff08;&#xff09;方法
/*** View大小改变** &#64;param w* &#64;param h* &#64;param oldw* &#64;param oldh*/&#64;Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//下面这三句代码只是为了绘制矩形的线mCenterY &#61; 0.5f * getHeight();//获取左上方Y的位置&#xff0c;获取该点的意义是为了方便画矩形左上的Y位置mLeftY &#61; mCenterY - (mCompletedLineHeight / 2);//获取右下方Y的位置&#xff0c;获取该点的意义是为了方便画矩形右下的Y位置mRightY &#61; mCenterY &#43; mCompletedLineHeight / 2;mComplectedXPosition.clear();//计算所有总空间离最左边的距离&#xff0c;并记录所有的圆心x轴的坐标for (int i &#61; 0; i
在这里就使用到了我们上面的公式了&#xff0c;而且再记录我们的矩形线段的起始和结束的Y坐标&#xff0c;还有OnSizeChange&#xff08;&#xff09;这个方法是在View的大小发生改变的时候进行调用&#xff0c;在这里我们需要了解一下
第四步&#xff1a;重写OnDraw&#xff08;&#xff09;方法
&#64;Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (mOnDrawListener !&#61; null) {mOnDrawListener.ondrawIndicator();}mUnComplectedPaint.setColor(mUnCompletedLineColor);mComplectedPaint.setColor(mCompletedLineColor);//首先绘制线for (int i &#61; 0; i
这个方法使我们最核心的&#xff0c;但是我们上面已经把难点的地方都抽出来单个解决了&#xff0c;所以onDraw&#xff08;&#xff09;方法里面还是很好理解的&#xff0c;大体思路是根据我们onSizeChange&#xff08;&#xff09;方法中记录的坐标来绘制对应的view&#xff0c;先是绘制线&#xff0c;再是绘制Rect&#xff0c;这里要注意下面这段代码&#xff0c;在做的时候我们发现即使你的坐标是正确的两个步骤之前的连线也会向左偏移一些&#xff0c;所以这里我们试了一下 &#xff0c;向右偏移10px就差不多了&#xff0c;这个大家可以自己去试试
canvas.drawRect(preComplectedXPosition &#43; mCircleRadius - 10, mLeftY, afterComplectedXPosition - mCircleRadius &#43; 10, mRightY, mComplectedPaint);
这就是这个类的核心了&#xff0c;接下来我们来使用两组测试数据来试试&#xff0c;添加以下代码和以下方法
在init&#xff08;&#xff09;方法中添加以下代码//添加测试数据List
StepBean.java&#xff08;用实体类将具体的每一步的信息给抽象化&#xff09;
package com.qianmo.mystepview.bean;/*** Created by Administrator on 2017/3/16 0016.* E-Mail&#xff1a;543441727&#64;qq.com*/public class StepBean {public static final int STEP_UNDO &#61; -1;//未完成 undo steppublic static final int STEP_CURRENT &#61; 0;//正在进行 current steppublic static final int STEP_COMPLETED &#61; 1;//已完成 completed stepprivate String name;private int state;public String getName() {return name;}public void setName(String name) {this.name &#61; name;}public int getState() {return state;}public void setState(int state) {this.state &#61; state;}public StepBean() {}public StepBean(String name, int state) {this.name &#61; name;this.state &#61; state;}
}
看一下我们绘制的效果&#xff0c;这就把我们上面的图标给绘制完成了&#xff0c;我们的自定义view就基本上完成了一大半了&#xff01;
再看一下我们StepViewIndicator类的所有代码
package com.qianmo.mystepview.view;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;import com.qianmo.mystepview.R;
import com.qianmo.mystepview.bean.StepBean;import java.util.ArrayList;
import java.util.List;/*** Created by wangjitao on 2017/3/16 0016.* E-Mail&#xff1a;543441727&#64;qq.com* 横向指示器的创建*/public class StepViewIndicator extends View {//定义默认的高度private int defaultStepIndicatorNum &#61; (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());//直线private Path mPath;//虚线绘制函数private DashPathEffect mEffect;//完成的下标集合private List
// mOnDrawListener.ondrawIndicator();
// Log.i("wangjitao", "onSizeChanged");
// }}/*** 绘制view** &#64;param canvas*/&#64;Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (mOnDrawListener !&#61; null) {mOnDrawListener.ondrawIndicator();Log.i("wangjitao", "onDraw");}mUnComplectedPaint.setColor(mUnCompletedLineColor);mComplectedPaint.setColor(mCompletedLineColor);//首先绘制线for (int i &#61; 0; i
}
- 创建StepView类&#xff0c;继承自LinearLayout&#xff0c;用于将我们上面的创建的StepViewIndicator和包含所有文字的LinearLayout合并在一起&#xff0c;其布局文件如下
具体的代码如下
package com.qianmo.mystepview.view;import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;import com.qianmo.mystepview.R;
import com.qianmo.mystepview.bean.StepBean;import java.util.List;/*** 日期&#xff1a;16/6/22 15:47* * 描述&#xff1a;StepView*/
public class StepView extends LinearLayout implements StepViewIndicator.OnDrawIndicatorListener
{private RelativeLayout mTextContainer;private StepViewIndicator mStepsViewIndicator;private List
但是我们关注ondrawIndicator&#xff08;&#xff09;这个方法中的逻辑&#xff0c;主要是通过OnDrawIndicatorListener这个类的回调来绘制对应的文字描述&#xff0c;再看看在Activity中只的使用
StepView setpview &#61; (StepView) findViewById(R.id.step_view0);List
很简单有没有&#xff0c;看着一次我们自己写的效果有没有之前作者那种文字重叠的问题 如下图&#xff1a;
很明显&#xff0c;没有&#xff0c;因为我在StepViewIndicator类中将的mLinePadding属性的值修改大了
修改之后
mLinePadding &#61; 1.5f * defaultStepIndicatorNum;
修改之钱
mLinePadding &#61; 0.85f * defaultStepIndicatorNum;
这里其实可以提供一个set方法来设置动态的设置一下实线和虚线的距离的&#xff0c;好了&#xff0c;这样我们就是实现了我们的需求了&#xff0c;再次感谢StepView的作者&#xff0c;如果有需要源码的同学&#xff0c;可以去下一下&#xff0c;Github下载地址&#xff0c;See You Next Time &#xff01;&#xff01;&#xff01;