欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android仿餓了么加入購(gòu)物車(chē)旋轉(zhuǎn)控件自帶閃轉(zhuǎn)騰挪動(dòng)畫(huà)的按鈕效果(實(shí)例詳解)

 更新時(shí)間:2017年01月09日 09:05:11   作者:張旭童  
這篇文章主要介紹了Android仿餓了么加入購(gòu)物車(chē)旋轉(zhuǎn)控件自帶閃轉(zhuǎn)騰挪動(dòng)畫(huà)的按鈕效果(實(shí)例詳解)的相關(guān)資料,需要的朋友可以參考下

概述

在上文,酷炫Path動(dòng)畫(huà)已經(jīng)預(yù)告了,今天給大家?guī)?lái)的是利用 純自定義View,實(shí)現(xiàn)的仿餓了么加入購(gòu)物車(chē)控件,自帶閃轉(zhuǎn)騰挪動(dòng)畫(huà)的按鈕。

效果圖如下:

圖1 項(xiàng)目中使用的效果,考慮到了View的回收復(fù)用,

并且可以看到在RecyclerView中使用,切換LayoutManager也是沒(méi)有問(wèn)題的,

項(xiàng)目中使用的效果

圖2 Demo效果,測(cè)試各種屬性值

圖2 Demo效果,測(cè)試各種屬性值

注意,本控件非繼承自ViewGroup,而是純自定義View實(shí)現(xiàn)。理由如下:

  1. 1 減少布局層級(jí),從而提高性能
  2. 2 文字和圖形純draw,用到什么draw什么,沒(méi)有其他的額外工作,也間接提高性能。
  3. 3 純自定義View難度更高,更有實(shí)(裝)踐(B)的意義

1 減少布局層次,很好理解,ViewGroup內(nèi)嵌套幾個(gè)TextView、ImageV這里寫(xiě)代碼片iew也可以實(shí)現(xiàn)這個(gè)效果,然而這會(huì)使布局層次多了一級(jí),并且內(nèi)部要嵌套多個(gè)控件,層級(jí)越多,控件越多,繪制的就越慢,在列表中對(duì)性能的影響更大。

2 別小看了“小小”的TextView和的ImageView,其實(shí)它們有很多的屬性和特性在本例中是不必要的,舉個(gè)例子,查看源碼,TextView有一萬(wàn)多行,ondraw()方法有一百多行, ImageView有1588行,這么多行代碼都是我們需要的嗎?直接使用這些現(xiàn)成的控件嵌套實(shí)現(xiàn),其實(shí)性能不如我們用到什么draw什么。唯一的好處可能就是比較簡(jiǎn)單了。(其實(shí)TextView的性能是不高的)

3 純自定義View,draw出這些需要的元素,并且還要考慮動(dòng)畫(huà),以及點(diǎn)擊各區(qū)域的監(jiān)聽(tīng),實(shí)現(xiàn)起來(lái)還是有一些難度的,但我們多寫(xiě)一些有難度的代碼才能提高水平。

如何使用

伸手黨福利:講解實(shí)現(xiàn)前,先看一下如何使用 以及支持的屬性等。

使用

xml:

 <!--使用默認(rèn)UI屬性-->
 <com.mcxtzhang.lib.AnimShopButton
 android:id="@+id/btn1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 app:maxCount="3"/>
 <!--設(shè)置了兩圓間距-->
 <com.mcxtzhang.lib.AnimShopButton
 android:id="@+id/btn2"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 app:count="3"
 app:gapBetweenCircle="90dp"
 app:maxCount="99"/>
 <!--仿餓了么-->
 <com.mcxtzhang.lib.AnimShopButton
 android:id="@+id/btnEle"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 app:addEnableBgColor="#3190E8"
 app:addEnableFgColor="#ffffff"
 app:hintBgColor="#3190E8"
 app:hintBgRoundValue="15dp"
 app:hintFgColor="#ffffff"
 app:maxCount="99"/>

