1. 程式人生 > >仿支付寶獎勵金的時間軸(也可用於做垂直進度條)

仿支付寶獎勵金的時間軸(也可用於做垂直進度條)

前言:
這是一個,既可以用做時間軸,也可以用做垂直進度條的自定義控制元件。(沒有用系統的Progress控制元件,進度是自己用Canvas畫的)。

先看Gif效果圖:
這裡寫圖片描述

通過介面,來定義自定義控制元件的功能。閱讀程式碼的時候,看介面就行。具體功能,再到自定義控制元件的原始碼找。

/**
 * @Author 李嶽鋒
 * @CreateTime 2018/1/26
 * @Description 垂直進度條樣式的功能定義
 * 當需要給VerticalProgress增加新功能時,要在這裡定義方法
 **/
public interface IVerticalProgress {

    /**
     * 初始化檢視
     *
     * @param
context 上下文 * @param attrs 屬性 */
void initView(Context context, @Nullable AttributeSet attrs); /** * 設定當前所在的點(位置) * * @param currentPoint 該值必須大於0。並且小於或等於最大的點數,不然會丟擲執行時異常。 * 詳見{{@link #setMaxPointCount(int)}}. */ void setCurrentPoint(int
currentPoint); /** * 設定最大圓點數 * * @param maxPointCount 最大圓點數。 */ void setMaxPointCount(int maxPointCount); /** * 計算分割線的大小 * @param extraLength 額外新增的長度。該值大於0時,分割線的長度增加。小於0時,分割線的長度減少。為0時,不做處理。 */ void countDividerLength(int extraLength); /** * 獲取當前點的位置 */
int getCurrentPoint(); /** * 獲取最大圓點數 */ int getMaxPointCount(); /** * 計算點的位置 */ void countPointPositions(); /** * 畫直線,多條水平線組成一條垂直線,來實現漸變色 */ void drawVerticalLine(Canvas canvas); /** * 畫圓點 */ void drawPoints(Canvas canvas); /** * 繪製最後一個點,需要加光暈效果,圓點一樣,但半徑是3倍 */ void drawLastPoint(Canvas canvas); /** * 獲取圓的中心點的起始x座標 */ float getDrawingStartCircleX(); /** * 獲取圓的中心點的起始y座標 */ float getDrawingNextCircleY(); /** * 獲取下個要繪製圓點的中心點的Y座標 * * @param cy 根據cy,計算出下個要繪製點的Y座標 * @return 計算出下個要繪製點的Y座標 */ float getDrawingNextCircleY(float cy); /** * @param drawingPoint 當前正在繪製的點的下標,比如,第x個。 * @return 是否達到該點 */ boolean isSearched(int drawingPoint); /** * @param drawingY 正在繪製的Y座標 * @return 是否達到該點 */ boolean isSearched(float drawingY); /** * @param radio 當前的漸變比率 * @return 獲取漸變色 */ int getGradientColor(float radio); /** * 獲取分割線的長度 */ float getDividerLength(); /** * 獲取點的座標 */ float[][] getPointPositions(); }

這是原始碼部分:

/**
 * @Author 李嶽鋒
 * @CreateTime 2018/1/25
 * @Description 垂直進度條樣式.
 **/
public class VerticalProgress extends View implements IVerticalProgress {

    // 建立畫筆
    private Context mContext;
    private Paint mPaint; // 畫筆
    private Paint mVerticalPaint; // 垂直線的畫筆
    private Paint mHalationPaint; // 光暈畫筆
    private int mHalationColor =Color.parseColor("#FFE052"); // 光暈的顏色
    private int mBackGroundColor = Color.parseColor("#f2f2f2"); // 進度條背景顏色
    protected float mRadius; // 圓的半徑
    private float mDividerLength; // 圓的間距
    private int mMaxPointCount = 0; // 圓點數量
    private int mCurrentPoint = 1; // 當前到達的位置
    private int mMeasuredHeight; // 控制元件高
    private int mMeasuredWidth; // 控制元件寬
    private float mVerticalLineWidth; // 直線寬度


    // 圓點的座標
    protected float mPointPositions[][];

    // 透明度
    private int mAlpha = 255;
    // 光暈
    private int mHalation = 255; //透明度
    private float mHalationWidthTimes = 3.0f;// 光暈相對於圓點的大小,倍數。

