Android-自定义View的流程和步骤

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);
        }
    });
}

你可能感兴趣的:(Android-自定义View的流程和步骤)