注意:

加減點(diǎn)擊后,具體的操作,要根據(jù)業(yè)務(wù)的不同來(lái)編寫(xiě)了,設(shè)計(jì)到實(shí)際的購(gòu)物車(chē)可能還有寫(xiě)數(shù)據(jù)庫(kù)操作,或者請(qǐng)求接口等,要操作成功后才執(zhí)行動(dòng)畫(huà)、或者修改count,這一塊代碼每個(gè)人寫(xiě)法可能不同。
使用時(shí),可以重寫(xiě)onDelClick()和onAddClick()方法,并在合適的時(shí)機(jī)回調(diào)onCountAddSuccess()和onCountDelSuccess()以執(zhí)行動(dòng)畫(huà)。

效果圖如圖2.

支持的屬性

name format description 中文解釋
isAddFillMode boolean Plus button is opened Fill mode default is stroke (false) 加按鈕是否開(kāi)啟fill模式 默認(rèn)是stroke(false)
addEnableBgColor color The background color of the plus button 加按鈕的背景色
addEnableFgColor color The foreground color of the plus button 加按鈕的前景色
addDisableBgColor color The background color when the button is not available 加按鈕不可用時(shí)的背景色
addDisableFgColor color The foreground color when the button is not available 加按鈕不可用時(shí)的前景色
isDelFillMode boolean Plus button is opened Fill mode default is stroke (false) 減按鈕是否開(kāi)啟fill模式 默認(rèn)是stroke(false)
delEnableBgColor color The background color of the minus button 減按鈕的背景色
delEnableFgColor color The foreground color of the minus button 減按鈕的前景色
delDisableBgColor color The background color when the button is not available 減按鈕不可用時(shí)的背景色
delDisableFgColor color The foreground color when the button is not available 減按鈕不可用時(shí)的前景色
radius dimension The radius of the circle 圓的半徑
circleStrokeWidth dimension The width of the circle 圓圈的寬度
lineWidth dimension The width of the line (+ - sign) 線(xiàn)(+ - 符號(hào))的寬度
gapBetweenCircle dimension The spacing between two circles 兩個(gè)圓之間的間距
numTextSize dimension The textSize of draws the number 繪制數(shù)量的textSize
maxCount integer max count 最大數(shù)量
count integer current count 當(dāng)前數(shù)量
hintText string The hint text when number is 0 數(shù)量為0時(shí),hint文字
hintBgColor color The hint background when number is 0 數(shù)量為0時(shí),hint背景色
hintFgColor color The hint foreground when number is 0 數(shù)量為0時(shí),hint前景色
hingTextSize dimension The hint text size when number is 0 數(shù)量為0時(shí),hint文字大小
hintBgRoundValue dimension The background fillet value when number is 0 數(shù)量為0時(shí),hint背景圓角值

這么多屬性夠你用了吧。

下面看重點(diǎn)的實(shí)現(xiàn)吧,Let's Go!.

實(shí)現(xiàn)解剖

關(guān)于自定義View的基礎(chǔ),這里不再贅述。

如果閱讀時(shí)有不明白的,建議下載源碼邊看邊讀,或者學(xué)習(xí)自定義View基礎(chǔ)知識(shí)后再閱讀本文。

代碼傳送門(mén):喜歡的話(huà),隨手點(diǎn)個(gè)star。多謝

https://github.com/mcxtzhang/AnimShopButton

我們撿重點(diǎn)說(shuō),無(wú)非是繪制。

繪制的重點(diǎn),這里分三塊:

  1. 靜態(tài)繪制。(分兩塊:加減按鈕和數(shù)量、hint提示文字和背景)
  2. 第一層。(加減按鈕和數(shù)量)以及它的旋轉(zhuǎn)、位移、透明度動(dòng)畫(huà)
  3. 第二層。(hint區(qū)域)以及它的伸展收縮動(dòng)畫(huà)

