轉載地址:https://ruzhan123.github.io/2016/07/01/2016-07-01-01-%E5%AF%B9RecyclerViewItem%E5%81%9A%E5%8A%A8%E7%94%BB/#rd
對RecyclerView Item做動畫,剛剛開始研究的時候一些坑
在這裏把一些設計思路分享出去
添加動態位移,靜態位移,縮放等動畫,保證了動畫狀態的平滑銜接
RecyclerView,ListView這些具有Item複用性的View,想要對其Item做動畫
需要注意以下幾點:
1,如果要一點擊,讓所有Item做動畫的效果。例如,上圖的編輯和取消,這樣的動態動畫。可以對所有ViewHolder中的View直接做動畫。但是需要在onBindViewHolder方法中對複用的item做靜態動畫,保證動畫狀態的平滑銜接。
2,每一個Item的特有屬性,例如,上圖checkbox的選中狀態,都需要把狀態字段放到對應的Java bean中, 並在onBindViewHolder方法從java bean取出狀態值,設置到view裏。
首先,對一些細節進行分析:
如何設計一個自定義View,來讓他可以自己移動,做動畫起來?
1,首先,創建一個View,他是RecyclerView Item的根佈局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
public class SlideRelativeLayout extends RelativeLayout { public static final String TAG = SlideRelativeLayout.class.getSimpleName(); private CheckBox mCheckBox; private RelativeLayout mContentSlide; private int mOffset; public SlideRelativeLayout(Context context) { super(context); } public SlideRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } public SlideRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } protected void onFinishInflate() { super.onFinishInflate(); mCheckBox = (CheckBox) findViewById(R.id.item_checkbox); mContentSlide = (RelativeLayout) findViewById(R.id.item_content_rl); setOffset(35); } public void setOffset(int offset) { mOffset = (int) (getContext().getResources().getDisplayMetrics().density * offset + 0.5f); } (Build.VERSION_CODES.HONEYCOMB) public void openAnimation() { ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setIntValues(0, 1); valueAnimator.setDuration(300); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { (Build.VERSION_CODES.HONEYCOMB_MR1) public void onAnimationUpdate(ValueAnimator valueAnimator) { float fraction = valueAnimator.getAnimatedFraction(); int endX = (int) (-mOffset * fraction); doAnimationSet(endX, fraction); } }); valueAnimator.start(); } (Build.VERSION_CODES.HONEYCOMB) public void closeAnimation() { ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setIntValues(0, 1); valueAnimator.setDuration(150); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { (Build.VERSION_CODES.HONEYCOMB_MR1) public void onAnimationUpdate(ValueAnimator valueAnimator) { float fraction = valueAnimator.getAnimatedFraction(); int endX = (int) (-mOffset * (1 - fraction)); doAnimationSet(endX, (1 - fraction)); } }); valueAnimator.start(); } (Build.VERSION_CODES.HONEYCOMB) private void doAnimationSet(int dx, float fraction) { mContentSlide.scrollTo(dx, 0); mCheckBox.setScaleX(fraction); mCheckBox.setScaleY(fraction); mCheckBox.setAlpha(fraction * 255); } public void open() { mContentSlide.scrollTo(-mOffset, 0); } public void close() { mContentSlide.scrollTo(0, 0); } } |
這裏,在View樹創建完畢之後找到我們需要做動畫的子View:
1 2 3 4 5 6 7 8 |
protected void onFinishInflate() { super.onFinishInflate(); mCheckBox = (CheckBox) findViewById(R.id.item_checkbox); mContentSlide = (RelativeLayout) findViewById(R.id.item_content_rl); setOffset(35); } |
然後,設計4個方法,分別爲:動態的打開動畫,動態的關閉動畫,靜態的打開動畫,靜態的關閉動畫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
(Build.VERSION_CODES.HONEYCOMB)public void openAnimation() { ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setIntValues(0, 1); valueAnimator.setDuration(300); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { (Build.VERSION_CODES.HONEYCOMB_MR1) public void onAnimationUpdate(ValueAnimator valueAnimator) { float fraction = valueAnimator.getAnimatedFraction(); int endX = (int) (-mOffset * fraction); doAnimationSet(endX, fraction); } }); valueAnimator.start(); } (Build.VERSION_CODES.HONEYCOMB)public void closeAnimation() { ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setIntValues(0, 1); valueAnimator.setDuration(150); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { (Build.VERSION_CODES.HONEYCOMB_MR1) public void onAnimationUpdate(ValueAnimator valueAnimator) { float fraction = valueAnimator.getAnimatedFraction(); int endX = (int) (-mOffset * (1 - fraction)); doAnimationSet(endX, (1 - fraction)); } }); valueAnimator.start(); } (Build.VERSION_CODES.HONEYCOMB)private void doAnimationSet(int dx, float fraction) { mContentSlide.scrollTo(dx, 0); mCheckBox.setScaleX(fraction); mCheckBox.setScaleY(fraction); mCheckBox.setAlpha(fraction * 255); } public void open() { mContentSlide.scrollTo(-mOffset, 0); } public void close() { mContentSlide.scrollTo(0, 0); } (Build.VERSION_CODES.HONEYCOMB)private void doAnimationSet(int dx, float fraction) { mContentSlide.scrollTo(dx, 0); mCheckBox.setScaleX(fraction); mCheckBox.setScaleY(fraction); mCheckBox.setAlpha(fraction * 255); } |
對子View做動畫我採取的策略是:使用屬性動畫,在每一貞動畫裏獲取到對應的值,對子View做相應的動畫,例如:動態的打開動畫。
onAnimationUpdate方法,顯示每一貞動畫都會回調一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
(Build.VERSION_CODES.HONEYCOMB)public void openAnimation() { ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setIntValues(0, 1); valueAnimator.setDuration(300); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { (Build.VERSION_CODES.HONEYCOMB_MR1) public void onAnimationUpdate(ValueAnimator valueAnimator) { float fraction = valueAnimator.getAnimatedFraction(); int endX = (int) (-mOffset * fraction); doAnimationSet(endX, fraction); } }); valueAnimator.start(); } (Build.VERSION_CODES.HONEYCOMB)private void doAnimationSet(int dx, float fraction) { mContentSlide.scrollTo(dx, 0); mCheckBox.setScaleX(fraction); mCheckBox.setScaleY(fraction); mCheckBox.setAlpha(fraction * 255); } |
這樣RecylerView 帶有動態動畫和靜態動畫的View就設計好了。
2,在bind方法中使用靜態動畫,動態動畫對外提供方法調用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
private class SlideViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private SlideRelativeLayout mSlideRelativeLayout; private CheckBox mCheckBox; private ItemBean mItemBean; public SlideViewHolder(View itemView) { super(itemView); mSlideRelativeLayout = (SlideRelativeLayout) itemView.findViewById(R.id.item_root); mCheckBox = (CheckBox) itemView.findViewById(R.id.item_checkbox); itemView.setOnClickListener(this); } public void bind(ItemBean itemBean) { mItemBean = itemBean; mCheckBox.setChecked(itemBean.isChecked()); switch (mState) { case NORMAL: mSlideRelativeLayout.close(); break; case SLIDE: mSlideRelativeLayout.open(); break; } } public void openItemAnimation() { mSlideRelativeLayout.openAnimation(); } public void closeItemAnimation() { mSlideRelativeLayout.closeAnimation(); } |
可以看到靜態動畫在bind裏調用,打開或者關閉是由mState變量決定的。而動態的滑動需要手動調用,那怎麼來使用這些動畫呢?
動態動畫的使用方法:存儲所有創建出來的ViewHolder,統一調用動態動畫方法。並設置mState變量值,防止滑動時動畫不能平滑銜接.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private List<SlideViewHolder> mSlideViewHolders = new ArrayList<>(); public void openItemAnimation() { mState = SLIDE;// for (SlideViewHolder holder : mSlideViewHolders) { holder.openItemAnimation(); } } public void closeItemAnimation() { mState = NORMAL; for (SlideViewHolder holder : mSlideViewHolders) { holder.closeItemAnimation(); } } |
而外面又是這樣調用的:
1 2 3 4 5 6 7 8 9 10 |
private void editItems() { if ("編輯".equals(mRightTV.getText().toString())) { mRightTV.setText("取消"); mSlideAdapter.openItemAnimation(); } else if ("取消".equals(mRightTV.getText().toString())) { mRightTV.setText("編輯"); mSlideAdapter.closeItemAnimation(); } } |
總體就是:點擊按鈕 – 變量ViewHolder集合做動態動畫,並設置mState變量 – 手機滑動屏幕走bind方法又是根據mState做靜態動畫
動畫從而平滑的銜接起來
再來看一次效果圖:
動態動畫起先,設置狀態值,引導處理正確的靜態動畫,RecyclerView item的動畫處理是不是變簡單了。
還有,對item的特殊數據需要在對應的java bean裏設置值,在bind方法取值設置到item中去。
分析就到這裏了,小程序一枚。
Demo下載:我的Github