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 !!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM