Android -- 自定义StepView实现个人信息验证进度条

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 < num; i++)
  {
      //先计算全部最左边的padding值(getWidth()-(圆形直径+两圆之间距离)*2)
      float paddingLeft = (getWidth() - num * mCircleRadius * 2 - (num - 1) * mLinePadding) / 2;
      mComplectedXPosition.add(paddingLeft + mCircleRadius + i * mCircleRadius * 2 + i * mLinePadding);
  }

  第一步:到了这里我们基本上把难点都解决了,现在我们创建一个StepViewIndicator,继承View,首先重写三个构造函数,再在创建构造函数的同时初始化类似于画笔、数组之类的属性,关键代码如下:

//下面这是重写构造函数 
public StepViewIndicator(Context context) {
        this(context, null);
    }

    public StepViewIndicator(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public StepViewIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

//这里是初始化属性


    /**
     * 初始化常见属性
     */
    private void init() {
        mStepBeanList = new ArrayList<>();
        mPath = new Path();
        mEffect = new DashPathEffect(new float[]{8, 8, 8, 8}, 1);

        //设置已完成的初始化基本设置
        mComplectedXPosition = new ArrayList();
        mComplectedPaint = new Paint();
        mComplectedPaint.setAntiAlias(true);
        mComplectedPaint.setColor(mCompletedLineColor);
        mComplectedPaint.setStyle(Paint.Style.FILL);
        mComplectedPaint.setStrokeWidth(2);

        //设置未完成的初始化基本设置
        mUnComplectedPaint = new Paint();
        mUnComplectedPaint.setAntiAlias(true);
        mUnComplectedPaint.setColor(mUnCompletedLineColor);
        mUnComplectedPaint.setStyle(Paint.Style.STROKE);
        mUnComplectedPaint.setStrokeWidth(2);
        mUnComplectedPaint.setPathEffect(mEffect);

        //圆的半径、线的长度、线的高度基本值设置
        mCompletedLineHeight = 0.05f * defaultStepIndicatorNum;
        mCircleRadius = 0.28f * defaultStepIndicatorNum;
        mLinePadding = 1.5f * defaultStepIndicatorNum;

        //初始化三种状态下的图片
        mCompleteIcon = ContextCompat.getDrawable(getContext(), R.drawable.complted);
        mAttentionIcon = ContextCompat.getDrawable(getContext(), R.drawable.attention);
        mDefaultIcon = ContextCompat.getDrawable(getContext(), R.drawable.default_icon);

    }

  再来看看我们这个view中要用到的所有属性,代码里面都注释好了所有属性的含义了,我就不再给大家一一解释了

    //定义默认的高度
    private int defaultStepIndicatorNum = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());
    //直线
    private Path mPath;
    //虚线绘制函数
    private DashPathEffect mEffect;
    //完成的下标集合
    private List<Float> mComplectedXPosition;

    //已完成的部分绘制对象
    private Paint mComplectedPaint;
    //定义默认完成线的颜色  ;
    private int mCompletedLineColor = Color.WHITE;
    //已完成线的高度
    private float mCompletedLineHeight;

    //未完成的部分绘制对象
    private Paint mUnComplectedPaint;
    //定义默认未完成线的颜色  ;
    private int mUnCompletedLineColor = Color.WHITE;

    //定义圆的半径
    private float mCircleRadius;
    //定义线的长度
    private float mLinePadding;

    //定义三种状态下的图片(已完成的图片,正在进行的图片,未完成的图片)
    Drawable mCompleteIcon;
    Drawable mAttentionIcon;
    Drawable mDefaultIcon;

    //动态计算view位置
    private float mCenterY;
    private float mLeftY;
    private float mRightY;

    //总共有多少步骤
    private int mStepNum = 0;
    private List<StepBean> mStepBeanList;
    //正在进行的位置
    private int mComplectingPosition;

    //添加对view的监听
    private OnDrawIndicatorListener mOnDrawListener;

  第二步:重写OnMeasuer()方法,这里只是丈量了一下控件的宽高

 /**
     * 测量自身的高度
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = defaultStepIndicatorNum * 2;
        if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
            width = MeasureSpec.getSize(widthMeasureSpec);
        }
        int height = defaultStepIndicatorNum;
        if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
            height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
        }
        setMeasuredDimension(width, height);
    }

  这里要注意一下MeasureSpec的三种测量模式,具体解释如下(这点自定义view经常用到,在这里还是给大家普及一下):

mode共有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。

MeasureSpec.EXACTLY:精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。

MeasureSpec.AT_MOST:最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。

MeasureSpec.UNSPECIFIED:未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式,例如在ScrollView里面嵌套ListView,里面的listview的高度不确定,这时候要重写listview,将我们的布局撑到最大,代码如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(1000>> 2,
        MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }
1000的二进制:1111101000
右移2位后:11111010,十进制为:250
这样就指定了listview的高度为250px以内的最大允许值(一般就是250)。

  第三步:重写OnSizeChange()方法

    /**
     * View大小改变
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //下面这三句代码只是为了绘制矩形的线
        mCenterY = 0.5f * getHeight();
        //获取左上方Y的位置,获取该点的意义是为了方便画矩形左上的Y位置
        mLeftY = mCenterY - (mCompletedLineHeight / 2);
        //获取右下方Y的位置,获取该点的意义是为了方便画矩形右下的Y位置
        mRightY = mCenterY + mCompletedLineHeight / 2;

        mComplectedXPosition.clear();
        //计算所有总空间离最左边的距离,并记录所有的圆心x轴的坐标
        for (int i = 0; i < mStepNum; i++) {
            //计算全部最左边
            float paddingLeft = (getWidth() - mStepNum * mCircleRadius * 2 - (mStepNum - 1) * mLinePadding) / 2;
            //将所有的圆心X轴坐标记录到集合中
            mComplectedXPosition.add(paddingLeft + mCircleRadius + i * mCircleRadius * 2 + i * mLinePadding);
        }
    }

  在这里就使用到了我们上面的公式了,而且再记录我们的矩形线段的起始和结束的Y坐标,还有OnSizeChange()这个方法是在View的大小发生改变的时候进行调用,在这里我们需要了解一下

  第四步:重写OnDraw()方法

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mOnDrawListener != null) {
            mOnDrawListener.ondrawIndicator();
        }
        mUnComplectedPaint.setColor(mUnCompletedLineColor);
        mComplectedPaint.setColor(mCompletedLineColor);

        //首先绘制线
        for (int i = 0; i < mComplectedXPosition.size() - 1; i++) {
            //前一个ComplectedXPosition
            final float preComplectedXPosition = mComplectedXPosition.get(i);
            //后一个ComplectedXPosition
            final float afterComplectedXPosition = mComplectedXPosition.get(i + 1);
            if (i < mComplectingPosition) {
                //判断在完成之前的所有的点画完成的线,这里是矩形(这里会有10像素的偏移,没懂)
                canvas.drawRect(preComplectedXPosition + mCircleRadius - 10, mLeftY, afterComplectedXPosition - mCircleRadius + 10, mRightY, mComplectedPaint);
            } else {
                mPath.moveTo(preComplectedXPosition + mCircleRadius, mCenterY);
                mPath.lineTo(afterComplectedXPosition - mCircleRadius, mCenterY);
                canvas.drawPath(mPath, mUnComplectedPaint);
            }
        }

        //再绘制图标
        for (int i = 0; i < mComplectedXPosition.size(); i++) {
            final float currentComplectedXPosition = mComplectedXPosition.get(i);
            //创建矩形
            Rect rect = new Rect((int) (currentComplectedXPosition - mCircleRadius), (int) (mCenterY - mCircleRadius), (int) (currentComplectedXPosition + mCircleRadius), (int) (mCenterY + mCircleRadius));
            if (i < mComplectingPosition) {
                mCompleteIcon.setBounds(rect);
                mCompleteIcon.draw(canvas);
            } else if (i == mComplectingPosition && mComplectedXPosition.size() != 1) {
                canvas.drawCircle(currentComplectedXPosition, mCenterY, mCircleRadius * 1.1f, mComplectedPaint);
                mAttentionIcon.setBounds(rect);
                mAttentionIcon.draw(canvas);
            } else {
                mDefaultIcon.setBounds(rect);
                mDefaultIcon.draw(canvas);
            }
        }

    }

  这个方法使我们最核心的,但是我们上面已经把难点的地方都抽出来单个解决了,所以onDraw()方法里面还是很好理解的,大体思路是根据我们onSizeChange()方法中记录的坐标来绘制对应的view,先是绘制线,再是绘制Rect,这里要注意下面这段代码,在做的时候我们发现即使你的坐标是正确的两个步骤之前的连线也会向左偏移一些,所以这里我们试了一下 ,向右偏移10px就差不多了,这个大家可以自己去试试

canvas.drawRect(preComplectedXPosition + mCircleRadius - 10, mLeftY, afterComplectedXPosition - mCircleRadius + 10, mRightY, mComplectedPaint);

  这就是这个类的核心了,接下来我们来使用两组测试数据来试试,添加以下代码和以下方法

在init()方法中添加以下代码

        //添加测试数据
        List<StepBean> datas = new ArrayList<>();
        datas.add(new StepBean("接单", StepBean.STEP_COMPLETED));
        datas.add(new StepBean("打包", StepBean.STEP_CURRENT));
        datas.add(new StepBean("出发", StepBean.STEP_UNDO));
        setStepNum(datas);

//这是setStepNuw方法具体代码

/**
     * 设置步骤的基本流程
     */
    public void setStepNum(List<StepBean> stepsBeanList) {
        this.mStepBeanList = stepsBeanList;
        mStepNum = mStepBeanList.size();

        if (mStepBeanList != null && mStepBeanList.size() > 0) {
            for (int i = 0; i < mStepNum; i++) {
                StepBean stepsBean = mStepBeanList.get(i);
                {
                    if (stepsBean.getState() == StepBean.STEP_CURRENT) {
                        mComplectingPosition = i;
                    }
                }
            }
        }
        requestLayout();
    }

  StepBean.java(用实体类将具体的每一步的信息给抽象化)  