    // 起始顏色的RGB
    private int mRedStart;
    private int mGreenStart;
    private int mBlueStart;

    // 終止顏色的RGB
    private int mRedEnd;
    private int mGreenEnd;
    private int mBlueEnd;

    // 過度顏色差
    private int mRedFlag = 1;
    private int mGreenFlag = 1;
    private int mBlueFlag = 1;

    // 每一幀,光暈的透明度的變化值,該值越大,透明度變淡的速度越慢。
    // 該值必須在這個範圍 mReduceHalationAlpha < 255 && mReduceHalationAlpha > 0
    private float mReduceHalationAlpha = 16.0f;
    // 動畫效果是否已準備好
    private boolean mIsAnimationPrepared = false;
    // 是否停止動畫
    private boolean isForceStopAnimation = false;
    // 當前的幀
    private int mFrameCount = 1;
    // 最大幀
    private int mMaxFrameCount = 29;
    // 最小幀
    private int mMinFrameCount = 1;


    public VerticalProgress(Context context) {
        super(context);
        initView(context, null);
    }

    public VerticalProgress(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView(context, attrs);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);

        mMeasuredWidth = getMeasuredWidth();

        // 半徑長 == 控制元件寬度的一半,再除以光暈的倍數
        if(mRadius == 0) {
            mRadius = (mMeasuredWidth / 2 / mHalationWidthTimes);
        }

        mMeasuredHeight = getMeasuredHeight() - (int) mRadius * 2;

