• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.example.android.supportv7.widget;
17 
18 import android.support.v4.util.ArrayMap;
19 import android.widget.CompoundButton;
20 import com.example.android.supportv7.R;
21 import android.app.Activity;
22 import android.content.Context;
23 import android.os.Bundle;
24 import android.support.v4.view.MenuItemCompat;
25 import android.support.v7.widget.RecyclerView;
26 import android.util.DisplayMetrics;
27 import android.util.TypedValue;
28 import android.view.Menu;
29 import android.view.MenuItem;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.CheckBox;
33 import android.widget.TextView;
34 
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.List;
38 
39 public class AnimatedRecyclerView extends Activity {
40 
41     private static final int SCROLL_DISTANCE = 80; // dp
42 
43     private RecyclerView mRecyclerView;
44 
45     private int mNumItemsAdded = 0;
46     ArrayList<String> mItems = new ArrayList<String>();
47     MyAdapter mAdapter;
48 
49     boolean mAnimationsEnabled = true;
50     boolean mPredictiveAnimationsEnabled = true;
51     RecyclerView.ItemAnimator mCachedAnimator = null;
52 
53     @Override
onCreate(Bundle savedInstanceState)54     protected void onCreate(Bundle savedInstanceState) {
55         super.onCreate(savedInstanceState);
56         setContentView(R.layout.animated_recycler_view);
57 
58         ViewGroup container = (ViewGroup) findViewById(R.id.container);
59         mRecyclerView = new RecyclerView(this);
60         mCachedAnimator = mRecyclerView.getItemAnimator();
61         mRecyclerView.setLayoutManager(new MyLayoutManager(this));
62         mRecyclerView.setHasFixedSize(true);
63         mRecyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
64                 ViewGroup.LayoutParams.MATCH_PARENT));
65         for (int i = 0; i < 6; ++i) {
66             mItems.add("Item #" + i);
67         }
68         mAdapter = new MyAdapter(mItems);
69         mRecyclerView.setAdapter(mAdapter);
70         container.addView(mRecyclerView);
71 
72         CheckBox enableAnimations = (CheckBox) findViewById(R.id.enableAnimations);
73         enableAnimations.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
74             @Override
75             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
76                 if (isChecked && mRecyclerView.getItemAnimator() == null) {
77                     mRecyclerView.setItemAnimator(mCachedAnimator);
78                 } else if (!isChecked && mRecyclerView.getItemAnimator() != null) {
79                     mRecyclerView.setItemAnimator(null);
80                 }
81                 mAnimationsEnabled = isChecked;
82             }
83         });
84 
85         CheckBox enablePredictiveAnimations =
86                 (CheckBox) findViewById(R.id.enablePredictiveAnimations);
87         enablePredictiveAnimations.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
88             @Override
89             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
90                 mPredictiveAnimationsEnabled = isChecked;
91             }
92         });
93 
94         CheckBox enableChangeAnimations =
95                 (CheckBox) findViewById(R.id.enableChangeAnimations);
96         enableChangeAnimations.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
97             @Override
98             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
99                 mCachedAnimator.setSupportsChangeAnimations(isChecked);
100             }
101         });
102     }
103 
104     @Override
onCreateOptionsMenu(Menu menu)105     public boolean onCreateOptionsMenu(Menu menu) {
106         super.onCreateOptionsMenu(menu);
107         MenuItemCompat.setShowAsAction(menu.add("Layout"), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
108         return true;
109     }
110 
111     @Override
onOptionsItemSelected(MenuItem item)112     public boolean onOptionsItemSelected(MenuItem item) {
113         mRecyclerView.requestLayout();
114         return super.onOptionsItemSelected(item);
115     }
116 
checkboxClicked(View view)117     public void checkboxClicked(View view) {
118         ViewGroup parent = (ViewGroup) view.getParent();
119         boolean selected = ((CheckBox) view).isChecked();
120         MyViewHolder holder = (MyViewHolder) mRecyclerView.getChildViewHolder(parent);
121         mAdapter.selectItem(holder, selected);
122     }
123 
itemClicked(View view)124     public void itemClicked(View view) {
125         ViewGroup parent = (ViewGroup) view;
126         MyViewHolder holder = (MyViewHolder) mRecyclerView.getChildViewHolder(parent);
127         final int position = holder.getAdapterPosition();
128         if (position == RecyclerView.NO_POSITION) {
129             return;
130         }
131         mAdapter.toggleExpanded(holder);
132         mAdapter.notifyItemChanged(position);
133     }
134 
deleteItem(View view)135     public void deleteItem(View view) {
136         int numItems = mItems.size();
137         if (numItems > 0) {
138             for (int i = numItems - 1; i >= 0; --i) {
139                 final String itemText = mItems.get(i);
140                 boolean selected = mAdapter.mSelected.get(itemText);
141                 if (selected) {
142                     removeAtPosition(i);
143                 }
144             }
145         }
146     }
147 
generateNewText()148     private String generateNewText() {
149         return "Added Item #" + mNumItemsAdded++;
150     }
151 
d1a2d3(View view)152     public void d1a2d3(View view) {
153         removeAtPosition(1);
154         addAtPosition(2, "Added Item #" + mNumItemsAdded++);
155         removeAtPosition(3);
156     }
157 
removeAtPosition(int position)158     private void removeAtPosition(int position) {
159         mItems.remove(position);
160         mAdapter.notifyItemRemoved(position);
161     }
162 
addAtPosition(int position, String text)163     private void addAtPosition(int position, String text) {
164         mItems.add(position, text);
165         mAdapter.mSelected.put(text, Boolean.FALSE);
166         mAdapter.mExpanded.put(text, Boolean.FALSE);
167         mAdapter.notifyItemInserted(position);
168     }
169 
addDeleteItem(View view)170     public void addDeleteItem(View view) {
171         addItem(view);
172         deleteItem(view);
173     }
174 
deleteAddItem(View view)175     public void deleteAddItem(View view) {
176         deleteItem(view);
177         addItem(view);
178     }
179 
addItem(View view)180     public void addItem(View view) {
181         addAtPosition(3, "Added Item #" + mNumItemsAdded++);
182     }
183 
184     /**
185      * A basic ListView-style LayoutManager.
186      */
187     class MyLayoutManager extends RecyclerView.LayoutManager {
188         private static final String TAG = "MyLayoutManager";
189         private int mFirstPosition;
190         private final int mScrollDistance;
191 
MyLayoutManager(Context c)192         public MyLayoutManager(Context c) {
193             final DisplayMetrics dm = c.getResources().getDisplayMetrics();
194             mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f);
195         }
196 
197         @Override
supportsPredictiveItemAnimations()198         public boolean supportsPredictiveItemAnimations() {
199             return mPredictiveAnimationsEnabled;
200         }
201 
202         @Override
onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)203         public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
204             int parentBottom = getHeight() - getPaddingBottom();
205 
206             final View oldTopView = getChildCount() > 0 ? getChildAt(0) : null;
207             int oldTop = getPaddingTop();
208             if (oldTopView != null) {
209                 oldTop = Math.min(oldTopView.getTop(), oldTop);
210             }
211 
212             // Note that we add everything to the scrap, but we do not clean it up;
213             // that is handled by the RecyclerView after this method returns
214             detachAndScrapAttachedViews(recycler);
215 
216             int top = oldTop;
217             int bottom = top;
218             final int left = getPaddingLeft();
219             final int right = getWidth() - getPaddingRight();
220 
221             int count = state.getItemCount();
222             for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) {
223                 View v = recycler.getViewForPosition(mFirstPosition + i);
224 
225                 RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) v.getLayoutParams();
226                 addView(v);
227                 measureChild(v, 0, 0);
228                 bottom = top + v.getMeasuredHeight();
229                 v.layout(left, top, right, bottom);
230                 if (mPredictiveAnimationsEnabled && params.isItemRemoved()) {
231                     parentBottom += v.getHeight();
232                 }
233             }
234 
235             if (mAnimationsEnabled && mPredictiveAnimationsEnabled && !state.isPreLayout()) {
236                 // Now that we've run a full layout, figure out which views were not used
237                 // (cached in previousViews). For each of these views, position it where
238                 // it would go, according to its position relative to the visible
239                 // positions in the list. This information will be used by RecyclerView to
240                 // record post-layout positions of these items for the purposes of animating them
241                 // out of view
242 
243                 View lastVisibleView = getChildAt(getChildCount() - 1);
244                 if (lastVisibleView != null) {
245                     RecyclerView.LayoutParams lastParams =
246                             (RecyclerView.LayoutParams) lastVisibleView.getLayoutParams();
247                     int lastPosition = lastParams.getViewLayoutPosition();
248                     final List<RecyclerView.ViewHolder> previousViews = recycler.getScrapList();
249                     count = previousViews.size();
250                     for (int i = 0; i < count; ++i) {
251                         View view = previousViews.get(i).itemView;
252                         RecyclerView.LayoutParams params =
253                                 (RecyclerView.LayoutParams) view.getLayoutParams();
254                         if (params.isItemRemoved()) {
255                             continue;
256                         }
257                         int position = params.getViewLayoutPosition();
258                         int newTop;
259                         if (position < mFirstPosition) {
260                             newTop = view.getHeight() * (position - mFirstPosition);
261                         } else {
262                             newTop = lastVisibleView.getTop() + view.getHeight() *
263                                     (position - lastPosition);
264                         }
265                         view.offsetTopAndBottom(newTop - view.getTop());
266                     }
267                 }
268             }
269         }
270 
271         @Override
generateDefaultLayoutParams()272         public RecyclerView.LayoutParams generateDefaultLayoutParams() {
273             return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
274                     ViewGroup.LayoutParams.WRAP_CONTENT);
275         }
276 
277         @Override
canScrollVertically()278         public boolean canScrollVertically() {
279             return true;
280         }
281 
282         @Override
scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)283         public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
284                 RecyclerView.State state) {
285             if (getChildCount() == 0) {
286                 return 0;
287             }
288 
289             int scrolled = 0;
290             final int left = getPaddingLeft();
291             final int right = getWidth() - getPaddingRight();
292             if (dy < 0) {
293                 while (scrolled > dy) {
294                     final View topView = getChildAt(0);
295                     final int hangingTop = Math.max(-topView.getTop(), 0);
296                     final int scrollBy = Math.min(scrolled - dy, hangingTop);
297                     scrolled -= scrollBy;
298                     offsetChildrenVertical(scrollBy);
299                     if (mFirstPosition > 0 && scrolled > dy) {
300                         mFirstPosition--;
301                         View v = recycler.getViewForPosition(mFirstPosition);
302                         addView(v, 0);
303                         measureChild(v, 0, 0);
304                         final int bottom = topView.getTop(); // TODO decorated top?
305                         final int top = bottom - v.getMeasuredHeight();
306                         v.layout(left, top, right, bottom);
307                     } else {
308                         break;
309                     }
310                 }
311             } else if (dy > 0) {
312                 final int parentHeight = getHeight();
313                 while (scrolled < dy) {
314                     final View bottomView = getChildAt(getChildCount() - 1);
315                     final int hangingBottom = Math.max(bottomView.getBottom() - parentHeight, 0);
316                     final int scrollBy = -Math.min(dy - scrolled, hangingBottom);
317                     scrolled -= scrollBy;
318                     offsetChildrenVertical(scrollBy);
319                     if (scrolled < dy && state.getItemCount() > mFirstPosition + getChildCount()) {
320                         View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
321                         final int top = getChildAt(getChildCount() - 1).getBottom();
322                         addView(v);
323                         measureChild(v, 0, 0);
324                         final int bottom = top + v.getMeasuredHeight();
325                         v.layout(left, top, right, bottom);
326                     } else {
327                         break;
328                     }
329                 }
330             }
331             recycleViewsOutOfBounds(recycler);
332             return scrolled;
333         }
334 
335         @Override
onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, RecyclerView.State state)336         public View onFocusSearchFailed(View focused, int direction,
337                 RecyclerView.Recycler recycler, RecyclerView.State state) {
338             final int oldCount = getChildCount();
339 
340             if (oldCount == 0) {
341                 return null;
342             }
343 
344             final int left = getPaddingLeft();
345             final int right = getWidth() - getPaddingRight();
346 
347             View toFocus = null;
348             int newViewsHeight = 0;
349             if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) {
350                 while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) {
351                     mFirstPosition--;
352                     View v = recycler.getViewForPosition(mFirstPosition);
353                     final int bottom = getChildAt(0).getTop(); // TODO decorated top?
354                     addView(v, 0);
355                     measureChild(v, 0, 0);
356                     final int top = bottom - v.getMeasuredHeight();
357                     v.layout(left, top, right, bottom);
358                     if (v.isFocusable()) {
359                         toFocus = v;
360                         break;
361                     }
362                 }
363             }
364             if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) {
365                 while (mFirstPosition + getChildCount() < state.getItemCount() &&
366                         newViewsHeight < mScrollDistance) {
367                     View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
368                     final int top = getChildAt(getChildCount() - 1).getBottom();
369                     addView(v);
370                     measureChild(v, 0, 0);
371                     final int bottom = top + v.getMeasuredHeight();
372                     v.layout(left, top, right, bottom);
373                     if (v.isFocusable()) {
374                         toFocus = v;
375                         break;
376                     }
377                 }
378             }
379 
380             return toFocus;
381         }
382 
recycleViewsOutOfBounds(RecyclerView.Recycler recycler)383         public void recycleViewsOutOfBounds(RecyclerView.Recycler recycler) {
384             final int childCount = getChildCount();
385             final int parentWidth = getWidth();
386             final int parentHeight = getHeight();
387             boolean foundFirst = false;
388             int first = 0;
389             int last = 0;
390             for (int i = 0; i < childCount; i++) {
391                 final View v = getChildAt(i);
392                 if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth &&
393                         v.getBottom() >= 0 && v.getTop() <= parentHeight)) {
394                     if (!foundFirst) {
395                         first = i;
396                         foundFirst = true;
397                     }
398                     last = i;
399                 }
400             }
401             for (int i = childCount - 1; i > last; i--) {
402                 removeAndRecycleViewAt(i, recycler);
403             }
404             for (int i = first - 1; i >= 0; i--) {
405                 removeAndRecycleViewAt(i, recycler);
406             }
407             if (getChildCount() == 0) {
408                 mFirstPosition = 0;
409             } else {
410                 mFirstPosition += first;
411             }
412         }
413 
414         @Override
onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount)415         public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
416             if (positionStart < mFirstPosition) {
417                 mFirstPosition += itemCount;
418             }
419         }
420 
421         @Override
onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount)422         public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
423             if (positionStart < mFirstPosition) {
424                 mFirstPosition -= itemCount;
425             }
426         }
427     }
428 
429     class MyAdapter extends RecyclerView.Adapter {
430         private int mBackground;
431         List<String> mData;
432         ArrayMap<String, Boolean> mSelected = new ArrayMap<String, Boolean>();
433         ArrayMap<String, Boolean> mExpanded = new ArrayMap<String, Boolean>();
434 
MyAdapter(List<String> data)435         public MyAdapter(List<String> data) {
436             TypedValue val = new TypedValue();
437             AnimatedRecyclerView.this.getTheme().resolveAttribute(
438                     R.attr.selectableItemBackground, val, true);
439             mBackground = val.resourceId;
440             mData = data;
441             for (String itemText : mData) {
442                 mSelected.put(itemText, Boolean.FALSE);
443                 mExpanded.put(itemText, Boolean.FALSE);
444             }
445         }
446 
447         @Override
onCreateViewHolder(ViewGroup parent, int viewType)448         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
449             MyViewHolder h = new MyViewHolder(getLayoutInflater().inflate(R.layout.selectable_item,
450                     null));
451             h.textView.setMinimumHeight(128);
452             h.textView.setFocusable(true);
453             h.textView.setBackgroundResource(mBackground);
454             return h;
455         }
456 
457         @Override
onBindViewHolder(RecyclerView.ViewHolder holder, int position)458         public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
459             String itemText = mData.get(position);
460             ((MyViewHolder) holder).textView.setText(itemText);
461             ((MyViewHolder) holder).expandedText.setText("More text for the expanded version");
462             boolean selected = false;
463             if (mSelected.get(itemText) != null) {
464                 selected = mSelected.get(itemText);
465             }
466             ((MyViewHolder) holder).checkBox.setChecked(selected);
467             Boolean expanded = mExpanded.get(itemText);
468             if (expanded != null && expanded) {
469                 ((MyViewHolder) holder).expandedText.setVisibility(View.VISIBLE);
470                 ((MyViewHolder) holder).textView.setVisibility(View.GONE);
471             } else {
472                 ((MyViewHolder) holder).expandedText.setVisibility(View.GONE);
473                 ((MyViewHolder) holder).textView.setVisibility(View.VISIBLE);
474             }
475         }
476 
477         @Override
getItemCount()478         public int getItemCount() {
479             return mData.size();
480         }
481 
selectItem(String itemText, boolean selected)482         public void selectItem(String itemText, boolean selected) {
483             mSelected.put(itemText, selected);
484         }
485 
selectItem(MyViewHolder holder, boolean selected)486         public void selectItem(MyViewHolder holder, boolean selected) {
487             mSelected.put((String) holder.textView.getText().toString(), selected);
488         }
489 
toggleExpanded(MyViewHolder holder)490         public void toggleExpanded(MyViewHolder holder) {
491             String text = (String) holder.textView.getText();
492             mExpanded.put(text, !mExpanded.get(text));
493         }
494     }
495 
496     static class MyViewHolder extends RecyclerView.ViewHolder {
497         public TextView expandedText;
498         public TextView textView;
499         public CheckBox checkBox;
500 
MyViewHolder(View v)501         public MyViewHolder(View v) {
502             super(v);
503             expandedText = (TextView) v.findViewById(R.id.expandedText);
504             textView = (TextView) v.findViewById(R.id.text);
505             checkBox = (CheckBox) v.findViewById(R.id.selected);
506         }
507 
508         @Override
toString()509         public String toString() {
510             return super.toString() + " \"" + textView.getText() + "\"";
511         }
512     }
513 }
514