package com.qianmo.mystepview.bean;

/**
 * Created by Administrator on 2017/3/16 0016.
 * E-Mail:543441727@qq.com
 */

public class StepBean {
    public static final int STEP_UNDO = -1;//未完成  undo step
    public static final int STEP_CURRENT = 0;//正在进行 current step
    public static final int STEP_COMPLETED = 1;//已完成 completed step

    private String name;
    private int state;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public StepBean() {
    }

    public StepBean(String name, int state) {
        this.name = name;
        this.state = state;
    }
}

  看一下我们绘制的效果,这就把我们上面的图标给绘制完成了,我们的自定义view就基本上完成了一大半了!

  再看一下我们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:543441727@qq.com
 * 横向指示器的创建
 */

public class StepViewIndicator extends View {
    //定义默认的高度
    private int defaultStepIndicatorNum = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());


    //直线
    private Path mPath;
    //虚线绘制函数
    private DashPathEffect mEffect;
    //完成的下标集合
    private List<Float> mComplectedXPosition;

    //已完成的部分绘制对象
    private Paint mComplectedPaint;
    //定义默认完成线的颜色  ;
    private int mCompletedLineColor = Color.WHITE;
    //已完成线的高度
    private float mCompletedLineHeight;

    //未完成的部分绘制对象
    private Paint mUnComplectedPaint;
    //定义默认未完成线的颜色  ;
    private int mUnCompletedLineColor = Color.WHITE;

    //定义圆的半径
    private float mCircleRadius;
    //定义线的长度
    private float mLinePadding;

    //定义三种状态下的图片(已完成的图片,正在进行的图片,未完成的图片)
    Drawable mCompleteIcon;
    Drawable mAttentionIcon;
    Drawable mDefaultIcon;

    //动态计算view位置
    private float mCenterY;
    private float mLeftY;
    private float mRightY;

    //总共有多少步骤
    private int mStepNum = 0;
    private List<StepBean> mStepBeanList;
    //正在进行的位置
    private int mComplectingPosition;

    //添加对view的监听
    private OnDrawIndicatorListener mOnDrawListener;


    private int screenWidth;//this screen width

    public StepViewIndicator(Context context) {
        this(context, null);
    }

    public StepViewIndicator(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public StepViewIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化常见属性
     */
    private void init() {
        mStepBeanList = new ArrayList<>();
        mPath = new Path();
        mEffect = new DashPathEffect(new float[]{8, 8, 8, 8}, 1);

        //设置已完成的初始化基本设置
        mComplectedXPosition = new ArrayList();
        mComplectedPaint = new Paint();
        mComplectedPaint.setAntiAlias(true);
        mComplectedPaint.setColor(mCompletedLineColor);
        mComplectedPaint.setStyle(Paint.Style.FILL);
        mComplectedPaint.setStrokeWidth(2);

        //设置未完成的初始化基本设置
        mUnComplectedPaint = new Paint();
        mUnComplectedPaint.setAntiAlias(true);
        mUnComplectedPaint.setColor(mUnCompletedLineColor);
        mUnComplectedPaint.setStyle(Paint.Style.STROKE);
        mUnComplectedPaint.setStrokeWidth(2);
        mUnComplectedPaint.setPathEffect(mEffect);

        //圆的半径、线的长度、线的高度基本值设置
        mCompletedLineHeight = 0.05f * defaultStepIndicatorNum;
        mCircleRadius = 0.28f * defaultStepIndicatorNum;
        mLinePadding = 1.5f * defaultStepIndicatorNum;

        //初始化三种状态下的图片
        mCompleteIcon = ContextCompat.getDrawable(getContext(), R.drawable.complted);
        mAttentionIcon = ContextCompat.getDrawable(getContext(), R.drawable.attention);
        mDefaultIcon = ContextCompat.getDrawable(getContext(), R.drawable.default_icon);


        //添加测试数据
        //List<StepBean> datas = new ArrayList<>();
        //datas.add(new StepBean("接单", StepBean.STEP_COMPLETED));
        //datas.add(new StepBean("打包", StepBean.STEP_CURRENT));
        //datas.add(new StepBean("出发", StepBean.STEP_UNDO));
        //setStepNum(datas);
    }

    /**
     * 测量自身的高度
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = defaultStepIndicatorNum * 2;
        if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
            width = MeasureSpec.getSize(widthMeasureSpec);
        }
        int height = defaultStepIndicatorNum;
        if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
            height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
        }
        setMeasuredDimension(width, height);
    }



    /**
     * View大小改变
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //下面这三句代码只是为了绘制矩形的线
        mCenterY = 0.5f * getHeight();
        //获取左上方Y的位置,获取该点的意义是为了方便画矩形左上的Y位置
        mLeftY = mCenterY - (mCompletedLineHeight / 2);
        //获取右下方Y的位置,获取该点的意义是为了方便画矩形右下的Y位置
        mRightY = mCenterY + mCompletedLineHeight / 2;

        mComplectedXPosition.clear();
        //计算所有总空间离最左边的距离,并记录所有的圆心x轴的坐标
        for (int i = 0; i < mStepNum; i++) {
            //计算全部最左边
            float paddingLeft = (getWidth() - mStepNum * mCircleRadius * 2 - (mStepNum - 1) * mLinePadding) / 2;
            //将所有的圆心X轴坐标记录到集合中
            mComplectedXPosition.add(paddingLeft + mCircleRadius + i * mCircleRadius * 2 + i * mLinePadding);
        }
        Log.i("wangjitao", "screenWidth:" + screenWidth + ",geiwidth:" + getWidth());

        //当位置发生改变时的回调监听
//        if (mOnDrawListener != null) {
//            mOnDrawListener.ondrawIndicator();
//            Log.i("wangjitao", "onSizeChanged");
//        }

    }

    /**
     * 绘制view
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mOnDrawListener != null) {
            mOnDrawListener.ondrawIndicator();
            Log.i("wangjitao", "onDraw");
        }
        mUnComplectedPaint.setColor(mUnCompletedLineColor);
        mComplectedPaint.setColor(mCompletedLineColor);

        //首先绘制线
        for (int i = 0; i < mComplectedXPosition.size() - 1; i++) {
            //前一个ComplectedXPosition
            final float preComplectedXPosition = mComplectedXPosition.get(i);
            //后一个ComplectedXPosition
            final float afterComplectedXPosition = mComplectedXPosition.get(i + 1);
            if (i < mComplectingPosition) {
                //判断在完成之前的所有的点画完成的线,这里是矩形(这里会有10像素的偏移,没懂)
                canvas.drawRect(preComplectedXPosition + mCircleRadius - 10, mLeftY, afterComplectedXPosition - mCircleRadius + 10, mRightY, mComplectedPaint);
            } else {
                mPath.moveTo(preComplectedXPosition + mCircleRadius, mCenterY);
                mPath.lineTo(afterComplectedXPosition - mCircleRadius, mCenterY);
                canvas.drawPath(mPath, mUnComplectedPaint);
            }
        }

        //再绘制图标
        for (int i = 0; i < mComplectedXPosition.size(); i++) {
            final float currentComplectedXPosition = mComplectedXPosition.get(i);
            //创建矩形
            Rect rect = new Rect((int) (currentComplectedXPosition - mCircleRadius), (int) (mCenterY - mCircleRadius), (int) (currentComplectedXPosition + mCircleRadius), (int) (mCenterY + mCircleRadius));
            if (i < mComplectingPosition) {
                mCompleteIcon.setBounds(rect);
                mCompleteIcon.draw(canvas);
            } else if (i == mComplectingPosition && mComplectedXPosition.size() != 1) {
                canvas.drawCircle(currentComplectedXPosition, mCenterY, mCircleRadius * 1.1f, mComplectedPaint);
                mAttentionIcon.setBounds(rect);
                mAttentionIcon.draw(canvas);
            } else {
                mDefaultIcon.setBounds(rect);
                mDefaultIcon.draw(canvas);
            }
        }

    }

    /**
     * 设置步骤的基本流程
     */
    public void setStepNum(List<StepBean> stepsBeanList) {
        this.mStepBeanList = stepsBeanList;
        mStepNum = mStepBeanList.size();

        if (mStepBeanList != null && mStepBeanList.size() > 0) {
            for (int i = 0; i < mStepNum; i++) {
                StepBean stepsBean = mStepBeanList.get(i);
                {
                    if (stepsBean.getState() == StepBean.STEP_CURRENT) {
                        mComplectingPosition = i;
                    }
                }
            }
        }
        requestLayout();
    }

    /**
     * 设置监听
     *
     * @param onDrawListener
     */
    public void setOnDrawListener(OnDrawIndicatorListener onDrawListener) {
        mOnDrawListener = onDrawListener;
    }

    /**
     * 设置对view监听
     */
    public interface OnDrawIndicatorListener {
        void ondrawIndicator();
    }

    /**
     * 得到所有圆点所在的位置
     *
     * @return
     */
    public List<Float> getComplectedXPosition() {
        return mComplectedXPosition;
    }


    /**
     * 设置正在进行position
     *
     * @param complectingPosition
     */
    public void setComplectingPosition(int complectingPosition) {
        this.mComplectingPosition = complectingPosition;
        invalidate();
    }

    /**
     * 设置正在进行position
     */
    public int getComplectingPosition() {
        return this.mComplectingPosition;
    }

    /**
     * 设置流程步数
     *
     * @param stepNum 流程步数
     */
    public void setStepNum(int stepNum) {
        this.mStepNum = stepNum;
        invalidate();
    }

    /**
     * 设置未完成线的颜色
     *
     * @param unCompletedLineColor
     */
    public void setUnCompletedLineColor(int unCompletedLineColor) {
        this.mUnCompletedLineColor = unCompletedLineColor;
    }

    /**
     * 设置已完成线的颜色
     *
     * @param completedLineColor
     */
    public void setCompletedLineColor(int completedLineColor) {
        this.mCompletedLineColor = completedLineColor;
    }

    /**
     * 设置默认图片
     *
     * @param defaultIcon
     */
    public void setDefaultIcon(Drawable defaultIcon) {
        this.mDefaultIcon = defaultIcon;
    }

    /**
     * 设置已完成图片
     *
     * @param completeIcon
     */
    public void setCompleteIcon(Drawable completeIcon) {
        this.mCompleteIcon = completeIcon;
    }

    /**
     * 设置正在进行中的图片
     *
     * @param attentionIcon
     */
    public void setAttentionIcon(Drawable attentionIcon) {
        this.mAttentionIcon = attentionIcon;
    }
}
  • 创建StepView类,继承自LinearLayout,用于将我们上面的创建的StepViewIndicator和包含所有文字的LinearLayout合并在一起,其布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <com.qianmo.mystepview.view.StepViewIndicator
        android:id="@+id/steps_indicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"/>

    <RelativeLayout
        android:id="@+id/rl_text_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />
