RecyclerViewActivity.java revision bfda82df45c6f37f5773917d6f7c918fa3530d49
1/*
2 * Copyright (C) 2013 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
17
18package com.example.android.supportv7.widget;
19
20import android.R;
21import android.app.Activity;
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.os.Bundle;
28import android.support.v4.view.MenuItemCompat;
29import android.support.v7.widget.RecyclerView;
30import android.util.DisplayMetrics;
31import android.util.Log;
32import android.util.TypedValue;
33import android.view.Menu;
34import android.view.MenuItem;
35import android.view.View;
36import android.view.ViewGroup;
37import android.widget.TextView;
38import com.example.android.supportv7.Cheeses;
39
40import java.util.ArrayList;
41import java.util.Collections;
42
43public class RecyclerViewActivity extends Activity {
44    private static final String TAG = "RecyclerViewActivity";
45
46    private RecyclerView mRecyclerView;
47
48    @Override
49    protected void onCreate(Bundle savedInstanceState) {
50        super.onCreate(savedInstanceState);
51
52        final RecyclerView rv = new RecyclerView(this);
53        rv.setLayoutManager(new MyLayoutManager(this));
54        rv.setHasFixedSize(true);
55        rv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
56                ViewGroup.LayoutParams.MATCH_PARENT));
57        rv.setAdapter(new MyAdapter(Cheeses.sCheeseStrings));
58
59        rv.addItemDecoration(new DividerItemDecoration(this));
60
61        setContentView(rv);
62
63        mRecyclerView = rv;
64    }
65
66    @Override
67    public boolean onCreateOptionsMenu(Menu menu) {
68        super.onCreateOptionsMenu(menu);
69        MenuItemCompat.setShowAsAction(menu.add("Layout"), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
70        return true;
71    }
72
73    @Override
74    public boolean onOptionsItemSelected(MenuItem item) {
75        mRecyclerView.requestLayout();
76        return super.onOptionsItemSelected(item);
77    }
78
79    private static final int SCROLL_DISTANCE = 80; // dp
80
81    /**
82     * A basic ListView-style LayoutManager.
83     */
84    class MyLayoutManager extends RecyclerView.LayoutManager {
85        private static final String TAG = "MyLayoutManager";
86        private int mFirstPosition;
87        private final int mScrollDistance;
88
89        public MyLayoutManager(Context c) {
90            final DisplayMetrics dm = c.getResources().getDisplayMetrics();
91            mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f);
92        }
93
94        @Override
95        public void layoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
96                boolean structureChanged) {
97            final int parentBottom = getHeight() - getPaddingBottom();
98
99            final View oldTopView = getChildCount() > 0 ? getChildAt(0) : null;
100            int oldTop = getPaddingTop();
101            if (oldTopView != null) {
102                oldTop = oldTopView.getTop();
103            }
104
105            detachAndScrapAttachedViews(recycler);
106
107            int top = oldTop;
108            int bottom;
109            final int left = getPaddingLeft();
110            final int right = getWidth() - getPaddingRight();
111
112            final int count = adapter.getItemCount();
113            for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) {
114                View v = recycler.getViewForPosition(adapter, mFirstPosition + i);
115                addView(v, i);
116                measureChildWithMargins(v, 0, 0);
117                bottom = top + getDecoratedMeasuredHeight(v);
118                layoutDecorated(v, left, top, right, bottom);
119            }
120
121            removeAndRecycleScrap(recycler);
122        }
123
124        @Override
125        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
126            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
127                    ViewGroup.LayoutParams.WRAP_CONTENT);
128        }
129
130        @Override
131        public boolean canScrollVertically() {
132            return true;
133        }
134
135        @Override
136        public int scrollVerticallyBy(int dy, RecyclerView.Adapter adapter,
137                RecyclerView.Recycler recycler) {
138            if (getChildCount() == 0) {
139                return 0;
140            }
141
142            int scrolled = 0;
143            final int left = getPaddingLeft();
144            final int right = getWidth() - getPaddingRight();
145            if (dy < 0) {
146                while (scrolled > dy) {
147                    final View topView = getChildAt(0);
148                    final int hangingTop = Math.max(-getDecoratedTop(topView), 0);
149                    final int scrollBy = Math.min(scrolled - dy, hangingTop);
150                    scrolled -= scrollBy;
151                    offsetChildrenVertical(scrollBy);
152                    if (mFirstPosition > 0 && scrolled > dy) {
153                        mFirstPosition--;
154                        View v = recycler.getViewForPosition(adapter, mFirstPosition);
155                        addView(v, 0);
156                        measureChildWithMargins(v, 0, 0);
157                        final int bottom = getDecoratedTop(topView);
158                        final int top = bottom - getDecoratedMeasuredHeight(v);
159                        layoutDecorated(v, left, top, right, bottom);
160                    } else {
161                        break;
162                    }
163                }
164            } else if (dy > 0) {
165                final int parentHeight = getHeight();
166                while (scrolled < dy) {
167                    final View bottomView = getChildAt(getChildCount() - 1);
168                    final int hangingBottom =
169                            Math.max(getDecoratedBottom(bottomView) - parentHeight, 0);
170                    final int scrollBy = -Math.min(dy - scrolled, hangingBottom);
171                    scrolled -= scrollBy;
172                    offsetChildrenVertical(scrollBy);
173                    if (scrolled < dy && getItemCount() > mFirstPosition + getChildCount()) {
174                        View v = recycler.getViewForPosition(adapter,
175                                mFirstPosition + getChildCount());
176                        final int top = getDecoratedBottom(getChildAt(getChildCount() - 1));
177                        addView(v);
178                        measureChildWithMargins(v, 0, 0);
179                        final int bottom = top + getDecoratedMeasuredHeight(v);
180                        layoutDecorated(v, left, top, right, bottom);
181                    } else {
182                        break;
183                    }
184                }
185            }
186            recycleViewsOutOfBounds(recycler);
187            return scrolled;
188        }
189
190        @Override
191        public View onFocusSearchFailed(View focused, int direction,
192                RecyclerView.Adapter adapter, RecyclerView.Recycler recycler) {
193            final int oldCount = getChildCount();
194
195            if (oldCount == 0) {
196                return null;
197            }
198
199            final int left = getPaddingLeft();
200            final int right = getWidth() - getPaddingRight();
201
202            View toFocus = null;
203            int newViewsHeight = 0;
204            if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) {
205                while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) {
206                    mFirstPosition--;
207                    View v = recycler.getViewForPosition(adapter, mFirstPosition);
208                    final int bottom = getDecoratedTop(getChildAt(0));
209                    addView(v, 0);
210                    measureChildWithMargins(v, 0, 0);
211                    final int top = bottom - getDecoratedMeasuredHeight(v);
212                    layoutDecorated(v, left, top, right, bottom);
213                    if (v.isFocusable()) {
214                        toFocus = v;
215                        break;
216                    }
217                }
218            }
219            if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) {
220                while (mFirstPosition + getChildCount() < getItemCount() &&
221                        newViewsHeight < mScrollDistance) {
222                    View v = recycler.getViewForPosition(adapter, mFirstPosition + getChildCount());
223                    final int top = getDecoratedBottom(getChildAt(getChildCount() - 1));
224                    addView(v);
225                    measureChildWithMargins(v, 0, 0);
226                    final int bottom = top + getDecoratedMeasuredHeight(v);
227                    layoutDecorated(v, left, top, right, bottom);
228                    if (v.isFocusable()) {
229                        toFocus = v;
230                        break;
231                    }
232                }
233            }
234
235            return toFocus;
236        }
237
238        public void recycleViewsOutOfBounds(RecyclerView.Recycler recycler) {
239            final int childCount = getChildCount();
240            final int parentWidth = getWidth();
241            final int parentHeight = getHeight();
242            boolean foundFirst = false;
243            int first = 0;
244            int last = 0;
245            for (int i = 0; i < childCount; i++) {
246                final View v = getChildAt(i);
247                if (v.hasFocus() || (getDecoratedRight(v) >= 0 &&
248                        getDecoratedLeft(v) <= parentWidth &&
249                        getDecoratedBottom(v) >= 0 &&
250                        getDecoratedTop(v) <= parentHeight)) {
251                    if (!foundFirst) {
252                        first = i;
253                        foundFirst = true;
254                    }
255                    last = i;
256                }
257            }
258            for (int i = childCount - 1; i > last; i--) {
259                removeAndRecycleViewAt(i, recycler);
260            }
261            for (int i = first - 1; i >= 0; i--) {
262                removeAndRecycleViewAt(i, recycler);
263            }
264            if (getChildCount() == 0) {
265                mFirstPosition = 0;
266            } else {
267                mFirstPosition += first;
268            }
269        }
270    }
271
272    class MyAdapter extends RecyclerView.Adapter<ViewHolder> {
273        private int mBackground;
274        private ArrayList<String> mValues;
275
276        public MyAdapter(String[] strings) {
277            TypedValue val = new TypedValue();
278            RecyclerViewActivity.this.getTheme().resolveAttribute(
279                    R.attr.selectableItemBackground, val, true);
280            mBackground = val.resourceId;
281            mValues = new ArrayList<String>();
282            Collections.addAll(mValues, strings);
283        }
284
285        @Override
286        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
287            final ViewHolder h = new ViewHolder(new TextView(RecyclerViewActivity.this));
288            h.textView.setMinimumHeight(128);
289            h.textView.setFocusable(true);
290            h.textView.setBackgroundResource(mBackground);
291            h.textView.setOnClickListener(new View.OnClickListener() {
292                @Override
293                public void onClick(View v) {
294                    final int pos = h.getPosition();
295                    if (mValues.size() > pos + 1) {
296                        final String t = mValues.get(pos);
297                        mValues.set(pos, mValues.get(pos + 1));
298                        mValues.set(pos + 1, t);
299                        notifyItemRemoved(pos);
300                        notifyItemInserted(pos + 1);
301                    }
302                }
303            });
304            return h;
305        }
306
307        @Override
308        public void onBindViewHolder(ViewHolder holder, int position) {
309            holder.textView.setText(mValues.get(position));
310        }
311
312        @Override
313        public int getItemCount() {
314            return mValues.size();
315        }
316    }
317
318    static class ViewHolder extends RecyclerView.ViewHolder {
319        public TextView textView;
320
321        public ViewHolder(TextView v) {
322            super(v);
323            textView = v;
324        }
325
326        @Override
327        public String toString() {
328            return super.toString() + " '" + textView.getText();
329        }
330    }
331
332    static class DividerItemDecoration extends RecyclerView.ItemDecoration {
333        private static final int[] ATTRS = new int[] {
334                R.attr.listDivider
335        };
336
337        private Drawable mDivider;
338
339        public DividerItemDecoration(Context context) {
340            final TypedArray a = context.obtainStyledAttributes(ATTRS);
341            mDivider = a.getDrawable(0);
342            a.recycle();
343        }
344
345        public DividerItemDecoration(Drawable divider) {
346            mDivider = divider;
347        }
348
349        @Override
350        public void onDraw(Canvas c, RecyclerView parent) {
351            final int left = parent.getPaddingLeft();
352            final int right = parent.getWidth() - parent.getPaddingRight();
353
354            final int childCount = parent.getChildCount();
355            for (int i = 0; i < childCount; i++) {
356                final View child = parent.getChildAt(i);
357                final int top = child.getBottom();
358                final int bottom = top + mDivider.getIntrinsicHeight();
359                mDivider.setBounds(left, top, right, bottom);
360                mDivider.draw(c);
361            }
362        }
363
364        @Override
365        public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
366            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
367        }
368    }
369}
370