除了繪制以外的重點(diǎn)是:

  1. 由于采用了完全的自定義View去實(shí)現(xiàn)這么一個(gè)“組合控件效果”,則點(diǎn)擊事件的監(jiān)聽(tīng)需要自己處理。
  2. 在回收復(fù)用的列表中使用時(shí),列表滑動(dòng),如何正確顯示UI。

靜態(tài)繪制

靜態(tài)繪制就是最基本的自定義View知識(shí),繪制圓圈(Circle)、線(xiàn)段(Line)、數(shù)字(Text)以及圓角矩形(RoundRect),值得注意的是,

要考慮到 避免overDraw和動(dòng)畫(huà)的需求,

我們要繪制的兩層應(yīng)該是互斥關(guān)系。

剝離掉動(dòng)畫(huà)代碼,大致如下(基本都是draw代碼,可以快速閱讀):

@Override
 protected void onDraw(Canvas canvas) {
 if (isHintMode) {
  //hint 展開(kāi)
  //背景
  mHintPaint.setColor(mHintBgColor);
  RectF rectF = new RectF(mLeft, mTop
   , mWidth - mCircleWidth, mHeight - mCircleWidth);
  canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint);
  //前景文字
  mHintPaint.setColor(mHintFgColor);
  // 計(jì)算Baseline繪制的起點(diǎn)X軸坐標(biāo)
  int baseX = (int) (mWidth / 2 - mHintPaint.measureText(mHintText) / 2);
  // 計(jì)算Baseline繪制的Y坐標(biāo)
  int baseY = (int) ((mHeight / 2) - ((mHintPaint.descent() + mHintPaint.ascent()) / 2));
  canvas.drawText(mHintText, baseX, baseY, mHintPaint);
 } else {
  //左邊
  //背景 圓
  if (mCount > 0) {
  mDelPaint.setColor(mDelEnableBgColor);
  } else {
  mDelPaint.setColor(mDelDisableBgColor);
  }
  mDelPaint.setStrokeWidth(mCircleWidth);
  mDelPath.reset();
  mDelPath.addCircle(mLeft + mRadius, mTop + mRadius, mRadius, Path.Direction.CW);
  mDelRegion.setPath(mDelPath, new Region(mLeft, mTop, mWidth - getPaddingRight(), mHeight - getPaddingBottom()));
  canvas.drawPath(mDelPath, mDelPaint);
  //前景 -
  if (mCount > 0) {
  mDelPaint.setColor(mDelEnableFgColor);
  } else {
  mDelPaint.setColor(mDelDisableFgColor);
  }
  mDelPaint.setStrokeWidth(mLineWidth);
  canvas.drawLine(-mRadius / 2, 0,
   +mRadius / 2, 0,
   mDelPaint);
  //數(shù)量
  //是沒(méi)有動(dòng)畫(huà)的普通寫(xiě)法,x left, y baseLine
  canvas.drawText(mCount + "", mLeft + mRadius * 2, mTop + mRadius - (mFontMetrics.top + mFontMetrics.bottom) / 2, mTextPaint);
  //右邊
  //背景 圓
  if (mCount < mMaxCount) {
  mAddPaint.setColor(mAddEnableBgColor);
  } else {
  mAddPaint.setColor(mAddDisableBgColor);
  }
  mAddPaint.setStrokeWidth(mCircleWidth);
  float left = mLeft + mRadius * 2 + mGapBetweenCircle;
  mAddPath.reset();
  mAddPath.addCircle(left + mRadius, mTop + mRadius, mRadius, Path.Direction.CW);
  mAddRegion.setPath(mAddPath, new Region(mLeft, mTop, mWidth - getPaddingRight(), mHeight - getPaddingBottom()));
  canvas.drawPath(mAddPath, mAddPaint);
  //前景 +
  if (mCount < mMaxCount) {
  mAddPaint.setColor(mAddEnableFgColor);
  } else {
  mAddPaint.setColor(mAddDisableFgColor);
  }
  mAddPaint.setStrokeWidth(mLineWidth);
  canvas.drawLine(left + mRadius / 2, mTop + mRadius, left + mRadius / 2 + mRadius, mTop + mRadius, mAddPaint);
  canvas.drawLine(left + mRadius, mTop + mRadius / 2, left + mRadius, mTop + mRadius / 2 + mRadius, mAddPaint);
 }
 }

