對RecyclerView Item做動畫


轉載地址: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);
    }

    @Override
    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);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void openAnimation() {
        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setIntValues(0, 1);
        valueAnimator.setDuration(300);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float fraction = valueAnimator.getAnimatedFraction();
                int endX = (int) (-mOffset * fraction);
                doAnimationSet(endX, fraction);
            }
        });
        valueAnimator.start();
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void closeAnimation() {
        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setIntValues(0, 1);
        valueAnimator.setDuration(150);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float fraction = valueAnimator.getAnimatedFraction();
                int endX = (int) (-mOffset * (1 - fraction));
                doAnimationSet(endX, (1 - fraction));
            }
        });
        valueAnimator.start();
    }

    @TargetApi(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

@Override
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
	
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void openAnimation() {
    ValueAnimator valueAnimator = new ValueAnimator();
    valueAnimator.setIntValues(0, 1);
    valueAnimator.setDuration(300);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float fraction = valueAnimator.getAnimatedFraction();
            int endX = (int) (-mOffset * fraction);
            doAnimationSet(endX, fraction);
        }
    });
    valueAnimator.start();
}
	
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void closeAnimation() {
    ValueAnimator valueAnimator = new ValueAnimator();
    valueAnimator.setIntValues(0, 1);
    valueAnimator.setDuration(150);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
	
        @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float fraction = valueAnimator.getAnimatedFraction();
            int endX = (int) (-mOffset * (1 - fraction));
            doAnimationSet(endX, (1 - fraction));
        }
    });
    valueAnimator.start();
}
	
@TargetApi(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);
}

@TargetApi(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


@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void openAnimation() {
    ValueAnimator valueAnimator = new ValueAnimator();
    valueAnimator.setIntValues(0, 1);
    valueAnimator.setDuration(300);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float fraction = valueAnimator.getAnimatedFraction();
            int endX = (int) (-mOffset * fraction);
            doAnimationSet(endX, fraction);
        }
    });
    valueAnimator.start();
}

@TargetApi(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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章