RecyclerViewActivity.java revision 549b58504ef397f4c1524ec370bbd0569027e315
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.graphics.Rect;
24import android.os.Bundle;
25import android.support.v4.view.MenuItemCompat;
26import android.support.v7.widget.RecyclerView;
27import android.util.DisplayMetrics;
28import android.util.TypedValue;
29import android.view.Menu;
30import android.view.MenuItem;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.View.MeasureSpec;
34import android.widget.TextView;
35import com.example.android.supportv7.Cheeses;
36
37import java.util.ArrayList;
38import java.util.Collections;
39
40public class RecyclerViewActivity extends Activity {
41    private RecyclerView mRecyclerView;
42
43    @Override
44    protected void onCreate(Bundle savedInstanceState) {
45        super.onCreate(savedInstanceState);
46
47        final RecyclerView rv = new RecyclerView(this);
48        rv.setLayoutManager(new MyLayoutManager(this));
49        rv.setHasFixedSize(true);
50        rv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
51                ViewGroup.LayoutParams.MATCH_PARENT));
52        rv.setAdapter(new MyAdapter(Cheeses.sCheeseStrings));
53        setContentView(rv);
54
55        mRecyclerView = rv;
56    }
57
58    @Override
59    public boolean onCreateOptionsMenu(Menu menu) {
60        super.onCreateOptionsMenu(menu);
61        MenuItemCompat.setShowAsAction(menu.add("Layout"), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
62        return true;
63    }
64
65    @Override
66    public boolean onOptionsItemSelected(MenuItem item) {
67        mRecyclerView.requestLayout();
68        return super.onOptionsItemSelected(item);
69    }
70
71    private static final int SCROLL_DISTANCE = 80; // dp
72
73    /**
74     * A basic ListView-style LayoutManager.
75     */
76    class MyLayoutManager extends RecyclerView.LayoutManager {
77        private static final String TAG = "MyLayoutManager";
78        private int mFirstPosition;
79        private final int mScrollDistance;
80
81        public MyLayoutManager(Context c) {
82            final DisplayMetrics dm = c.getResources().getDisplayMetrics();
83            mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f);
84        }
85
86        @Override
87        public void layoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler) {
88            final RecyclerView parent = getRecyclerView();
89            final int parentBottom = parent.getHeight() - parent.getPaddingBottom();
90
91            final View oldTopView = parent.getChildCount() > 0 ? parent.getChildAt(0) : null;
92            int oldTop = parent.getPaddingTop();
93            if (oldTopView != null) {
94                oldTop = oldTopView.getTop();
95            }
96
97            recycler.scrapAllViewsAttached();
98
99            int top = oldTop;
100            int bottom;
101            final int left = parent.getPaddingLeft();
102            final int right = parent.getWidth() - parent.getPaddingRight();
103
104            final int count = adapter.getItemCount();
105            for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) {
106                View v = recycler.getViewForPosition(mFirstPosition + i);
107                addView(v, i);
108                v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
109                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
110                bottom = top + v.getMeasuredHeight();
111                v.layout(left, top, right, bottom);
112            }
113
114            recycler.detachDirtyScrapViews();
115        }
116
117        @Override
118        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
119            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
120                    ViewGroup.LayoutParams.WRAP_CONTENT);
121        }
122
123        @Override
124        public boolean canScrollVertically() {
125            return true;
126        }
127
128        @Override
129        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler) {
130            final RecyclerView parent = getRecyclerView();
131            if (parent.getChildCount() == 0) {
132                return 0;
133            }
134
135            int scrolled = 0;
136            final int left = parent.getPaddingLeft();
137            final int right = parent.getWidth() - parent.getPaddingRight();
138            if (dy < 0) {
139                while (scrolled > dy) {
140                    final View topView = parent.getChildAt(0);
141                    final int hangingTop = Math.max(-topView.getTop(), 0);
142                    final int scrollBy = Math.min(scrolled - dy, hangingTop);
143                    scrolled -= scrollBy;
144                    parent.offsetChildrenVertical(scrollBy);
145                    if (mFirstPosition > 0 && scrolled > dy) {
146                        mFirstPosition--;
147                        View v = recycler.getViewForPosition(mFirstPosition);
148                        addView(v, 0);
149                        v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
150                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
151                        final int bottom = topView.getTop(); // TODO decorated top?
152                        final int top = bottom - v.getMeasuredHeight();
153                        v.layout(left, top, right, bottom);
154                    } else {
155                        break;
156                    }
157                }
158            } else if (dy > 0) {
159                final int parentHeight = parent.getHeight();
160                while (scrolled < dy) {
161                    final View bottomView = parent.getChildAt(parent.getChildCount() - 1);
162                    final int hangingBottom = Math.max(bottomView.getBottom() - parentHeight, 0);
163                    final int scrollBy = -Math.min(dy - scrolled, hangingBottom);
164                    scrolled -= scrollBy;
165                    parent.offsetChildrenVertical(scrollBy);
166                    if (scrolled < dy &&
167                            parent.getAdapter().getItemCount() > mFirstPosition + parent.getChildCount()) {
168                        View v = recycler.getViewForPosition(
169                                mFirstPosition + parent.getChildCount());
170                        final int top = parent.getChildAt(parent.getChildCount() - 1).getBottom();
171                        addView(v);
172                        v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
173                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
174                        final int bottom = top + v.getMeasuredHeight();
175                        v.layout(left, top, right, bottom);
176                    } else {
177                        break;
178                    }
179                }
180            }
181            detachAndScrapViewsOutOfBounds(recycler);
182            return scrolled;
183        }
184
185        @Override
186        public View onFocusSearchFailed(View focused, int direction,
187                RecyclerView.Recycler recycler) {
188            final RecyclerView rv = getRecyclerView();
189            final int oldFirstPosition = mFirstPosition;
190            final int oldCount = rv.getChildCount();
191
192            if (oldCount == 0) {
193                return null;
194            }
195
196            final int left = rv.getPaddingLeft();
197            final int right = rv.getWidth() - rv.getPaddingRight();
198
199            View toFocus = null;
200            int newViewsHeight = 0;
201            if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) {
202                while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) {
203                    mFirstPosition--;
204                    View v = recycler.getViewForPosition(mFirstPosition);
205                    final int bottom = rv.getChildAt(0).getTop(); // TODO decorated top?
206                    addView(v, 0);
207                    v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
208                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
209                    final int top = bottom - v.getMeasuredHeight();
210                    v.layout(left, top, right, bottom);
211                    if (v.isFocusable()) {
212                        toFocus = v;
213                        break;
214                    }
215                }
216            }
217            if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) {
218                while (mFirstPosition + rv.getChildCount() < rv.getAdapter().getItemCount() &&
219                        newViewsHeight < mScrollDistance) {
220                    View v = recycler.getViewForPosition(mFirstPosition + rv.getChildCount());
221                    final int top = rv.getChildAt(rv.getChildCount() - 1).getBottom();
222                    addView(v);
223                    v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
224                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
225                    final int bottom = top + v.getMeasuredHeight();
226                    v.layout(left, top, right, bottom);
227                    if (v.isFocusable()) {
228                        toFocus = v;
229                        break;
230                    }
231                }
232            }
233
234            return toFocus;
235        }
236
237        public void detachAndScrapViewsOutOfBounds(RecyclerView.Recycler recycler) {
238            final RecyclerView parent = getRecyclerView();
239            final int childCount = parent.getChildCount();
240            final int parentWidth = parent.getWidth();
241            final int parentHeight = parent.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 = parent.getChildAt(i);
247                if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth &&
248                        v.getBottom() >= 0 && v.getTop() <= parentHeight)) {
249                    if (!foundFirst) {
250                        first = i;
251                        foundFirst = true;
252                    }
253                    last = i;
254                }
255            }
256            for (int i = childCount - 1; i > last; i--) {
257                recycler.detachAndScrapView(parent.getChildAt(i));
258            }
259            for (int i = 0; i < first; i++) {
260                recycler.detachAndScrapView(parent.getChildAt(i));
261            }
262            if (parent.getChildCount() == 0) {
263                mFirstPosition = 0;
264            } else {
265                mFirstPosition += first;
266            }
267        }
268    }
269
270    class MyAdapter extends RecyclerView.Adapter {
271        private int mBackground;
272        private ArrayList<String> mValues;
273
274        public MyAdapter(String[] strings) {
275            TypedValue val = new TypedValue();
276            RecyclerViewActivity.this.getTheme().resolveAttribute(
277                    R.attr.selectableItemBackground, val, true);
278            mBackground = val.resourceId;
279            mValues = new ArrayList();
280            Collections.addAll(mValues, strings);
281        }
282
283        @Override
284        public RecyclerView.ViewHolder createViewHolder(ViewGroup parent, int viewType) {
285            final ViewHolder h = new ViewHolder(new TextView(RecyclerViewActivity.this));
286            h.textView.setMinimumHeight(128);
287            h.textView.setFocusable(true);
288            h.textView.setBackgroundResource(mBackground);
289            h.textView.setOnClickListener(new View.OnClickListener() {
290                @Override
291                public void onClick(View v) {
292                    final int pos = h.getPosition();
293                    if (mValues.size() > pos + 1) {
294                        final String t = mValues.get(pos);
295                        mValues.set(pos, mValues.get(pos + 1));
296                        mValues.set(pos + 1, t);
297                        notifyDataSetChanged();
298                    }
299                }
300            });
301            return h;
302        }
303
304        @Override
305        public void bindViewHolder(RecyclerView.ViewHolder holder, int position) {
306            ((ViewHolder) holder).textView.setText(mValues.get(position));
307        }
308
309        @Override
310        public int getItemCount() {
311            return mValues.size();
312        }
313    }
314
315    static class ViewHolder extends RecyclerView.ViewHolder {
316        public TextView textView;
317
318        public ViewHolder(TextView v) {
319            super(v);
320            textView = v;
321        }
322    }
323}
324