根據(jù)isHintMode 布爾值變量,區(qū)分是繪制第二層(Hint層)或者第一層(加減按鈕層)。

繪制第二層時(shí)沒(méi)啥好說(shuō)的,就是利用canvas.drawRoundRect,繪制圓角矩形,然后canvas.drawText繪制hint。
(如果圓角的值足夠大,矩形的寬度足夠小,就變成了圓形。)

繪制第一層時(shí),要根據(jù)當(dāng)前的數(shù)量選擇不同的顏色,注意在繪制加減按鈕的圓圈時(shí),我們是用Path繪制的,這是因?yàn)槲覀冞€需要用Path構(gòu)建Region類(lèi),這個(gè)類(lèi)就是我們監(jiān)聽(tīng)點(diǎn)擊區(qū)域的重點(diǎn)。

點(diǎn)擊事件的監(jiān)聽(tīng)

在講解動(dòng)畫(huà)之前,我們先說(shuō)說(shuō)如何監(jiān)聽(tīng)點(diǎn)擊的區(qū)域,因?yàn)楸究丶膭?dòng)畫(huà)是和加減數(shù)量息息相關(guān)的,而數(shù)量的加減是由點(diǎn)擊相應(yīng)”+ - 按鈕”區(qū)域觸發(fā)的。

所以我們的監(jiān)聽(tīng)按鈕的點(diǎn)擊事件,其實(shí)就是監(jiān)聽(tīng)相應(yīng)的”+ - 按鈕”區(qū)域。

上一節(jié)中,我們?cè)诶L制”+ - 按鈕”區(qū)域時(shí),通過(guò)Path,構(gòu)建了兩個(gè)Region類(lèi),Region類(lèi)有個(gè)contains(int x, int y)方法如下,通過(guò)傳入對(duì)應(yīng)觸摸的x、y坐標(biāo),就可知道知否點(diǎn)擊了相應(yīng)區(qū)域。

 /**
 * Return true if the region contains the specified point
 */
 public native boolean contains(int x, int y);

知道了這一點(diǎn),再寫(xiě)這部分代碼就相當(dāng)簡(jiǎn)單了:

@Override
 public boolean onTouchEvent(MotionEvent event) {
 int action = event.getAction();
 switch (action) {
  case MotionEvent.ACTION_DOWN:
  //hint模式
  if (isHintMode) {
   onAddClick();
   return true;
  } else {
   if (mAddRegion.contains((int) event.getX(), (int) event.getY())) {
   onAddClick();
   return true;
   } else if (mDelRegion.contains((int) event.getX(), (int) event.getY())) {
   onDelClick();
   return true;
   }
  }
  break;
  case MotionEvent.ACTION_MOVE:
  break;
  case MotionEvent.ACTION_UP:
  case MotionEvent.ACTION_CANCEL:
  break;
 }
 return super.onTouchEvent(event);
 }

hint模式時(shí),我們可以認(rèn)為控件所有范圍都是“+”的有效區(qū)域。

而在非hint模式時(shí),根據(jù)上一節(jié)構(gòu)建的mAddRegion和mDelRegion去判斷。

