亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android使用屬性動畫如何自定義倒計時控件詳解

 更新時間:2018年05月18日 10:14:52   作者:亂世白衣  
自Android 3.0版本開始,系統(tǒng)給我們提供了一種全新的動畫模式,屬性動畫(property animation),它的功能非常強(qiáng)大,下面這篇文章主要給大家介紹了關(guān)于Android使用屬性動畫如何自定義倒計時控件的相關(guān)資料,需要的朋友可以參考下

為什么要引入屬性動畫?

Android之前的補(bǔ)間動畫機(jī)制其實(shí)還算是比較健全的,在android.view.animation包下面有好多的類可以供我們操作,來完成一系列的動畫效果,比如說對View進(jìn)行移動、縮放、旋轉(zhuǎn)和淡入淡出,并且我們還可以借助AnimationSet來將這些動畫效果組合起來使用,除此之外還可以通過配置Interpolator來控制動畫的播放速度等等等等。那么這里大家可能要產(chǎn)生疑問了,既然之前的動畫機(jī)制已經(jīng)這么健全了,為什么還要引入屬性動畫呢?

其實(shí)上面所謂的健全都是相對的,如果你的需求中只需要對View進(jìn)行移動、縮放、旋轉(zhuǎn)和淡入淡出操作,那么補(bǔ)間動畫確實(shí)已經(jīng)足夠健全了。但是很顯然,這些功能是不足以覆蓋所有的場景的,一旦我們的需求超出了移動、縮放、旋轉(zhuǎn)和淡入淡出這四種對View的操作,那么補(bǔ)間動畫就不能再幫我們忙了,也就是說它在功能和可擴(kuò)展方面都有相當(dāng)大的局限性,那么下面我們就來看看補(bǔ)間動畫所不能勝任的場景。

注意上面我在介紹補(bǔ)間動畫的時候都有使用“對View進(jìn)行操作”這樣的描述,沒錯,補(bǔ)間動畫是只能夠作用在View上的。也就是說,我們可以對一個Button、TextView、甚至是LinearLayout、或者其它任何繼承自View的組件進(jìn)行動畫操作,但是如果我們想要對一個非View的對象進(jìn)行動畫操作,抱歉,補(bǔ)間動畫就幫不上忙了??赡苡械呐笥褧械讲荒芾斫?,我怎么會需要對一個非View的對象進(jìn)行動畫操作呢?這里我舉一個簡單的例子,比如說我們有一個自定義的View,在這個View當(dāng)中有一個Point對象用于管理坐標(biāo),然后在onDraw()方法當(dāng)中就是根據(jù)這個Point對象的坐標(biāo)值來進(jìn)行繪制的。也就是說,如果我們可以對Point對象進(jìn)行動畫操作,那么整個自定義View的動畫效果就有了。顯然,補(bǔ)間動畫是不具備這個功能的,這是它的第一個缺陷。

然后補(bǔ)間動畫還有一個缺陷,就是它只能夠?qū)崿F(xiàn)移動、縮放、旋轉(zhuǎn)和淡入淡出這四種動畫操作,那如果我們希望可以對View的背景色進(jìn)行動態(tài)地改變呢?很遺憾,我們只能靠自己去實(shí)現(xiàn)了。說白了,之前的補(bǔ)間動畫機(jī)制就是使用硬編碼的方式來完成的,功能限定死就是這些,基本上沒有任何擴(kuò)展性可言。

最后,補(bǔ)間動畫還有一個致命的缺陷,就是它只是改變了View的顯示效果而已,而不會真正去改變View的屬性。什么意思呢?比如說,現(xiàn)在屏幕的左上角有一個按鈕,然后我們通過補(bǔ)間動畫將它移動到了屏幕的右下角,現(xiàn)在你可以去嘗試點(diǎn)擊一下這個按鈕,點(diǎn)擊事件是絕對不會觸發(fā)的,因?yàn)閷?shí)際上這個按鈕還是停留在屏幕的左上角,只不過補(bǔ)間動畫將這個按鈕繪制到了屏幕的右下角而已。

也正是因?yàn)檫@些原因,Android開發(fā)團(tuán)隊決定在3.0版本當(dāng)中引入屬性動畫這個功能,那么屬性動畫是不是就把上述的問題全部解決掉了?下面我們就來一起看一看。

新引入的屬性動畫機(jī)制已經(jīng)不再是針對于View來設(shè)計的了,也不限定于只能實(shí)現(xiàn)移動、縮放、旋轉(zhuǎn)和淡入淡出這幾種動畫操作,同時也不再只是一種視覺上的動畫效果了。它實(shí)際上是一種不斷地對值進(jìn)行操作的機(jī)制,并將值賦值到指定對象的指定屬性上,可以是任意對象的任意屬性。所以我們?nèi)匀豢梢詫⒁粋€View進(jìn)行移動或者縮放,但同時也可以對自定義View中的Point對象進(jìn)行動畫操作了。我們只需要告訴系統(tǒng)動畫的運(yùn)行時長,需要執(zhí)行哪種類型的動畫,以及動畫的初始值和結(jié)束值,剩下的工作就可以全部交給系統(tǒng)去完成了。

既然屬性動畫的實(shí)現(xiàn)機(jī)制是通過對目標(biāo)對象進(jìn)行賦值并修改其屬性來實(shí)現(xiàn)的,那么之前所說的按鈕顯示的問題也就不復(fù)存在了,如果我們通過屬性動畫來移動一個按鈕,那么這個按鈕就是真正的移動了,而不再是僅僅在另外一個位置繪制了而已。

