1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Android 绘制数字向上向下滚动的动画

Android 绘制数字向上向下滚动的动画

时间:2022-12-13 21:18:24

相关推荐

Android 绘制数字向上向下滚动的动画

先上效果如下(模拟器有些卡):

代码实现:

一个数字对应一个 ScrollingNumber 对象。

import android.animation.Animator;import android.animation.AnimatorSet;import android.animation.ValueAnimator;import android.annotation.SuppressLint;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.LinearGradient;import android.graphics.Paint;import android.graphics.Point;import android.graphics.Rect;import android.graphics.Shader;import android.graphics.Typeface;import android.text.TextUtils;import android.util.AttributeSet;import android.view.View;import android.view.animation.AccelerateDecelerateInterpolator;import androidx.annotation.ColorInt;import androidx.annotation.IntDef;import androidx.annotation.IntRange;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.util.ArrayList;/*** @author ganmin.he* @date /5/13*/public class ScrollingNumbersView extends View implements ValueAnimator.AnimatorUpdateListener,ColorAnimateHelper.ColorUpdateListener, PauseResumeAnimateHelper.PauseResumeListener {@IntDef({ALIGNMENT_CENTER, ALIGNMENT_LEFT, ALIGNMENT_RIGHT})@Retention(RetentionPolicy.SOURCE)public @interface Alignment {}public static final int ALIGNMENT_CENTER = 0;public static final int ALIGNMENT_LEFT = 1;public static final int ALIGNMENT_RIGHT = 2;public static final float DEFAULT_TEXT_SIZE = 105f;public static final float DEFAULT_LINE_SPACING_MULTIPLIER = 1.5f;public static final float DEFAULT_SINGLE_NUMBER_WIDTH_MULTIPLIER = 1.2f;public static final float DEFAULT_SUB_TEXT_SIZE = 36f;public static final int NO_ADJUST_SUBTEXT = Integer.MAX_VALUE;private final int mMaxDigits; // The count of all numbersprivate int mShowingCount; // The count of showing numbers@Alignmentprivate int mAlignment; // Default: ALIGNMENT_CENTERprivate float mLineSpacingMult; // Default: DEFAULT_LINE_SPACING_MULTIPLIERprivate float mSingleNumberWidthMult; // Default: DEFAULT_SINGLE_NUMBER_WIDTH_MULTIPLIERprivate String mSubText; // May be "%" or "分"private float mSubTextSize; // Default: DEFAULT_SUB_TEXT_SIZEprivate boolean mPauseAt95; // Whether to pause at 95%private int mAdjustSubTextThreshold = NO_ADJUST_SUBTEXT; // adjust sub text x when centeredprivate int mCurrentValue; // 0 9 9private int mAdjacentValue;// 1 0 0private final int[] mCurrentNumbers; // |0|9|9|private final int[] mAdjacentNumbers;// |1|0|0|private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);private final Point mTextWidthHeight = new Point();private final Point mSubWidthHeight = new Point();private final Point mSubLocation = new Point();private final Rect mDrawingArea = new Rect();private final ArrayList<ScrollingNumber> mNumbers = new ArrayList<>();private final AnimatorSet mAnimatorSet = new AnimatorSet();private final ArrayList<Animator> mAnimators = new ArrayList<>();public ScrollingNumbersView(Context context) {this(context, null);}public ScrollingNumbersView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public ScrollingNumbersView(Context context, AttributeSet attrs,int defStyleAttr) {this(context, attrs, defStyleAttr, 0);}public ScrollingNumbersView(Context context, AttributeSet attrs,int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ScrollingNumbersView, defStyleAttr, defStyleRes);int maxDigits = a.getInt(R.styleable.ScrollingNumbersView_maxDigits, 3);int initialValue = a.getInt(R.styleable.ScrollingNumbersView_initialValue, 0);int textColor = a.getColor(R.styleable.ScrollingNumbersView_textColor, Color.RED);float textSize = a.getDimension(R.styleable.ScrollingNumbersView_textSize,DEFAULT_TEXT_SIZE);int styleIndex = a.getInt(R.styleable.ScrollingNumbersView_textStyle, Typeface.NORMAL);int alignment = a.getInt(R.styleable.ScrollingNumbersView_alignment, ALIGNMENT_CENTER);float lineSpacingMult = a.getFloat(R.styleable.ScrollingNumbersView_lineSpacingMultiplier,DEFAULT_LINE_SPACING_MULTIPLIER);float singleNumberWidthMult = a.getFloat(R.styleable.ScrollingNumbersView_singleNumberWidthMultiplier,DEFAULT_SINGLE_NUMBER_WIDTH_MULTIPLIER);float subTextSize = a.getDimension(R.styleable.ScrollingNumbersView_subTextSize,DEFAULT_SUB_TEXT_SIZE);String subText = a.getString(R.styleable.ScrollingNumbersView_subText);int adjustSubTextThreshold = a.getInt(R.styleable.ScrollingNumbersView_adjustSubTextThreshold, NO_ADJUST_SUBTEXT);a.recycle();mMaxDigits = maxDigits;mCurrentNumbers = new int[mMaxDigits];mAdjacentNumbers = new int[mMaxDigits];for (int i = 0; i < mMaxDigits; i++) {addScrollingNumber(new ScrollingNumber(i), true);}setPaintColor(textColor);setPaintTextSize(textSize);Typeface tf = Typeface.createFromAsset(context.getAssets(), "fonts/HelveticaNeue.ttc");setPaintTypeface(Typeface.create(tf, styleIndex));//setPaintTypeface(Typeface.createFromFile("/system/fonts/Smartisan_Latin-Bold.otf"));//setPaintTypeface(Typeface.defaultFromStyle(styleIndex));setAlignment(alignment);setLineSpacingMultiplier(lineSpacingMult);setSingleNumberWidthMultiplier(singleNumberWidthMult);setSubTextSize(subTextSize);setSubText(subText);setAdjustSubTextThresholdWhenCentered(adjustSubTextThreshold);setInitialValue(initialValue);}private void addScrollingNumber(ScrollingNumber sn, boolean toLeft) {if (toLeft) {mNumbers.add(0, sn);} else {mNumbers.add(sn);}ValueAnimator animator = sn.getValueAnimator();animator.addUpdateListener(this);mAnimators.add(animator);mAnimatorSet.playTogether(mAnimators);}/*private void removeScrollingNumber(boolean fromLeft) {if (mNumbers.isEmpty()) {return;}ScrollingNumber sn;if (fromLeft) {sn = mNumbers.remove(0);} else {sn = mNumbers.remove(mNumbers.size() - 1);}if (sn == null) {return;}ValueAnimator animator = sn.getValueAnimator();animator.removeUpdateListener(this);mAnimators.remove(animator);mAnimatorSet.playTogether(mAnimators);sn.removeNumberChangeListener();}*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);int allNumberWidth = (int) (mMaxDigits * mTextWidthHeight.x * mSingleNumberWidthMult);//int height = getPaddingTop() + mTextWidthHeight.y + getPaddingBottom();int height = (int) mPaint.getTextSize();int paddingVertical = (height - mTextWidthHeight.y) / 2;setPadding(getPaddingLeft(), paddingVertical, getPaddingRight(), paddingVertical);int width = getPaddingLeft() + allNumberWidth + mSubWidthHeight.x + getPaddingRight();if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {// Use width, height} else if (widthSpecMode == MeasureSpec.AT_MOST) {height = heightSpecSize;} else if (heightSpecMode == MeasureSpec.AT_MOST) {width = widthSpecSize;}setMeasuredDimension(width, height);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);updateNumberLocation();mDrawingArea.set(0, 0, getWidth(), getHeight());}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.save();int height = getHeight();int color = mPaint.getColor();float factor = 0.5f - mTextWidthHeight.y * 0.5f / height;@SuppressLint("DrawAllocation")LinearGradient verticalGradient = new LinearGradient(0, 0, 0, height,new int[]{Color.TRANSPARENT, color, color, Color.TRANSPARENT},new float[]{0, factor, 1f - factor, 1f},Shader.TileMode.CLAMP);mPaint.setShader(verticalGradient);drawNumberOnCanvas(canvas, mPaint);mPaint.setShader(null);if (!TextUtils.isEmpty(mSubText)) {float textSize = mPaint.getTextSize();mPaint.setTextSize(mSubTextSize);canvas.drawText(mSubText, mSubLocation.x, mSubLocation.y, mPaint);mPaint.setTextSize(textSize);}canvas.restore();}private void drawNumberOnCanvas(Canvas canvas, Paint paint) {for (ScrollingNumber sn : mNumbers) {sn.drawOnCanvas(canvas, paint);}}public void setPaintTextSize(float textSize) {mPaint.setTextSize(textSize);updateTextWidthHeight();updateSubWidthHeight();//updateNumberLocation();}private void updateTextWidthHeight() {Rect bounds = new Rect();mPaint.getTextBounds("0", 0, 1, bounds);mTextWidthHeight.set(bounds.width(), bounds.height());updateNumberLocation();}public void setPaintColor(@ColorInt int color) {mPaint.setColor(color);}public void setPaintTypeface(Typeface tf) {mPaint.setTypeface(tf);}public void setAlignment(@Alignment int alignment) {mAlignment = alignment;updateNumberLocation();}public void setLineSpacingMultiplier(float lineSpacingMult) {mLineSpacingMult = lineSpacingMult;}public void setSingleNumberWidthMultiplier(float singleNumberWidthMult) {mSingleNumberWidthMult = singleNumberWidthMult;updateNumberLocation();}public void setSubText(String text) {mSubText = text == null ? "" : text.trim();updateSubWidthHeight();}public void setSubTextSize(float subTextSize) {mSubTextSize = subTextSize;updateSubWidthHeight();}private void updateSubWidthHeight() {if (mSubTextSize < 0.001f) {mSubText = "";}if (TextUtils.isEmpty(mSubText)) {mSubWidthHeight.set(0, 0);} else {float textSize = mPaint.getTextSize();mPaint.setTextSize(mSubTextSize);Rect bounds = new Rect();mPaint.getTextBounds(mSubText, 0, 1, bounds);mSubWidthHeight.set(bounds.width(), bounds.height());mPaint.setTextSize(textSize);}updateNumberLocation();}public void setPauseAt95Percent(boolean pauseAt95) {mPauseAt95 = pauseAt95;}public void setInitialValue(int initValue) {mCurrentValue = initValue;mAdjacentValue = initValue;valueToArray(mCurrentValue, mCurrentNumbers);valueToArray(mAdjacentValue, mAdjacentNumbers);setNumberWithValue(initValue);}private void setNumberWithValue(int value) {for (ScrollingNumber sn : mNumbers) {sn.setNumberWithValue(value);}int newCount = updateNumberCanShowZero() + 1;updateShowingNumberCount(newCount);}private int updateNumberCanShowZero() {int count = 0;boolean canShowZero = false;for (int i = 0; i < mAdjacentNumbers.length - 1; i++) {mNumbers.get(i).setCanShowZero(canShowZero);if (!canShowZero && mAdjacentNumbers[i] != 0) {count = mAdjacentNumbers.length - (i + 1);canShowZero = true;}}// The ones digit can show zeromNumbers.get(mAdjacentNumbers.length - 1).setCanShowZero(true);return count;}private void updateShowingNumberCount(int newCount) {if (newCount == mShowingCount) {return;}mShowingCount = newCount;updateNumberLocation();}public void setAdjustSubTextThresholdWhenCentered(int adjustSubTextThreshold) {mAdjustSubTextThreshold = adjustSubTextThreshold;updateNumberLocation();}// Call this when mAlignment, mTextWidthHeight, mSubWidthHeight, mSingleNumberWidthMult,// mShowingCount, mAdjustSubTextThreshold changed and onLayoutprivate void updateNumberLocation() {int paddingLeft = getPaddingLeft();int paddingRight = getPaddingRight();int paddingTop = getPaddingTop();int newX = paddingLeft;int singleNumberWidth = (int) (mTextWidthHeight.x * mSingleNumberWidthMult);int allNumberWidth = mMaxDigits * singleNumberWidth;int showingNumberWidth = mShowingCount * singleNumberWidth;int offsetX = allNumberWidth - showingNumberWidth;switch (mAlignment) {case ALIGNMENT_CENTER:int subWidth = (mShowingCount >= mAdjustSubTextThreshold) ? mSubWidthHeight.x : 0;newX = (int) ((getWidth() - showingNumberWidth - subWidth) / 2f) - offsetX;break;case ALIGNMENT_LEFT:newX -= offsetX;break;case ALIGNMENT_RIGHT:newX = getWidth() - paddingRight - mSubWidthHeight.x - allNumberWidth;break;}mSubLocation.set(newX + allNumberWidth, paddingTop + mSubWidthHeight.y);int newY = paddingTop + mTextWidthHeight.y;for (ScrollingNumber sn : mNumbers) {sn.setLocation(newX, newY);newX += singleNumberWidth;}}public void setNumberAnimatorValues(int... targetValues) {for (ScrollingNumber sn : mNumbers) {sn.setAnimatorValues(mCurrentValue, targetValues);}}public void setNumberAnimatorDuration(long duration) {mAnimatorSet.setDuration(duration);}public void startNumberAnimator() {mAnimatorSet.start();}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {invalidate();}@Overridepublic void onColorUpdate(int color) {setPaintColor(color);invalidate();}@Overridepublic void onPause() {mAnimatorSet.pause();}@Overridepublic void onResume() {mAnimatorSet.resume();}private static int[] valueToArray(int value, int length) {int[] array = new int[length];valueToArray(value, array);return array;}private static void valueToArray(int value, int[] array) {/*Arrays.fill(array, 0);char[] chars = String.valueOf(value).toCharArray();int charlen = chars.length;for (int i = 0; i < charlen; i++) {array[i + array.length - charlen] = chars[i] - 48;}*/for (int i = array.length - 1; i >= 0; i--) {array[i] = value % 10;value /= 10;}}private static int arrayToValue(int[] array) {/*int len = array.length;char[] chars = new char[len];for (int i = 0; i < len; i++) {chars[i] = (char) (array[i] + 48);}return Integer.parseInt(String.valueOf(chars));*/int value = 0;int pow = 1;for (int i = array.length - 1; i >= 0; i--) {value += array[i] * pow;pow *= 10;}return value;}public void onNumberChanged(ScrollingNumber sn, int oldNumber, int newNumber) {mCurrentNumbers[mMaxDigits - sn.getDigit() - 1] = newNumber;int oldValue = mCurrentValue;mCurrentValue = arrayToValue(mCurrentNumbers);if (oldValue < 95 && mCurrentValue == 95 && mPauseAt95) {PauseResumeAnimateHelper.getInstance().pauseAnimator();}}public void onAdjacentNumberStartShowing(ScrollingNumber sn, int adjacentNumber) {mAdjacentNumbers[mMaxDigits - sn.getDigit() - 1] = adjacentNumber;int newCount = updateNumberCanShowZero() + 1;updateShowingNumberCount(newCount);mAdjacentValue = arrayToValue(mAdjacentNumbers);}private class ScrollingNumber implements ValueAnimator.AnimatorUpdateListener,Animator.AnimatorListener {private final int mDigit;private final int mPower; // 10^digitprivate final ValueAnimator mValueAnimator;private int mPreNumber, mNumber, mNextNumber;private int mLocationX, mLocationY, mScrollingY;private int mScrollYAccumulation; // |mScrollYAccumulation| < mLineSpacing;private int mLastAnimatedValue;private boolean mNotifyAdjacentNumberStartShowing;private boolean mCanShowZero;public ScrollingNumber(int digit) {mDigit = digit;mPower = (int) Math.pow(10, digit);mValueAnimator = new ValueAnimator();mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());mValueAnimator.addUpdateListener(this);mValueAnimator.addListener(this);}public int getDigit() {return mDigit;}private void setNumber(@IntRange(from = 0, to = 9) int number) {onNumberChanged(this, mNextNumber, number);mNotifyAdjacentNumberStartShowing = true;mNumber = number;mPreNumber = (mNumber + 9) % 10;mNextNumber = (mNumber + 1) % 10;}public void setNumberWithValue(int value) {setNumber((value / mPower) % 10);}private boolean isVisible(int number) {int lineSpacing = (int) (mTextWidthHeight.y * mLineSpacingMult);if (number == mPreNumber) {return mScrollingY - lineSpacing > 0;} else if (number == mNumber) {return mScrollingY > 0|| mScrollingY - mTextWidthHeight.y < mDrawingArea.height();} else if (number == mNextNumber) {return mScrollingY + lineSpacing - mTextWidthHeight.y < mDrawingArea.height();}return false;}public void setLocation(int newX, int newY) {mLocationX = newX;int offsetY = newY - mLocationY;mLocationY = newY;mScrollingY += offsetY;}private void scrollYBy(int offsetY) {if (offsetY == 0) {return;}int lineSpacing = (int) (mTextWidthHeight.y * mLineSpacingMult);mScrollingY += offsetY;mScrollYAccumulation += offsetY;int valueDiff = mScrollYAccumulation / lineSpacing;if (valueDiff != 0) {int newNumber = (mNumber - valueDiff) % 10;if (newNumber < 0) {newNumber += 10;}setNumber(newNumber);mScrollingY -= valueDiff * lineSpacing;mScrollYAccumulation %= lineSpacing;}if (mNotifyAdjacentNumberStartShowing) {int adjacentNumber = offsetY > 0 ? mPreNumber : mNextNumber;if (isVisible(adjacentNumber)) {onAdjacentNumberStartShowing(this, adjacentNumber);mNotifyAdjacentNumberStartShowing = false;}}}public void setCanShowZero(boolean canShowZero) {mCanShowZero = canShowZero;}public void drawOnCanvas(Canvas canvas, Paint paint) {int lineSpacing = (int) (mTextWidthHeight.y * mLineSpacingMult);if ((mPreNumber != 0 || mCanShowZero) && isVisible(mPreNumber)) {canvas.drawText("" + mPreNumber, mLocationX, mScrollingY - lineSpacing, paint);}if ((mNumber != 0 || mCanShowZero) && isVisible(mNumber)) {canvas.drawText("" + mNumber, mLocationX, mScrollingY, paint);}if ((mNextNumber != 0 || mCanShowZero) && isVisible(mNextNumber)) {canvas.drawText("" + mNextNumber, mLocationX, mScrollingY + lineSpacing, paint);}}public ValueAnimator getValueAnimator() {return mValueAnimator;}public void setAnimatorValues(int currentValue, int... targetValues) {int lineSpacing = (int) (mTextWidthHeight.y * mLineSpacingMult);int len = targetValues.length + 1;int[] intValues = new int[len];//intValues[0] = 0;for (int i = 1; i < len; i++) {intValues[i] = (currentValue / mPower - targetValues[i - 1] / mPower) * lineSpacing;}mValueAnimator.setIntValues(intValues);}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int curAnimatedValue = (int) animation.getAnimatedValue();int diff = curAnimatedValue - mLastAnimatedValue;scrollYBy(diff);mLastAnimatedValue = curAnimatedValue;}@Overridepublic void onAnimationStart(Animator animation) {mScrollingY = mLocationY;mScrollYAccumulation = 0;mLastAnimatedValue = 0;}@Overridepublic void onAnimationEnd(Animator animation) {}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}}}

attrs.xml ScrollingNumbersView 可配置的属性

<resources><!-- Default text typeface style. --><attr name="textStyle"><flag name="normal" value="0" /><flag name="bold" value="1" /><flag name="italic" value="2" /></attr><attr name="alignment"><enum name="center" value="0" /><enum name="left" value="1" /><enum name="right" value="2" /></attr><declare-styleable name="ScrollingNumbersView"><attr name="maxDigits" format="integer" /><attr name="initialValue" format="integer" /><attr name="textColor" format="color" /><attr name="textSize" format="dimension" /><attr name="textStyle" /><attr name="subTextSize" format="dimension" /><attr name="subText" format="string" /><attr name="adjustSubTextThreshold" format="integer" /><attr name="alignment" /><attr name="lineSpacingMultiplier" format="float" /><attr name="singleNumberWidthMultiplier" format="float" /></declare-styleable></resources>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。