判斷確認(rèn)點(diǎn)擊后,具體的操作,要根據(jù)業(yè)務(wù)的不同來(lái)編寫(xiě)了,設(shè)計(jì)到實(shí)際的購(gòu)物車(chē)可能還有寫(xiě)數(shù)據(jù)庫(kù)操作,或者請(qǐng)求接口等,要操作成功后才執(zhí)行動(dòng)畫(huà)、或者修改count,這一塊代碼每個(gè)人寫(xiě)法可能不同。
使用時(shí),可以重寫(xiě)onDelClick()和onAddClick()方法,并在合適的時(shí)機(jī)回調(diào)onCountAddSuccess()和onCountDelSuccess()以執(zhí)行動(dòng)畫(huà)。

本文如下編寫(xiě):

protected void onDelClick() {
 if (mCount > 0) {
  mCount--;
  onCountDelSuccess();
 }
 }
 protected void onAddClick() {
 if (mCount < mMaxCount) {
  mCount++;
  onCountAddSuccess();
 } else {
 }
 }
 /**
 * 數(shù)量增加成功后,使用者回調(diào)
 */
 public void onCountAddSuccess() {
 if (mCount == 1) {
  cancelAllAnim();
  mAnimReduceHint.start();
 } else {
  mAnimFraction = 0;
  invalidate();
 }
 }
 /**
 * 數(shù)量減少成功后,使用者回調(diào)
 */
 public void onCountDelSuccess() {
 if (mCount == 0) {
  cancelAllAnim();
  mAniDel.start();
 } else {
  mAnimFraction = 0;
  invalidate();
 }
 }

動(dòng)畫(huà)的實(shí)現(xiàn)

這里會(huì)用到兩個(gè)變量:

//動(dòng)畫(huà)的基準(zhǔn)值 動(dòng)畫(huà):減 0~1, 加 1~0 
 // 普通狀態(tài)下是0
 protected float mAnimFraction;
 //提示語(yǔ)收縮動(dòng)畫(huà) 0-1 展開(kāi)1-0
 //普通模式時(shí),應(yīng)該是1, 只在 isHintMode true 才有效
 protected float mAnimExpandHintFraction;

依次分析有哪些動(dòng)畫(huà):

Hint動(dòng)畫(huà)

主要是圓角矩形的展開(kāi)、收縮。

固定right、bottom,當(dāng)展開(kāi)時(shí),不斷減少矩形的左起點(diǎn)left坐標(biāo)值,則整個(gè)矩形寬度變大,呈現(xiàn)展開(kāi)。收縮時(shí)相反。
代碼:         

//背景
  mHintPaint.setColor(mHintBgColor);
  RectF rectF = new RectF(mLeft + (mWidth - mRadius * 2) * mAnimExpandHintFraction, mTop
   , mWidth - mCircleWidth, mHeight - mCircleWidth);
  canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint);

減按鈕動(dòng)畫(huà)

看起來(lái)是旋轉(zhuǎn)、位移、透明度。

那么對(duì)于背景的圓圈來(lái)說(shuō),我們只需要位移、透明度。因?yàn)樗旧硎莻€(gè)圓,就不要旋轉(zhuǎn)了。

代碼:       

//動(dòng)畫(huà) mAnimFraction :減 0~1, 加 1~0 ,
  //動(dòng)畫(huà)位移Max,
  float animOffsetMax = (mRadius * 2 +mGapBetweenCircle);
  //透明度動(dòng)畫(huà)的基準(zhǔn)
  int animAlphaMax = 255;
  int animRotateMax = 360;
  //左邊
  //背景 圓
  mDelPaint.setAlpha((int) (animAlphaMax * (1 - mAnimFraction)));
  mDelPath.reset();
  //改變圓心的X坐標(biāo),實(shí)現(xiàn)位移
  mDelPath.addCircle(animOffsetMax * mAnimFraction + mLeft + mRadius, mTop + mRadius, mRadius, Path.Direction.CW);
  canvas.drawPath(mDelPath, mDelPaint);