        // 計算分割線的長度
        countDividerLength(0);
    }

    @Override
    public void countDividerLength(int extraLength){
        mDividerLength = (mMeasuredHeight - mRadius * 2 * mMaxPointCount - mRadius * mHalationWidthTimes) / (mMaxPointCount - 1)  + extraLength;
    }

    @Override
    public void initView(Context context, @Nullable AttributeSet attrs) {
        mContext = context;
        mPaint = new Paint();
        mHalationPaint = new Paint();
        mVerticalPaint = new Paint();

        mPaint.setAntiAlias(true);
        mVerticalPaint.setAntiAlias(true);

        if (attrs != null) {

            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerticalProgress);

            mRadius = typedArray.getDimension(R.styleable.VerticalProgress_point_radius,0f);
            mBackGroundColor = typedArray.getColor(R.styleable.VerticalProgress_background_color, mBackGroundColor);
            mMaxPointCount = typedArray.getInt(R.styleable.VerticalProgress_point_count, mMaxPointCount);
            mCurrentPoint = typedArray.getInt(R.styleable.VerticalProgress_current_point, mMaxPointCount);

            int startColor = typedArray.getColor(R.styleable.VerticalProgress_start_color, mBackGroundColor);
            int endColor = typedArray.getColor(R.styleable.VerticalProgress_end_color, mBackGroundColor);

            mRedStart = Color.red(startColor);
            mGreenStart = Color.green(startColor);
            mBlueStart = Color.blue(startColor);

            mRedEnd = Color.red(endColor);
            mGreenEnd = Color.green(endColor);
            mBlueEnd = Color.blue(endColor);

            typedArray.recycle();
        }

        if (mMaxPointCount == 0) {
            throw new RuntimeException("必須設定app:point_count屬性,並且值要大於0");
        }

        // 初始化圓點座標陣列
        mPointPositions = new float[mMaxPointCount][2];
        // 開始動畫
        mAnimateThread.start();
        // 直線寬
        mVerticalLineWidth =px2dp(mContext,3);

    }

    @Override
    public void setCurrentPoint(int currentPoint) {

        if (currentPoint == 0) {
            throw new RuntimeException("currentPoint必須大於0");
        }

        if (currentPoint > mMaxPointCount) {
            throw new RuntimeException("currentPoint不能大於mMaxPointCount");
        }

        mCurrentPoint = currentPoint;
        invalidate();
    }

    @Override
    public void setMaxPointCount(int maxPointCount) {
        if (maxPointCount < mCurrentPoint) {
            throw new RuntimeException("maxPointCount不能小於mCurrentPoint");
        }

        mMaxPointCount = maxPointCount;
        invalidate();
    }

    @Override
    public int getCurrentPoint() {
        return mCurrentPoint;
    }

    @Override
    public int getMaxPointCount() {
        return mMaxPointCount;
    }

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

        // 計算點的位置
        countPointPositions();
        // 畫直線
        drawVerticalLine(canvas);
        // 畫圓點
        drawPoints(canvas);
    }

    @Override
    public void countPointPositions() {

        // 當前正在繪製的點
        int drawingPoint = 0;

        for (float cy = getDrawingNextCircleY(); cy < mMeasuredHeight && drawingPoint < mMaxPointCount;
             cy = getDrawingNextCircleY(cy), ++drawingPoint) {
            // 儲存當前繪製點的座標
            mPointPositions[drawingPoint][0] = getDrawingStartCircleX();
            mPointPositions[drawingPoint][1] = cy;
        }

    }

    @Override
    public void drawPoints(Canvas canvas) {

        // 分段的顏色比率
        float radio;
        // 當前的比率
        float currentRadio = 0;

        // 計算分段顏色比率
        if (mCurrentPoint == 1) {
            radio = 0;
        } else {
            radio = 1.0f / (mCurrentPoint - 1);
        }

        // 畫圓
        for (int drawingPoint = 1; drawingPoint <= mMaxPointCount; ++drawingPoint, currentRadio += radio) {

            // 已到達的
            if (isSearched(drawingPoint)) {
                if (drawingPoint == mCurrentPoint) {
                    // 最後一個點,額外處理
                    drawLastPoint(canvas);
                } else {
                    // 不是最後一個點,設定漸變色
                    mPaint.setColor(getGradientColor(currentRadio));
                }
            } else {
                // 未到達的,設定背景色
                mPaint.setColor(mBackGroundColor);
            }
            canvas.drawCircle(mPointPositions[drawingPoint - 1][0], mPointPositions[drawingPoint - 1][1], mRadius, mPaint);
        }

    }


    @Override
    public void drawVerticalLine(Canvas canvas) {

        if(mPointPositions == null) {
            return;
        }

        // 直線的位置
        float startY = getDrawingNextCircleY();
        float stopY = startY + 1;

        // 直線的寬度
        float startX = (int) (getDrawingStartCircleX() - mVerticalLineWidth / 2);
        float stopX = (int) (getDrawingStartCircleX() + mVerticalLineWidth / 2);

        // 繪製的顏色
        int drawingColor;

        // 直線長度
        float lineHeight = mPointPositions[mMaxPointCount-1][1] - mRadius;

        // 漸變線的長度
        float gradientLineHeight = mPointPositions[mCurrentPoint - 1][1] - mRadius;

        // 畫一條直線
        while (startY <= lineHeight) {

            if (isSearched(startY)) {
                // 繪製已到達的部分的漸變色
                if (startY == mRadius * mHalationWidthTimes) {
                    drawingColor = getGradientColor(0);
                } else {
                    drawingColor = getGradientColor(startY / gradientLineHeight);
                }
            } else {
                // 繪製未到達部分的漸變色
                drawingColor = mBackGroundColor;
            }

            // 設定顏色
            mVerticalPaint.setColor(drawingColor);
            // 畫線
            canvas.drawLine(startX, startY, stopX, stopY, mVerticalPaint);
            // 不斷的增長
            ++startY;
            ++stopY;
        }

    }

    @Override
    public boolean isSearched(int drawingPoint) {
        return (drawingPoint <= mCurrentPoint);
    }

    @Override
    public boolean isSearched(float drawingY) {
        if (mCurrentPoint == 1) {
            return false;
        }
        return (drawingY <= (mPointPositions[mCurrentPoint - 1][1]));
    }

    @Override
    public void drawLastPoint(Canvas canvas) {

        float mLastPointX = mPointPositions[mCurrentPoint - 1][0];
        float mLastPointY = mPointPositions[mCurrentPoint - 1][1];

        if (mLastPointX == 0 && mLastPointY == 0) {
            return;
        }

        // 光暈
        mHalationPaint.setColor(mHalationColor);
        mHalationPaint.setAlpha(mHalation);

        if (mFrameCount == 1 || mFrameCount > 16) {
            canvas.drawCircle(mLastPointX, mLastPointY, mRadius, mHalationPaint);
        } else {
            float radius = (mFrameCount / 16.0f) * (mRadius * mHalationWidthTimes);
            canvas.drawCircle(mLastPointX, mLastPointY, radius, mHalationPaint);
        }

        // 最後一個圓點
        mPaint.setColor(getGradientColor(1));
        canvas.drawCircle(mLastPointX, mLastPointY, mRadius, mPaint);

        // 可以開始動畫效果
        mIsAnimationPrepared = true;
    }

    @Override
    public float getDrawingStartCircleX() {
        return mRadius * mHalationWidthTimes;
    }

    @Override
    public float getDrawingNextCircleY() {
        return mRadius * mHalationWidthTimes;
    }

    @Override
    public float getDrawingNextCircleY(float cy) {
        return cy + mDividerLength + mRadius * 2;
    }

    @Override
    public int getGradientColor(float radio) {

        if (radio == 0) {
            return Color.argb(mAlpha, mRedStart, mGreenStart, mBlueStart);
        } else if (radio >= 1) {
            return Color.argb(mAlpha, mRedEnd, mGreenEnd, mBlueEnd);
        }

        int red = (int) (mRedStart + ((mRedEnd - mRedStart) * radio + mRedFlag));
        int green = (int) (mGreenStart + ((mGreenEnd - mGreenStart) * radio + mGreenFlag));
        int blue = (int) (mBlueStart + ((mBlueEnd - mBlueStart) * radio + mBlueFlag));

        return Color.argb(mAlpha, red, green, blue);
    }

    @Override
    public float getDividerLength() {
        return mDividerLength;
    }

    @Override
    public float[][] getPointPositions() {
        return mPointPositions;
    }


    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // 停止動畫
        isForceStopAnimation = true;
    }

    /**
     * 動畫執行緒
     */
    private Thread mAnimateThread = new Thread(new Runnable() {

        @Override
        public void run() {


            while (true) {
                try {
                    if (isForceStopAnimation) {
                        break;
                    }
                    if (mIsAnimationPrepared) {
                        mRefreshUI.sendEmptyMessage(0);
                    }
                    Thread.sleep(35);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }

            }
        }
    });

    /**
     * 重新整理介面
     */
    private Handler mRefreshUI = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {

            if (mReduceHalationAlpha < 255 && mReduceHalationAlpha > 0) {
                mHalation -= 255 / mReduceHalationAlpha;
            }

            invalidate();

            // 達到最大幀,重置
            if (mFrameCount == mMaxFrameCount) {
                mHalation = 255;
                mFrameCount = mMinFrameCount;
            } else {
                ++mFrameCount;
            }

            return false;
        }
    });

    /**
     * px轉換成dp
     */
    private int px2dp(Context context,float pxValue){
        float scale=context.getResources().getDisplayMetrics().density;
        return (int)(pxValue/scale+0.5f);
    }

}

