您好,登錄后才能下訂單哦!
這篇文章主要介紹Android使用屬性動畫怎么自定義倒計時控件,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
為什么要引入屬性動畫?
Android之前的補間動畫機制其實還算是比較健全的,在android.view.animation包下面有好多的類可以供我們操作,來完成一系列的動畫效果,比如說對View進行移動、縮放、旋轉和淡入淡出,并且我們還可以借助AnimationSet來將這些動畫效果組合起來使用,除此之外還可以通過配置Interpolator來控制動畫的播放速度等等等等。那么這里大家可能要產生疑問了,既然之前的動畫機制已經這么健全了,為什么還要引入屬性動畫呢?
其實上面所謂的健全都是相對的,如果你的需求中只需要對View進行移動、縮放、旋轉和淡入淡出操作,那么補間動畫確實已經足夠健全了。但是很顯然,這些功能是不足以覆蓋所有的場景的,一旦我們的需求超出了移動、縮放、旋轉和淡入淡出這四種對View的操作,那么補間動畫就不能再幫我們忙了,也就是說它在功能和可擴展方面都有相當大的局限性,那么下面我們就來看看補間動畫所不能勝任的場景。
注意上面我在介紹補間動畫的時候都有使用“對View進行操作”這樣的描述,沒錯,補間動畫是只能夠作用在View上的。也就是說,我們可以對一個Button、TextView、甚至是LinearLayout、或者其它任何繼承自View的組件進行動畫操作,但是如果我們想要對一個非View的對象進行動畫操作,抱歉,補間動畫就幫不上忙了。可能有的朋友會感到不能理解,我怎么會需要對一個非View的對象進行動畫操作呢?這里我舉一個簡單的例子,比如說我們有一個自定義的View,在這個View當中有一個Point對象用于管理坐標,然后在onDraw()方法當中就是根據這個Point對象的坐標值來進行繪制的。也就是說,如果我們可以對Point對象進行動畫操作,那么整個自定義View的動畫效果就有了。顯然,補間動畫是不具備這個功能的,這是它的第一個缺陷。
然后補間動畫還有一個缺陷,就是它只能夠實現移動、縮放、旋轉和淡入淡出這四種動畫操作,那如果我們希望可以對View的背景色進行動態地改變呢?很遺憾,我們只能靠自己去實現了。說白了,之前的補間動畫機制就是使用硬編碼的方式來完成的,功能限定死就是這些,基本上沒有任何擴展性可言。
最后,補間動畫還有一個致命的缺陷,就是它只是改變了View的顯示效果而已,而不會真正去改變View的屬性。什么意思呢?比如說,現在屏幕的左上角有一個按鈕,然后我們通過補間動畫將它移動到了屏幕的右下角,現在你可以去嘗試點擊一下這個按鈕,點擊事件是絕對不會觸發的,因為實際上這個按鈕還是停留在屏幕的左上角,只不過補間動畫將這個按鈕繪制到了屏幕的右下角而已。
也正是因為這些原因,Android開發團隊決定在3.0版本當中引入屬性動畫這個功能,那么屬性動畫是不是就把上述的問題全部解決掉了?下面我們就來一起看一看。
新引入的屬性動畫機制已經不再是針對于View來設計的了,也不限定于只能實現移動、縮放、旋轉和淡入淡出這幾種動畫操作,同時也不再只是一種視覺上的動畫效果了。它實際上是一種不斷地對值進行操作的機制,并將值賦值到指定對象的指定屬性上,可以是任意對象的任意屬性。所以我們仍然可以將一個View進行移動或者縮放,但同時也可以對自定義View中的Point對象進行動畫操作了。我們只需要告訴系統動畫的運行時長,需要執行哪種類型的動畫,以及動畫的初始值和結束值,剩下的工作就可以全部交給系統去完成了。
既然屬性動畫的實現機制是通過對目標對象進行賦值并修改其屬性來實現的,那么之前所說的按鈕顯示的問題也就不復存在了,如果我們通過屬性動畫來移動一個按鈕,那么這個按鈕就是真正的移動了,而不再是僅僅在另外一個位置繪制了而已。
好了,介紹了這么多,相信大家已經對屬性動畫有了一個最基本的認識了,下面來一看看詳細的介紹吧
引言
本文介紹一下利用屬性動畫(未使用Timer,通過動畫執行次數控制倒計時)自定義一個圓形倒計時控件,比較簡陋,僅做示例使用,如有需要,您可自行修改以滿足您的需求。控件中所使用的素材及配色均是筆者隨意選擇,導致效果不佳,先上示例圖片
示例中進度條底色、漸變色(僅支持兩個色值)、字體大小、圖片、進度條寬度及是否顯示進度條等可通過xml修改,倒計時時間可通過代碼設置。如果您感興趣,可修改代碼設置更豐富的漸變色值及文字變化效果,本文僅僅提供設計思路。
筆者利用屬性動畫多次執行實現倒計時,執行次數即為倒計時初始數值。對上述示例做一下拆解,會發現實現起來還是很容易的,需要處理的主要是以下幾部分
1.繪制外部環形進度條
2.繪制中央旋轉圖片
3.繪制倒計時時間
一.繪制外部環形進度條,分為兩部分:
1.環形背景 canvas.drawCircle方法繪制
2.扇形進度 canvas.drawArc方法繪制,弧度通過整體倒計時執行進度控制
二.繪制中央旋轉圖片:
前置描述:外層圓形直徑設為d1;中央旋轉圖片直徑設為d2;進度條寬度設為d3
1.將設置的圖片進行剪切縮放處理(也可不剪切,筆者有強迫癥),使其寬高等于d1 - 2 * d3,即d2 = d1 - 2 * d3;
2.利用Matrix將Bitmap平移至中央;
3.利用Matrix旋轉Bitmap
三.繪制倒計時時間:
通過每次動畫執行進度,控制文本位置
下面上示例代碼:
public class CircleCountDownView extends View { private CountDownListener countDownListener; private int width; private int height; private int padding; private int borderWidth; // 根據動畫執行進度計算出來的插值,用來控制動畫效果,建議取值范圍為0到1 private float currentAnimationInterpolation; private boolean showProgress; private float totalTimeProgress; private int processColorStart; private int processColorEnd; private int processBlurMaskRadius; private int initialCountDownValue; private int currentCountDownValue; private Paint circleBorderPaint; private Paint circleProcessPaint; private RectF circleProgressRectF; private Paint circleImgPaint; private Matrix circleImgMatrix; private Bitmap circleImgBitmap; private int circleImgRadius; private AnimationInterpolator animationInterpolator; private BitmapShader circleImgBitmapShader; private float circleImgTranslationX; private float circleImgTranslationY; private Paint valueTextPaint; private ValueAnimator countDownAnimator; public CircleCountDownView(Context context) { this(context, null); } public CircleCountDownView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CircleCountDownView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setLayerType(View.LAYER_TYPE_SOFTWARE, null); init(attrs); } private void init(AttributeSet attrs) { circleImgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); circleImgPaint.setStyle(Paint.Style.FILL); circleImgMatrix = new Matrix(); valueTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleCountDownView); // 控制外層進度條的邊距 padding = typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_padding, DisplayUtil.dp2px(5)); // 進度條邊線寬度 borderWidth = typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_circleBorderWidth, 0); if (borderWidth > 0) { circleBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); circleBorderPaint.setStyle(Paint.Style.STROKE); circleBorderPaint.setStrokeWidth(borderWidth); circleBorderPaint.setColor(typedArray.getColor(R.styleable.CircleCountDownView_circleBorderColor, Color.WHITE)); showProgress = typedArray.getBoolean(R.styleable.CircleCountDownView_showProgress, false); if (showProgress) { circleProcessPaint = new Paint(Paint.ANTI_ALIAS_FLAG); circleProcessPaint.setStyle(Paint.Style.STROKE); circleProcessPaint.setStrokeWidth(borderWidth); // 進度條漸變色值 processColorStart = typedArray.getColor(R.styleable.CircleCountDownView_processColorStart, Color.parseColor("#00ffff")); processColorEnd = typedArray.getColor(R.styleable.CircleCountDownView_processColorEnd, Color.parseColor("#35adc6")); // 進度條高斯模糊半徑 processBlurMaskRadius = typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_processBlurMaskRadius, DisplayUtil.dp2px(5)); } } int circleImgSrc = typedArray.getResourceId(R.styleable.CircleCountDownView_circleImgSrc, R.mipmap.ic_radar); // 圖片剪裁成正方形 circleImgBitmap = ImageUtil.cropSquareBitmap(BitmapFactory.decodeResource(getResources(), circleImgSrc)); valueTextPaint.setColor(typedArray.getColor(R.styleable.CircleCountDownView_valueTextColor, Color.WHITE)); valueTextPaint.setTextSize(typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_valueTextSize, DisplayUtil.dp2px(13))); typedArray.recycle(); // 初始化屬性動畫,周期為1秒 countDownAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000); countDownAnimator.setInterpolator(new LinearInterpolator()); countDownAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (countDownListener != null) { // 監聽剩余時間 long restTime = (long) ((currentCountDownValue - animation.getAnimatedFraction()) * 1000); countDownListener.restTime(restTime); } // 整體倒計時進度 totalTimeProgress = (initialCountDownValue - currentCountDownValue + animation.getAnimatedFraction()) / initialCountDownValue; if (animationInterpolator != null) { currentAnimationInterpolation = animationInterpolator.getInterpolation(animation.getAnimatedFraction()); } else { currentAnimationInterpolation = animation.getAnimatedFraction(); currentAnimationInterpolation *= currentAnimationInterpolation; } invalidate(); } }); countDownAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationRepeat(Animator animation) { currentCountDownValue--; } @Override public void onAnimationEnd(Animator animation) { if (countDownListener != null) { countDownListener.onCountDownFinish(); } } }); } // 設置倒計時初始時間 public void setStartCountValue(int initialCountDownValue) { this.initialCountDownValue = initialCountDownValue; this.currentCountDownValue = initialCountDownValue; // 設置重復執行次數,共執行initialCountDownValue次,恰好為倒計時總數 countDownAnimator.setRepeatCount(currentCountDownValue - 1); invalidate(); } public void setAnimationInterpolator(AnimationInterpolator animationInterpolator) { if (!countDownAnimator.isRunning()) { this.animationInterpolator = animationInterpolator; } } // 重置 public void reset() { countDownAnimator.cancel(); lastAnimationInterpolation = 0; totalTimeProgress = 0; currentAnimationInterpolation = 0; currentCountDownValue = initialCountDownValue; circleImgMatrix.setTranslate(circleImgTranslationX, circleImgTranslationY); circleImgMatrix.postRotate(0, width / 2, height / 2); invalidate(); } public void restart() { reset(); startCountDown(); } public void pause() { countDownAnimator.pause(); } public void setCountDownListener(CountDownListener countDownListener) { this.countDownListener = countDownListener; } // 啟動倒計時 public void startCountDown() { if (countDownAnimator.isPaused()) { countDownAnimator.resume(); return; } if (currentCountDownValue > 0) { countDownAnimator.start(); } else if (countDownListener != null) { countDownListener.onCountDownFinish(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = getMeasuredWidth(); height = getMeasuredHeight(); if (width > 0 && height > 0) { doCalculate(); } } private void doCalculate() { circleImgMatrix.reset(); // 圓形圖片繪制區域半徑 circleImgRadius = (Math.min(width, height) - 2 * borderWidth - 2 * padding) / 2; float actualCircleImgBitmapWH = circleImgBitmap.getWidth(); float circleDrawingScale = circleImgRadius * 2 / actualCircleImgBitmapWH; // bitmap縮放處理 Matrix matrix = new Matrix(); matrix.setScale(circleDrawingScale, circleDrawingScale, actualCircleImgBitmapWH / 2, actualCircleImgBitmapWH / 2); circleImgBitmap = Bitmap.createBitmap(circleImgBitmap, 0, 0, circleImgBitmap.getWidth(), circleImgBitmap.getHeight(), matrix, true); // 繪制圓形圖片使用 circleImgBitmapShader = new BitmapShader(circleImgBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); // 平移至中心 circleImgTranslationX = (width - circleImgRadius * 2) / 2; circleImgTranslationY = (height - circleImgRadius * 2) / 2; circleImgMatrix.setTranslate(circleImgTranslationX, circleImgTranslationY); if (borderWidth > 0) { // 外層進度條寬度(注意:需要減掉畫筆寬度) float circleProgressWH = Math.min(width, height) - borderWidth - 2 * padding; float left = (width > height ? (width - height) / 2 : 0) + borderWidth / 2 + padding; float top = (height > width ? (height - width) / 2 : 0) + borderWidth / 2 + padding; float right = left + circleProgressWH; float bottom = top + circleProgressWH; circleProgressRectF = new RectF(left, top, right, bottom); if (showProgress) { // 進度條漸變及邊緣高斯模糊處理 circleProcessPaint.setShader(new LinearGradient(left, top, left + circleImgRadius * 2, top + circleImgRadius * 2, processColorStart, processColorEnd, Shader.TileMode.MIRROR)); circleProcessPaint.setMaskFilter(new BlurMaskFilter(processBlurMaskRadius, BlurMaskFilter.Blur.SOLID)); // 設置進度條陰影效果 } } } private float lastAnimationInterpolation; @Override protected void onDraw(Canvas canvas) { if (width == 0 || height == 0) { return; } int centerX = width / 2; int centerY = height / 2; if (borderWidth > 0) { // 繪制外層圓環 canvas.drawCircle(centerX, centerY, Math.min(width, height) / 2 - borderWidth / 2 - padding, circleBorderPaint); if (showProgress) { // 繪制整體進度 canvas.drawArc(circleProgressRectF, 0, 360 * totalTimeProgress, false, circleProcessPaint); } } // 設置圖片旋轉角度增量 circleImgMatrix.postRotate((currentAnimationInterpolation - lastAnimationInterpolation) * 360, centerX, centerY); circleImgBitmapShader.setLocalMatrix(circleImgMatrix); circleImgPaint.setShader(circleImgBitmapShader); canvas.drawCircle(centerX, centerY, circleImgRadius, circleImgPaint); lastAnimationInterpolation = currentAnimationInterpolation; // 繪制倒計時時間 // current String currentTimePoint = currentCountDownValue + "s"; float textWidth = valueTextPaint.measureText(currentTimePoint); float x = centerX - textWidth / 2; Paint.FontMetrics fontMetrics = valueTextPaint.getFontMetrics(); // 文字繪制基準線(圓形區域正中央) float verticalBaseline = (height - fontMetrics.bottom - fontMetrics.top) / 2; // 隨動畫執行進度而更新的y軸位置 float y = verticalBaseline - currentAnimationInterpolation * (Math.min(width, height) / 2); valueTextPaint.setAlpha((int) (255 - currentAnimationInterpolation * 255)); canvas.drawText(currentTimePoint, x, y, valueTextPaint); // next String nextTimePoint = (currentCountDownValue - 1) + "s"; textWidth = valueTextPaint.measureText(nextTimePoint); x = centerX - textWidth / 2; y = y + (Math.min(width, height)) / 2; valueTextPaint.setAlpha((int) (currentAnimationInterpolation * 255)); canvas.drawText(nextTimePoint, x, y, valueTextPaint); } public interface CountDownListener { /** * 倒計時結束 */ void onCountDownFinish(); /** * 倒計時剩余時間 * * @param restTime 剩余時間,單位毫秒 */ void restTime(long restTime); } public interface AnimationInterpolator { /** * @param inputFraction 動畫執行時間因子,取值范圍0到1 */ float getInterpolation(float inputFraction); } }
自定義屬性如下
<declare-styleable name="CircleCountDownView"> <!--控件中間圖片資源--> <attr name="circleImgSrc" format="reference" /> <attr name="circleBorderColor" format="color" /> <attr name="circleBorderWidth" format="dimension" /> <attr name="valueTextSize" format="dimension" /> <attr name="valueTextColor" format="color" /> <attr name="padding" format="dimension" /> <attr name="showProgress" format="boolean" /> <attr name="processColorStart" format="color" /> <attr name="processColorEnd" format="color" /> <attr name="processBlurMaskRadius" format="dimension" /> </declare-styleable>
代碼比較簡單,如有疑問歡迎留言
完整代碼:https://github.com/670832188/TestApp (本地下載)
以上是“Android使用屬性動畫怎么自定義倒計時控件”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。