對(duì)于前景的“-”號(hào)來(lái)說(shuō),旋轉(zhuǎn)、位移、透明度都需要做。
這里我們利用canvas.translate() canvas.rotate 做旋轉(zhuǎn)和位移動(dòng)畫(huà),別忘了 canvas.save() canvas.restore()恢復(fù)畫(huà)布的狀態(tài)。(透明度在上面已經(jīng)設(shè)置過(guò)了。)        

//前景 -
  //旋轉(zhuǎn)動(dòng)畫(huà)
  canvas.save();
  canvas.translate(animOffsetMax * mAnimFraction + mLeft + mRadius, mTop + mRadius);
  canvas.rotate((int) (animRotateMax * (1 - mAnimFraction)));
  canvas.drawLine(-mRadius / 2, 0,
   +mRadius / 2, 0,
   mDelPaint);
  canvas.restore();

數(shù)量的動(dòng)畫(huà)

看起來(lái)也是旋轉(zhuǎn)、位移、透明度。同樣是利用canvas.translate() canvas.rotate 做旋轉(zhuǎn)和位移動(dòng)畫(huà)。     

 //數(shù)量
  canvas.save();
  //平移動(dòng)畫(huà)
  canvas.translate(mAnimFraction * (mGapBetweenCircle / 2 - mTextPaint.measureText(mCount + "") / 2 + mRadius), 0);
  //旋轉(zhuǎn)動(dòng)畫(huà),旋轉(zhuǎn)中心點(diǎn),x 是繪圖中心,y 是控件中心
  canvas.rotate(360 * mAnimFraction,
  mGapBetweenCircle / 2 + mLeft + mRadius * 2 ,
   mTop + mRadius);
  //透明度動(dòng)畫(huà)
  mTextPaint.setAlpha((int) (255 * (1 - mAnimFraction)));
  //是沒(méi)有動(dòng)畫(huà)的普通寫(xiě)法,x left, y baseLine
  canvas.drawText(mCount + "", mGapBetweenCircle / 2 - mTextPaint.measureText(mCount + "") / 2 + mLeft + mRadius * 2, mTop + mRadius - (mFontMetrics.top + mFontMetrics.bottom) / 2, mTextPaint);
  canvas.restore();

動(dòng)畫(huà)的定義:

動(dòng)畫(huà)是在View初始化時(shí)就定義好的,執(zhí)行順序:

  1. 數(shù)量增加,0-1時(shí),先收縮Hint(第二層)mAnimReduceHint執(zhí)行,完畢后執(zhí)行減按鈕(第一層)進(jìn)入的動(dòng)畫(huà)mAnimAdd。
  2. 數(shù)量減少,1-0時(shí),先執(zhí)行減按鈕退出的動(dòng)畫(huà)mAniDel,再伸展Hint動(dòng)畫(huà)mAnimExpandHint,完畢后,顯示hint文字。