這是自定義控制元件屬性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="VerticalProgress">
        <!-- 預設的背景顏色 -->
        <attr name="background_color" format="color" />
        <!-- 圓點的數量 -->
        <attr name="point_count" format="integer" />
        <!-- 當前到達的位置 -->
        <attr name="current_point" format="integer" />
        <!-- 起始顏色 -->
        <attr name="start_color" format="color" />
        <!-- 結束顏色 -->
        <attr name="end_color" format="color" />
        <!-- 圓點的半徑 -->
        <attr name="point_radius" format="dimension"/>
    </declare-styleable>
</resources>

這是使用示例:

   <com.lyf.okmvp.widget.verticalprogress.VerticalProgress
        android:id="@+id/verticalProgress"
        android:layout_width="30dp"
        android:layout_height="match_parent"
        android:layout_marginBottom="25dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="20dp"
        android:visibility="visible"
        app:background_color="@color/colorF2F2F2"
        app:current_point="1"
        app:end_color="@color/color_vertical_progress_end"
        app:point_count="7"
        app:point_radius="5dp"
        app:start_color="@color/color_vertical_progress_start" /> 

這是靜態示例圖,支付寶跟我的對比:

這是我的:
這裡寫圖片描述

這是支付寶的:
這裡寫圖片描述

本文到此為止… Thank you

注意,程式碼位於這個包下:
com.lyf.okmvp.widget.verticalprogress