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