代碼如下:     

 //動(dòng)畫(huà) +
 mAnimAdd = ValueAnimator.ofFloat(1, 0);
 mAnimAdd.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
  mAnimFraction = (float) animation.getAnimatedValue();
  invalidate();
  }
 });
 mAnimAdd.setDuration(350);
 //提示語(yǔ)收縮動(dòng)畫(huà) 0-1
 mAnimReduceHint = ValueAnimator.ofFloat(0, 1);
 mAnimReduceHint.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
  mAnimExpandHintFraction = (float) animation.getAnimatedValue();
  invalidate();
  }
 });
 mAnimReduceHint.addListener(new AnimatorListenerAdapter() {
  @Override
  public void onAnimationEnd(Animator animation) {
  if (mCount == 1) {
   //然后底色也不顯示了
   isHintMode = false;
  }
  if (mCount == 1) {
   Log.d(TAG, "現(xiàn)在還是1 開(kāi)始收縮動(dòng)畫(huà)");
   if (mAnimAdd != null && !mAnimAdd.isRunning()) {
   mAnimAdd.start();
   }
  }
  }
  @Override
  public void onAnimationStart(Animator animation) {
  if (mCount == 1) {
   //先不顯示文字了
   isShowHintText = false;
  }
  }
 });
 mAnimReduceHint.setDuration(350);
 //動(dòng)畫(huà) -
 mAniDel = ValueAnimator.ofFloat(0, 1);
 mAniDel.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
  mAnimFraction = (float) animation.getAnimatedValue();
  invalidate();
  }
 });
 //1-0的動(dòng)畫(huà)
 mAniDel.addListener(new AnimatorListenerAdapter() {
  @Override
  public void onAnimationEnd(Animator animation) {
  if (mCount == 0) {
   Log.d(TAG, "現(xiàn)在還是0onAnimationEnd() called with: animation = [" + animation + "]");
   if (mAnimExpandHint != null && !mAnimExpandHint.isRunning()) {
   mAnimExpandHint.start();
   }
  }
  }
 });
 mAniDel.setDuration(350);
 //提示語(yǔ)展開(kāi)動(dòng)畫(huà)
 //分析這個(gè)動(dòng)畫(huà),最初是個(gè)圓。 就是left 不斷減小
 mAnimExpandHint = ValueAnimator.ofFloat(1, 0);
 mAnimExpandHint.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
  mAnimExpandHintFraction = (float) animation.getAnimatedValue();
  invalidate();
  }
 });
 mAnimExpandHint.addListener(new AnimatorListenerAdapter() {
  @Override
  public void onAnimationEnd(Animator animation) {
  if (mCount == 0) {
   isShowHintText = true;
  }
  }
  @Override
  public void onAnimationStart(Animator animation) {
  if (mCount == 0) {
   isHintMode = true;
  }
  }
 });
 mAnimExpandHint.setDuration(350);

針對(duì)復(fù)用機(jī)制的處理

因?yàn)槲覀兊馁?gòu)物車(chē)控件肯定會(huì)用在列表中,不管你用ListView還是RecyclerView,都會(huì)涉及到復(fù)用的問(wèn)題。
復(fù)用給我們帶來(lái)一個(gè)麻煩的地方就是,我們要處理好一些屬性狀態(tài)值,否則UI上會(huì)有問(wèn)題。

可以從兩處下手處理:

onMeasure

列表復(fù)用時(shí),依然會(huì)回調(diào)onMeasure()方法,所以在這里初始化一些UI顯示的參數(shù)。

這里順帶將適配wrap_content 的代碼也一同貼上:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int wMode = MeasureSpec.getMode(widthMeasureSpec);
 int wSize = MeasureSpec.getSize(widthMeasureSpec);
 int hMode = MeasureSpec.getMode(heightMeasureSpec);
 int hSize = MeasureSpec.getSize(heightMeasureSpec);
 switch (wMode) {
  case MeasureSpec.EXACTLY:
  break;
  case MeasureSpec.AT_MOST:
  //不超過(guò)父控件給的范圍內(nèi),自由發(fā)揮
  int computeSize = (int) (getPaddingLeft() + mRadius * 2 +mGapBetweenCircle + mRadius * 2 + getPaddingRight() + mCircleWidth * 2);
  wSize = computeSize < wSize ? computeSize : wSize;
  break;
  case MeasureSpec.UNSPECIFIED:
  //自由發(fā)揮
  computeSize = (int) (getPaddingLeft() + mRadius * 2 + mGapBetweenCircle + mRadius * 2 + getPaddingRight() + mCircleWidth * 2);
  wSize = computeSize;
  break;
 }
 switch (hMode) {
  case MeasureSpec.EXACTLY:
  break;
  case MeasureSpec.AT_MOST:
  int computeSize = (int) (getPaddingTop() + mRadius * 2 + getPaddingBottom() + mCircleWidth * 2);
  hSize = computeSize < hSize ? computeSize : hSize;
  break;
  case MeasureSpec.UNSPECIFIED:
  computeSize = (int) (getPaddingTop() + mRadius * 2 + getPaddingBottom() + mCircleWidth * 2);
  hSize = computeSize;
  break;
 }
 setMeasuredDimension(wSize, hSize);
 //復(fù)用時(shí)會(huì)走這里,所以初始化一些UI顯示的參數(shù)
 mAnimFraction = 0;
 initHintSettings();
 }
 /**
 * 根據(jù)當(dāng)前count數(shù)量 初始化 hint提示語(yǔ)相關(guān)變量
 */
 private void initHintSettings() {
 if (mCount == 0) {
  isHintMode = true;
  isShowHintText = true;
  mAnimExpandHintFraction = 0;
 } else {
  isHintMode = false;
  isShowHintText = false;
  mAnimExpandHintFraction = 1;
 }
 }

