外部調用
<com.example.ocean.charts.RectNumberView
android:id="@+id/rectNumberView"
android:layout_width="match_parent"
android:layout_height="400dp"
/>
RectNumberView rectNumberView = findViewById(R.id.rectNumberView);
rectNumberView.setData("6783518");
rectNumberView.start();
數字跳轉特效實現類 RectNumberView.java
package com.example.ocean.charts;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.example.ocean.R;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
public class RectNumberView extends View {
private final Context mContext;
private Paint basePaint;
int[] data;
private int originalX;
private int originalY;
private int rectWidth = dip2px(25);
private int space = dip2px(15);
private int punctuationSize = space;
private Paint rectPaint;
private Paint numberPaint;
private Paint touchPaint;
private Paint touchnumberPaint;
public RectNumberView(Context context) {
this(context,null);
}
public RectNumberView(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public RectNumberView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public RectNumberView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
initPaint();
setData("8761325");
}
private void initPaint() {
basePaint = new Paint();
basePaint.setAntiAlias(true);//抗鋸齒
basePaint.setDither(true);//防抖動
basePaint.setTextSize(dip2px(8));//設置數字大小
basePaint.setStrokeWidth(dip2px(0.5f));//畫筆粗細
basePaint.setColor(Color.GRAY);//設置畫筆顏色
basePaint.setStyle(Paint.Style.STROKE);//設置畫筆顏色
rectPaint = new Paint(basePaint);//矩形畫筆
numberPaint = new Paint(basePaint);//數字畫筆
touchPaint = new Paint(basePaint);//按壓下矩形畫筆
touchPaint.setStyle(Paint.Style.FILL);
touchPaint.setColor(ContextCompat.getColor(getContext(), R.color.red_2));
touchnumberPaint = new Paint(basePaint);//按壓下數字畫筆
touchnumberPaint.setColor(ContextCompat.getColor(getContext(), R.color.blue_rgba_24_261_255));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
originalY = getMeasuredHeight() /2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawRectAndPuntuation(canvas);//對矩形和符號進行繪製
drawNumber(canvas, (int) (data.length* mAnimatedValue));//繪製數字,依據當前動畫來設定顯示幾位數
if ( onTouch && mAnimatedValue == mAnimatedValueMax) {//動畫完成之後才能進行觸摸繪製
drawOnTouch(canvas);
}
}
/** Explain : 對矩形和數字進行繪製
* @author LiXiang */
private void drawRectAndPuntuation(Canvas canvas) {
//使矩形行一直居中顯示
int centerWidth = getWidth() / 2;
int dataSize = (int) (data.length * mAnimatedValue);
int centerData = (dataSize * (rectWidth + space))/2;
originalX = centerWidth - centerData;//由於當前的矩形的個數是一直變化的,所以X軸的起始位置放在此處一直更新
for (int i = 0; i < (int) (data.length * mAnimatedValue); i++) {
int punctuationSum = getPunctuationSum(i);//獲取標點個數
int punctuationLength = getPunctuationAllLength(punctuationSum);//獲取標點個數的總長度
drawPunctuation(canvas, i, punctuationSum);//繪製標點
//繪製矩形
canvas.drawRect(
originalX+space*i+rectWidth*i + punctuationLength,//起始點位置+當前位置前間隔的個數+當前位置前間矩形的長度+當前位置前標點的長度
originalY - rectWidth,
originalX+space*i+(rectWidth)*(i+1)+ punctuationLength,//與起點相比之多了一個rectWidth(矩形的邊長)
originalY,
rectPaint);
//記錄當前的每個矩形的起始點座標和結束點座標,用於touch
if (mAnimatedValue == mAnimatedValueMax) {
rectLocations[i][0] = originalX+space*i+rectWidth*i + punctuationLength;
rectLocations[i][1] = originalX+space*i+(rectWidth)*(i+1)+ punctuationLength;
}
}
}
/** Explain : 獲取標點的總長度
* int sum: 標點的數量
* @author LiXiang */
private int getPunctuationAllLength(int sum) {
return sum*punctuationSize;
}
/** Explain : 繪製標點
* @author LiXiang */
private void drawPunctuation(Canvas canvas, int i, int i1) {
if (i>2 && i%3==0){//只有矩形大於三個且取餘等於0,才繪製標點符號
canvas.drawText(",",originalX+space*(i-1)+(rectWidth)*(i)+i1*space,originalY,numberPaint);
}
}
/** Explain : 通過矩形個數進行取餘計算當前需要幾個標點
* i:矩形個數
* @author LiXiang */
private int getPunctuationSum(int i) {
int i1;
if (i > 2) {
i1 = i / 3;
} else {
i1 = 0;
}
return i1;
}
/** Explain : 繪製數字
* sum: 當前繪製幾位數字
* @author LiXiang */
private void drawNumber(Canvas canvas, int sum) {
int i = sum-1;
for (int j = 0 ; j < sum; j++) {
int intNumber;
//當動畫完成時展示數據
if (mAnimatedValue == mAnimatedValueMax){
intNumber = (int) (data[j] * mAnimatedValue);
}else {
//數字跳動效果
intNumber = new Random().nextInt(9);
}
//獲取數字的寬高,用於計算,使數字居中矩形內
Rect rect = getDimenssion(intNumber);
//繪製數字
canvas.drawText(intNumber+"",
originalX+space*i+rectWidth*i+(rectWidth-rect.width())/2+getPunctuationAllLength(getPunctuationSum(i)),//起始點位置+當前位置前間隔的個數+當前位置前間矩形的長度+當前位置前標點的長度+文字的一半寬度
originalY-(rectWidth-rect.height())/2,
numberPaint);
if (mAnimatedValue == mAnimatedValueMax){
numberLocations[i][0] = originalX+space*i+rectWidth*i+(rectWidth-rect.width())/2+getPunctuationAllLength(getPunctuationSum(i));//起始點位置+當前位置前間隔的個數+當前位置前間矩形的長度+當前位置前標點的長度+文字的一半高度
numberLocations[i][1] = originalY-(rectWidth-rect.height())/2;
}
i--;
}
}
private float moveX;
private float moveY;
protected boolean onTouch = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mAnimatedValue == mAnimatedValueMax) {//曲線繪製完成才能繪製輔助線
onTouch = true;
moveX = event.getX();
moveY = event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
//動畫完成纔可以響應
if (mAnimatedValue == mAnimatedValueMax) {
moveX = event.getX();
moveY = event.getY();
//只有在矩形區域內才繪製
if (moveX >= originalX && moveX <= originalX+getNumberRectWith() && moveY >= originalY-rectWidth && moveY <= originalY ) {
getParent().requestDisallowInterceptTouchEvent(true);//繪製區域內 允許子view響應觸摸事件
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
invalidate();
moveX = event.getX();
moveY = event.getY();
getParent().requestDisallowInterceptTouchEvent(false);//繪製區域內 允許子view響應觸摸事件
onTouch = false;
break;
}
if (onTouch && mAnimatedValue == mAnimatedValueMax) {
return true;
} else {
return super.onTouchEvent(event);
}
}
int[][] rectLocations;
int[][] numberLocations;
/** Explain : 當前滑動到的位置 通過與數組記錄的每個矩形和數字的起始點位置和結束點位置進行校對
* 當出現在在合規範圍內,則對對應的矩形和數字進行繪製
* @author LiXiang */
private void drawOnTouch(Canvas canvas) {
for (int i = 0; i < rectLocations.length; i++) {
if (moveX>=rectLocations[i][0] && moveX<=rectLocations[i][1]) {//進行校對
canvas.drawRect(
rectLocations[i][0],//矩形的起點
originalY - rectWidth,
rectLocations[i][1],//矩形的終點
originalY,
touchPaint);
canvas.drawText(String.valueOf(data[data.length-1-i]),numberLocations[i][0],numberLocations[i][1],touchnumberPaint);
}
}
}
/** Explain : 獲取當前整個矩形的長度
* @author LiXiang */
private float getNumberRectWith() {
return data.length*rectWidth
+space*(data.length-1)
+getPunctuationAllLength(getPunctuationSum(data.length));
}
/** Explain : 計算數字的寬高
* @author LiXiang */
@NotNull
private Rect getDimenssion(int intNumber) {
String number = intNumber+"";
//獲取數字寬高尺寸
Rect rect= new Rect();
numberPaint.getTextBounds(number,0,number.length(),rect);
return rect;
}
// 開啓動畫
public void start() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
startAnimator();
} else {
this.post(new Runnable() {//可以避免頁面未初始化完成造成的 空白
@Override
public void run() {
startAnimator();
}
});
}
}
protected ValueAnimator valueAnimator;
protected boolean starting = false;
protected float mAnimatedValueMax = 1;
protected float mAnimatedValue = 0;
private void startAnimator() {
if ( starting) {//只能繪製一次 或者正在繪製過程中的話不能再次繪製
return;
}
starting = true;
valueAnimator = ValueAnimator.ofFloat(0, mAnimatedValueMax).setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatedValue = (float) valueAnimator.getAnimatedValue();
if (starting) {
invalidate();
}
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
starting = false;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.start();
}
public void setData(String str) {
this.data = new int[str.length()];
int i = 0;
for (int length = str.length()-1; length > -1; length--) {
this.data[i++] = Integer.valueOf(str.charAt(length)+"");
}
rectLocations = new int[data.length][2];
numberLocations = new int[data.length][2];
}
protected int dip2px(float dipValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}