仿支付寶獎勵金的時間軸(也可用於做垂直進度條)
阿新 • • 發佈:2019-01-06
前言:
這是一個,既可以用做時間軸,也可以用做垂直進度條的自定義控制元件。(沒有用系統的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