在改變count時(shí)

一般在onBindViewHolder()或者getView()時(shí),都會(huì)對(duì)本控件重新設(shè)置count值,count改變時(shí),當(dāng)然也是需要根據(jù)count進(jìn)行屬性值的調(diào)整。
且此時(shí)如果View正在做動(dòng)畫(huà),應(yīng)該停止這些動(dòng)畫(huà)。

 /**
 * 設(shè)置當(dāng)前數(shù)量
 * @param count
 * @return
 */
 public AnimShopButton setCount(int count) {
 mCount = count;
 //先暫停所有動(dòng)畫(huà)
 if (mAnimAdd != null && mAnimAdd.isRunning()) {
  mAnimAdd.cancel();
 }
 if (mAniDel != null && mAniDel.isRunning()) {
  mAniDel.cancel();
 }
 //復(fù)用機(jī)制的處理
 if (mCount == 0) {
  // 0 不顯示 數(shù)字和-號(hào)
  mAnimFraction = 1;
 } else {
  mAnimFraction = 0;
 }
 initHintSettings();
 return this;
 }

總結(jié)

代碼傳送門(mén):喜歡的話(huà),隨手點(diǎn)個(gè)star。多謝

https://github.com/mcxtzhang/AnimShopButton

我在實(shí)現(xiàn)這個(gè)控件時(shí),覺(jué)得難度相對(duì)大的地方在于做動(dòng)畫(huà)時(shí),“-”按鈕和數(shù)量的旋轉(zhuǎn)動(dòng)畫(huà),如何確定正確的坐標(biāo)值。因?yàn)閷ext繪制的居中本身就有一些注意事項(xiàng)在里面,再涉及到動(dòng)畫(huà),難免蒙圈。需要多計(jì)算,多試驗(yàn)。

還有就是觀察餓了么的效果,將hint區(qū)域的動(dòng)畫(huà)利用改變RoundRect的寬度去實(shí)現(xiàn)。起初沒(méi)有想到,也是思考了一會(huì)如何去做。這是屬于分析、拆解動(dòng)畫(huà)遇到的問(wèn)題。

除了繪制以外的重點(diǎn)是:

  1. 利用Region監(jiān)聽(tīng)區(qū)域點(diǎn)擊事件。
  2. 復(fù)用的列表,如何正確顯示UI。
  3. 動(dòng)畫(huà)次序以及考慮到復(fù)用時(shí),在合適的地方取消動(dòng)畫(huà)。

盡情在項(xiàng)目中使用它吧,有問(wèn)題隨時(shí)gayhub給我反饋。

通過(guò)sdk工具查看餓了么,它其實(shí)是用TextView和ImageView組合實(shí)現(xiàn)的。另外我十分懷疑它沒(méi)有封裝成控件,因?yàn)樵诹斜眄?yè)和詳情頁(yè)的交互,以及動(dòng)畫(huà)居然略有不同, 在詳情頁(yè),仔細(xì)看由0-1時(shí),它右邊的 + 按鈕的動(dòng)畫(huà)居然會(huì)閃一下,在列表頁(yè)卻沒(méi)有,很是不解。

好了,本文所述到此結(jié)束。

相關(guān)文章

最新評(píng)論