</LinearLayout>

  具体的代码如下

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;

/**
 * 日期:16/6/22 15:47
 * <p/>
 * 描述:StepView
 */
public class StepView extends LinearLayout implements StepViewIndicator.OnDrawIndicatorListener
{
    private RelativeLayout mTextContainer;
    private StepViewIndicator mStepsViewIndicator;
    private List<StepBean> mStepBeanList;
    private int mComplectingPosition;
    private int mUnComplectedTextColor = ContextCompat.getColor(getContext(), R.color.uncompleted_text_color);//定义默认未完成文字的颜色;
    private int mComplectedTextColor = ContextCompat.getColor(getContext(), android.R.color.white);//定义默认完成文字的颜色;
    private int mTextSize = 14;//default textSize
    private TextView mTextView;

    public StepView(Context context)
    {
        this(context, null);
    }

    public StepView(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public StepView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init()
    {
        View rootView = LayoutInflater.from(getContext()).inflate(R.layout.widget_horizontal_stepsview, this);
        mStepsViewIndicator = (StepViewIndicator) rootView.findViewById(R.id.steps_indicator);
        mStepsViewIndicator.setOnDrawListener(this);
        mTextContainer = (RelativeLayout) rootView.findViewById(R.id.rl_text_container);
    }

    /**
     * 设置显示的文字
     *
     * @param stepsBeanList
     * @return
     */
    public StepView setStepViewTexts(List<StepBean> stepsBeanList)
    {
        mStepBeanList = stepsBeanList;
        mStepsViewIndicator.setStepNum(mStepBeanList);
        return this;
    }


    /**
     * 设置未完成文字的颜色
     *
     * @param unComplectedTextColor
     * @return
     */
    public StepView setStepViewUnComplectedTextColor(int unComplectedTextColor)
    {
        mUnComplectedTextColor = unComplectedTextColor;
        return this;
    }

    /**
     * 设置完成文字的颜色
     *
     * @param complectedTextColor
     * @return
     */
    public StepView setStepViewComplectedTextColor(int complectedTextColor)
    {
        this.mComplectedTextColor = complectedTextColor;
        return this;
    }

    /**
     * 设置StepsViewIndicator未完成线的颜色
     *
     * @param unCompletedLineColor
     * @return
     */
    public StepView setStepsViewIndicatorUnCompletedLineColor(int unCompletedLineColor)
    {
        mStepsViewIndicator.setUnCompletedLineColor(unCompletedLineColor);
        return this;
    }

    /**
     * 设置StepsViewIndicator完成线的颜色
     *
     * @param completedLineColor
     * @return
     */
    public StepView setStepsViewIndicatorCompletedLineColor(int completedLineColor)
    {
        mStepsViewIndicator.setCompletedLineColor(completedLineColor);
        return this;
    }

    /**
     * 设置StepsViewIndicator默认图片
     *
     * @param defaultIcon
     */
    public StepView setStepsViewIndicatorDefaultIcon(Drawable defaultIcon)
    {
        mStepsViewIndicator.setDefaultIcon(defaultIcon);
        return this;
    }

    /**
     * 设置StepsViewIndicator已完成图片
     *
     * @param completeIcon
     */
    public StepView setStepsViewIndicatorCompleteIcon(Drawable completeIcon)
    {
        mStepsViewIndicator.setCompleteIcon(completeIcon);
        return this;
    }

    /**
     * 设置StepsViewIndicator正在进行中的图片
     *
     * @param attentionIcon
     */
    public StepView setStepsViewIndicatorAttentionIcon(Drawable attentionIcon)
    {
        mStepsViewIndicator.setAttentionIcon(attentionIcon);
        return this;
    }

    /**
     * set textSize
     *
     * @param textSize
     * @return
     */
    public StepView setTextSize(int textSize)
    {
        if(textSize > 0)
        {
            mTextSize = textSize;
        }
        return this;
    }

    @Override
    public void ondrawIndicator()
    {
        if(mTextContainer != null)
        {
            mTextContainer.removeAllViews();
            List<Float> complectedXPosition = mStepsViewIndicator.getComplectedXPosition();
            if(mStepBeanList != null && complectedXPosition != null && complectedXPosition.size() > 0)
            {
                for(int i = 0; i < mStepBeanList.size(); i++)
                {
                    mTextView = new TextView(getContext());
                    mTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSize);
                    mTextView.setText(mStepBeanList.get(i).getName());
                    int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                    mTextView.measure(spec, spec);
                    // getMeasuredWidth
                    int measuredWidth = mTextView.getMeasuredWidth();
                    mTextView.setX(complectedXPosition.get(i) - measuredWidth / 2);
                    mTextView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));

                    if(i <= mComplectingPosition)
                    {
                        mTextView.setTypeface(null, Typeface.BOLD);
                        mTextView.setTextColor(mComplectedTextColor);
                    } else
                    {
                        mTextView.setTextColor(mUnComplectedTextColor);
                    }

                    mTextContainer.addView(mTextView);
                }
            }
        }
    }

}

  但是我们关注ondrawIndicator()这个方法中的逻辑,主要是通过OnDrawIndicatorListener这个类的回调来绘制对应的文字描述,再看看在Activity中只的使用

        StepView setpview = (StepView) findViewById(R.id.step_view0);
        List<StepBean> stepsBeanList = new ArrayList<>();
        StepBean stepBean0 = new StepBean("手机绑定",1);
        StepBean stepBean1 = new StepBean("提交信息",1);
        StepBean stepBean2 = new StepBean("充值",0);
        StepBean stepBean3 = new StepBean("用车",-1);

        stepsBeanList.add(stepBean0);
        stepsBeanList.add(stepBean1);
        stepsBeanList.add(stepBean2);
        stepsBeanList.add(stepBean3);

        setpview.setStepViewTexts(stepsBeanList)
                .setTextSize(16);//set textSize 

  很简单有没有,看着一次我们自己写的效果有没有之前作者那种文字重叠的问题 如下图:

  很明显,没有,因为我在StepViewIndicator类中将的mLinePadding属性的值修改大了

  修改之后

mLinePadding = 1.5f * defaultStepIndicatorNum;

  修改之钱

mLinePadding = 0.85f * defaultStepIndicatorNum;

  这里其实可以提供一个set方法来设置动态的设置一下实线和虚线的距离的,好了,这样我们就是实现了我们的需求了,再次感谢StepView的作者,如果有需要源码的同学,可以去下一下,Github下载地址,See You Next Time !!!

posted @ 2017-03-17 16:12  阿呆哥哥  阅读(4893)  评论(0编辑  收藏  举报