1.效果图
2.实现思路
- 首先是画各步骤点之间的线条
- 接着是画未选步骤点的图标
- 第三步是画选中步骤点的图标
- 最后画出各步骤点对应的说明文字
3.实现细节
3.1概述
StepView继承自View,通过构造方法初始化一些必要参数,然后在OnsizeChanged方法中获取View的宽高以及其他额外计算的数据信息,最后通过onDraw方法绘制出View。
3.2首先通过res/values/attrs定义一些细节参数
3.3通过构造方法初始化
public StepView(Context context){
this(context,null)
}
public StepView(Context context, @Nullable AttributeSet attrs){
this(context,attrs,0)
}
public Step(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
super(context,attrs,defStyleAttr);
init(context,attrs,defStyleAttr);
}
private void init(Context context,AttributeSet attrs, int defStyleAttr){
defaultNormalLineColor = Color.ParseColor("#545454")
defaultPassLineColor = Color.WHITE;
defaultTextColor = Color.WHITE;
defaultLineStikeWidth = dp2px(context,1);
defaultTextSize = sp2px(context,80);
defaultText2DotMargin = dp2px(context,15);
defaultText2BottomMargin = dp2px(context,20);
normal_pic = BitmapFactory.decodeResource(getResources(),R.drawable.ic_normal);
target_pic = BitmapFactory.decodeResource(getResources(),R.drawable.ic_target);
passed_pic = BitmapFactory.decodeResource(getResources(),R.drawable.icpassed);
TypeArray a = context.obtainStyledAttributes(attrs,R.styleable.StepView,defStyleAttr,0);
dotCount = a.getInt(R.styleable.StepView_count,defaultDotCount);
if(dotCount < 2){
throw new IllegalArgumentException("Step can't be less than 2");
}
stepNum = a.getInt(R.styleable.StepView_step,defaultStepNum);
lineLength = a.getInt(R.styleable.StepView_line_length,defaultLineLength);
maxDotCount = a.getInt(R.styleable.StepView_max_dot_count,defaultMaxDotCount);
if(maxDotCount
由这段代码可知,通过init方法,不但初始化了上面的细节参数,还额外初始化了bitmap、paint、bounds等参数。由于调用了dp/sp2px方法,所以先贴一下该方法。
private int dp2px(Contextcontext,intvalue{
floatdensity=context.getResources().getDisplayMetrics().density;
return(int)(density*value+0.5f);
}
private int sp2px(Contextcontext,intvalue{
floatscaledDensity=context.getResources().getDisplayMetrics().scaledDensity;
return(int)(value/scaledDensity+0.5f);
}
3.4在onSizeChanged方法中,完成宽高等数据计算。
@Override
protected void onSizeChanged(int w,int h,int oldw,int oldh){
width = w -margin*2;
height = h;
//线条长度可否改变
if(lineLength == defaultLinelength){//可变
perLineLength = width / (dotCount - 1);
}
else{//固定
perLineLength = width / (maxCount - 1);
}
passWH = calculateWidthAndHeight(passed_pic);
normalWH = caculateWidthAndHeight(normal_pic);
targetWh = cacultateWidthAndHeight(target_pic)
}
此处说明一下,计算Bitmap宽高的方法,然后吧宽高信息存于二维数组中
/*计算Bitmap宽高*/
private int[] calculateWidthAndHeight(Bitmap bitmap){
int[] wh = new int[2];
int width = bitmap.getWidth();
int height = bitmap.getHeight();
wh[0] = width;
wh[1] = height;
return wh;
}
3.5onDraw方法绘制View
3.5.1画步聚点之间连线
/*绘制链接步骤点之间的线条*/
private void drawConnectLIne(Canvas canvas, int stepNum){
float startX = 0;
float stopX = 0;
for(int i = 0;istepNum){
startX = margin +perLineLength * i +passWH[0] / 2;
}
/*设置线条终点X轴坐标*/
if(i + 1 == stepNum){
stopX = margin + perLineLength * (i +1) -targetWH[0] / 2;
}else if(i+1 i){
linePaint.setColor(passLineColor);
}else{
linePaint.setColor(normalColor);
}
if(isTextBelowLine){
/*当文字在线条下方时,设置线条Y轴的位置并绘制*/
canvas.drawLine(startX,line2TopMargin,stopX,line2TopMargin,linePaint);
}
else{
canvas.drawLine(startX,height - line2BottomMargin,stopX,height-line2BottomMargin,linePaint);
}
}
}
3.5.2画普通步骤点
/*绘制一般情况下的步骤点图片*/
private void drawNormalSquar(Canvas canvas,int stepNum){
for(int i = 0;i < dotCount; i++){
/*在目标点状态时,普通图片不绘制,跳过,继续下一次循环*/
if(stepNum == i){
continue;
}
if(stepNum == i){
float left margin +perLineLength * i -passWH[0] /2;
float top = 0;
if(isTextBelowLine){
top = line2TopMargin -passWH[0] / 2;
}
else{
top = height - line2BottomMargin - passWH[1] /2;
}
canvas.drawBitmap(passed_pic,left,top,null);
}else{
float left = margin + perLineLength * i -normalWH[0] / 2;
float top = 0;
if(isTextBelowLine){
top = line2TopMargin - normalWH[1] / 2;
}else{
top = heght -line2BottomMargin - normalWH[1] / 2;
}
canvas.drwaBitmap(normal_pic,left,top,null);
}
}
}
3.5.3画目标步骤点
/*画目标步骤点图片*/
private void drawTargetSquar(Canvas canvas,int i){
float left = margin + perLineLength * i -targetWH[0] / 2;
float top = 0;
if(isTextBelowLine){
top = line2TopMargin - targetWH[1] / 2;
}else{
top = height -line2BottomMargin - targetWH[1] / 2;
}
canvas.drawBitmap(target_pic,left,top,null);
}
3.5.4画步骤点说明文字
/*绘制各步骤说明文字*/
private void drawDescText(Canvas canvas){
for(int i = 0;i < dotCount;i++){
String text = texts[i];
textPaint.getTextBounds(text,0,text.length(),bounds);
int textWidth = bounds.width();
int textHeight = bounds.height();
float x = margin +perLineLength * i - textWidth /2;
float y;
if(isTextBelowLine){
y = height -text2BottomMargin;
}else{
y = text2TopMargin + textHeight;
}
canvas.drawText(text,x,y,textPaint);
}
}
通过上面几个步骤就完成StepView的绘制了。
3.6根据用户设置的是否可点击,给StepView添加点击监听
这里先说明一下思路:当用户点击时,View通过touch事件监听用户点击的x/y值,然后转换为point,再通过判断point是否在几个步骤点区域范围内,返回对应的步骤点值,然后重新绘制View。
3.6.1下面onTouchEvent方法:
@Override
public boolean onTouchEvent(MotionEvent event){
if(clickable){
switch(event.getAction()){
case MotionEvent.ActionDown:
Point point = new Point():
point.x = (int) event.getX();
point.y = (int) event.getY();
int stepInDots = getStepInDots(point);
if(stepInDots ! = -1){
stepNum = stepInDots;
invalidate();
}
break;
defalut:
break;
}
}
return super.onTouchEvent(event);
}
3.6.2获取用户点击点在某个步骤点值:
/*获取手指触摸点为第几个步骤点,异常时返回-1*/
private int getStepInDots(Point point){
for(int i = 0;i < dotCount; i++){
Rect rect = getStepSquarRects()[i];
int x = point.x;
int y = point.y;
if(rect.contains(x,y)){
return i;
}
}
return -1;
}
3.6.3获取各步骤点矩阵所在区域:
/*获取所有步骤点的矩阵区域*/
private Rect[] getStepSquarRects(){
Rect[] rects = new Rect[dotCount];
int left,top,right,bottom;
for(int i = 0;i < dotCount; i++){
/*此处默认所有点的区域范围为被选中图片的区域范围*/
Rect rect = new Rect();
left = margin + perLineLength * i -targetWH[0] / 2;
right = margin + perLineLength * i _targetWH[0] / 2;
if(isTextBelowLine){
top = (int) (line2TopMargin - targetWH[1] /2);
bottom = (int) (line2TopMargin + targetWH[1] / 2);
}else{
top = (int) (height - line2BottomMargin -targetWH[1] / 2);
bottom = (int) (height-line2BottomMargin + targetWH[1] / 2);
}
rect.set(left,top,right,bottom);
rects[i] = rect;
}
return rects;
}
至此,StepView 的点击事件也完成了。
3.7设置外部调用接口
/*给外部调用接口,设置步骤总数*/
public void setDotCount(int count){
if(count < 2){
throw new IllgalArgumentException("dot count can't be less than 2.") ;
}
dotCount = count;
}
/*给外部调用接口,设置说明文字信息*/
public void setDescription(String[] descs){
if(descs == null || descs.length < dotCount){
throw new IllegalArgumentException("Descriptions can't be null or its length must more than dot count");
}
texts = descs;
}
/*给外部调用接口,设置该view是否可点击*/
public void setClickable(boolean clickable){
this.clickable = clickable;
}
/*给外部调用接口,设置步骤*/
public void setStep(Step step){
switch(step){
case ONE:
stepNum = 0;
break;
case TWO:
stepNum =1 ;
break;
case THREE:
stepNum = 2;
break;
case FOUR:
stepNum = 3;
break;
case stepNum = 4;
break;
default:
break;
}
invalidate();
}
/*此处默认最多为5个步骤*/
public enum Step{
ONE,TWO,THREE,FOUR,FIVE
}
4.部分细节详解
- 详解1
画线条时,由于目标步骤点比普通的大,因此在计算线条长度时应计算目标步骤点两端线条长度,避免线条长度画错,影响美观。
/*设置线条起点X轴坐标*/
if(i == stepNum){
startX = margin + perLineLength * i + targetWH[0] / 2;
}else if(i >stepNum){
startX = margin =perLineLength * i +normalWH[0] / 2;
}else {
startX = margin + perLineLength * i +passWH[0] / 2;
}
/*设置线条终点X轴坐标*/
if(i + 1 == stepNum){
stopX = margin + perLineLength * (i + 1) -targetWH[0] / 2;
}else if(i+1
- 详解2
线条长度是否可变(见gitview1/view2/view3/view4/view5),当设置线条长度固定时,线条的长度由view_width/(max_dot-1)决定;当设置线条长度不固定时(view6),由图可知,view6的长度与view5完整的长度一致。
- 详解3
文字是否在线条下面,默认是。当文字在线条上面的时候,这里采取数据导致设置,即把设置给view之前的线条距顶部、文字距底部的距离分别设置给了线条距底部、文字距顶部的距离。见如下代码:
//当文字在线条上面时,参数倒置
if(!isTextBelowLine){
line2BottomMargin = line2TopMargin;
text2TopMargin = text2BottomMargin;
}
- 详解4
获取个步骤点的矩形区域,首先是分别对各步骤点区域的左上右下进行计算,然后设置给各步骤点矩形。
/*获取所有步骤点的矩阵区域*/
private Rect[] getStepSquarRects(){
Rect[] rects = new Rect[dotCount];
int left,top,right,bottom;
for(int i = 0;i < dotCount; i++){
/*此处默认所有点的区域范围被选中图片的区域范围*/
Rect rect = new Rect();
left = margin + perLineLength * i - targetWH[0] / 2;
right = margin + perLineLength * i + targetWH[0]/2;
if(isTextBelowLine){
top = (int)(line2TopMargin -targetWH[1] / 2);
bottom = (int) (line2TopMargin + targetWH[1] /2);
}else{
top = (int) (height - line2BottomMargin - targetWH[1] / 2);
bottom=(int)(height-line2BottomMargin+targetWH[1]/2);
}
rect.set(left,top,right,bottom);
rects[i]=rect;
}
return rects;
}
5.调用
- xml调用
- 代码调用
private StepView step1,step2,step3,step4,step5,step6;
private CheckBox click1,click2,click3,click4,click5,click6;
private String[] texts={"确认身份信息","确认入住信息","选择房型","支付押金","完成入住"};
@Override
protected void onCreate(BundlesavedInstanceState{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
step1=(StepView)findViewById(R.id.step1);
step2=(StepView)findViewById(R.id.step2);
step3=(StepView)findViewById(R.id.step3);
step4=(StepView)findViewById(R.id.step4);
step5=(StepView)findViewById(R.id.step5);
step6=(StepView)findViewById(R.id.step6);
click1=(CheckBox)findViewById(R.id.click1);
click2=(CheckBox)findViewById(R.id.click2);
click3=(CheckBox)findViewById(R.id.click3);
click4=(CheckBox)findViewById(R.id.click4);
click5=(CheckBox)findViewById(R.id.click5);
click6=(CheckBox)findViewById(R.id.click6);
step1.setDescription(texts);
step2.setDescription(texts);
step3.setDescription(texts);
step4.setDescription(texts);
step5.setDescription(texts);
step6.setDescription(texts);
step1.setStep(StepView.Step.ONE);
step2.setStep(StepView.Step.TWO);
step3.setStep(StepView.Step.THREE);
step4.setStep(StepView.Step.FOUR);
step5.setStep(StepView.Step.FIVE);
step6.setStep(StepView.Step.FOUR);
checkChaged(click1,step1);
checkChaged(click2,step2);
checkChaged(click3,step3);
checkChaged(click4,step4);
checkChaged(click5,step5);
checkChaged(click6,step6);
}
private void checkChaged(CheckBoxcheck,finalStepViewstep{
check.setOnCheckedChangeListener(newCompoundButton.OnCheckedChangeListener(){
@Override public void onCheckedChanged(CompoundButtonbuttonView,booleanisChecked){
step.setClickable(isChecked);
}
});
}