好了,介紹了這么多,相信大家已經(jīng)對屬性動畫有了一個最基本的認(rèn)識了,下面來一看看詳細(xì)的介紹吧

引言

本文介紹一下利用屬性動畫(未使用Timer,通過動畫執(zhí)行次數(shù)控制倒計時)自定義一個圓形倒計時控件,比較簡陋,僅做示例使用,如有需要,您可自行修改以滿足您的需求??丶兴褂玫乃夭募芭渖枪P者隨意選擇,導(dǎo)致效果不佳,先上示例圖片


示例中進(jìn)度條底色、漸變色(僅支持兩個色值)、字體大小、圖片、進(jìn)度條寬度及是否顯示進(jìn)度條等可通過xml修改,倒計時時間可通過代碼設(shè)置。如果您感興趣,可修改代碼設(shè)置更豐富的漸變色值及文字變化效果,本文僅僅提供設(shè)計思路。

筆者利用屬性動畫多次執(zhí)行實(shí)現(xiàn)倒計時,執(zhí)行次數(shù)即為倒計時初始數(shù)值。對上述示例做一下拆解,會發(fā)現(xiàn)實(shí)現(xiàn)起來還是很容易的,需要處理的主要是以下幾部分

1.繪制外部環(huán)形進(jìn)度條

2.繪制中央旋轉(zhuǎn)圖片

3.繪制倒計時時間

一.繪制外部環(huán)形進(jìn)度條,分為兩部分:

1.環(huán)形背景 canvas.drawCircle方法繪制

2.扇形進(jìn)度 canvas.drawArc方法繪制,弧度通過整體倒計時執(zhí)行進(jìn)度控制

二.繪制中央旋轉(zhuǎn)圖片:

前置描述:外層圓形直徑設(shè)為d1;中央旋轉(zhuǎn)圖片直徑設(shè)為d2;進(jìn)度條寬度設(shè)為d3

1.將設(shè)置的圖片進(jìn)行剪切縮放處理(也可不剪切,筆者有強(qiáng)迫癥),使其寬高等于d1 - 2 * d3,即d2 = d1 - 2 * d3;

2.利用Matrix將Bitmap平移至中央;

3.利用Matrix旋轉(zhuǎn)Bitmap

三.繪制倒計時時間:

通過每次動畫執(zhí)行進(jìn)度,控制文本位置

下面上示例代碼:

public class CircleCountDownView extends View {
 private CountDownListener countDownListener;

 private int width;
 private int height;
 private int padding;
 private int borderWidth;
 // 根據(jù)動畫執(zhí)行進(jìn)度計算出來的插值,用來控制動畫效果,建議取值范圍為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);
 // 控制外層進(jìn)度條的邊距
 padding = typedArray.getDimensionPixelSize(R.styleable.CircleCountDownView_padding, DisplayUtil.dp2px(5));
 // 進(jìn)度條邊線寬度
 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);
  // 進(jìn)度條漸變色值
  processColorStart = typedArray.getColor(R.styleable.CircleCountDownView_processColorStart, Color.parseColor("#00ffff"));
  processColorEnd = typedArray.getColor(R.styleable.CircleCountDownView_processColorEnd, Color.parseColor("#35adc6"));
  // 進(jìn)度條高斯模糊半徑
  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) {
   // 監(jiān)聽剩余時間
   long restTime = (long) ((currentCountDownValue - animation.getAnimatedFraction()) * 1000);
   countDownListener.restTime(restTime);
  }
  // 整體倒計時進(jìn)度
  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();
  }
  }
 });
 }

 // 設(shè)置倒計時初始時間
 public void setStartCountValue(int initialCountDownValue) {
 this.initialCountDownValue = initialCountDownValue;
 this.currentCountDownValue = initialCountDownValue;
 // 設(shè)置重復(fù)執(zhí)行次數(shù),共執(zhí)行initialCountDownValue次,恰好為倒計時總數(shù)
 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();
 // 圓形圖片繪制區(qū)域半徑
 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) {
  // 外層進(jìn)度條寬度(注意:需要減掉畫筆寬度)
  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) {
  // 進(jìn)度條漸變及邊緣高斯模糊處理
  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)); // 設(shè)置進(jìn)度條陰影效果
  }
 }
 }

 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) { 
  // 繪制外層圓環(huán)
  canvas.drawCircle(centerX, centerY, Math.min(width, height) / 2 - borderWidth / 2 - padding, circleBorderPaint);
  if (showProgress) {
  // 繪制整體進(jìn)度
  canvas.drawArc(circleProgressRectF, 0, 360 * totalTimeProgress, false, circleProcessPaint);
  }

 }
 // 設(shè)置圖片旋轉(zhuǎn)角度增量
 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();
 // 文字繪制基準(zhǔn)線(圓形區(qū)域正中央)
 float verticalBaseline = (height - fontMetrics.bottom - fontMetrics.top) / 2;
 // 隨動畫執(zhí)行進(jìn)度而更新的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 {
 /**
  * 倒計時結(jié)束
  */
 void onCountDownFinish();

 /**
  * 倒計時剩余時間
  *
  * @param restTime 剩余時間,單位毫秒
  */
 void restTime(long restTime);
 }

 public interface AnimationInterpolator {
 /**
  * @param inputFraction 動畫執(zhí)行時間因子,取值范圍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 (本地下載)

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

最新評論