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.app.Activity;
21import android.content.Context;
22import android.os.Bundle;
23import android.support.v7.widget.DividerItemDecoration;
24import android.support.v7.widget.RecyclerView;
25import android.util.DisplayMetrics;
26import android.view.Menu;
27import android.view.MenuItem;
28import android.view.View;
29import android.view.ViewGroup;
30
31import com.example.android.supportv7.Cheeses;
32import com.example.android.supportv7.widget.adapter.SimpleStringAdapter;
33
34public class RecyclerViewActivity extends Activity {
35
36    private static final String TAG = "RecyclerViewActivity";
37
38    private RecyclerView mRecyclerView;
39
40    @Override
41    protected void onCreate(Bundle savedInstanceState) {
42        super.onCreate(savedInstanceState);
43
44        final RecyclerView rv = new RecyclerView(this);
45        rv.setLayoutManager(new MyLayoutManager(this));
46        rv.setHasFixedSize(true);
47        rv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
48                ViewGroup.LayoutParams.MATCH_PARENT));
49        rv.setAdapter(new SimpleStringAdapter(this, Cheeses.sCheeseStrings) {
50            @Override
51            public SimpleStringAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
52                    int viewType) {
53                final SimpleStringAdapter.ViewHolder vh = super
54                        .onCreateViewHolder(parent, viewType);
55                vh.itemView.setOnClickListener(new View.OnClickListener() {
56                    @Override
57                    public void onClick(View v) {
58                        final int pos = vh.getAdapterPosition();
59                        if (pos == RecyclerView.NO_POSITION) {
60                            return;
61                        }
62                        if (pos + 1 < getItemCount()) {
63                            swap(pos, pos + 1);
64                        }
65                    }
66                });
67                return vh;
68            }
69        });
70        rv.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
71        setContentView(rv);
72        mRecyclerView = rv;
73    }
74
75    @Override
76    public boolean onCreateOptionsMenu(Menu menu) {
77        super.onCreateOptionsMenu(menu);
78        menu.add("Layout").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
79        return true;
80    }
81
82    @Override
83    public boolean onOptionsItemSelected(MenuItem item) {
84        mRecyclerView.requestLayout();
85        return super.onOptionsItemSelected(item);
86    }
87
88    private static final int SCROLL_DISTANCE = 80; // dp
89
90    /**
91     * A basic ListView-style LayoutManager.
92     */
93    class MyLayoutManager extends RecyclerView.LayoutManager {
94
95        private static final String TAG = "MyLayoutManager";
96
97        private int mFirstPosition;
98
99        private final int mScrollDistance;
100
101        public MyLayoutManager(Context c) {
102            final DisplayMetrics dm = c.getResources().getDisplayMetrics();
103            mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f);
104        }
105
106        @Override
107        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
108            final int parentBottom = getHeight() - getPaddingBottom();
109            final View oldTopView = getChildCount() > 0 ? getChildAt(0) : null;
110            int oldTop = getPaddingTop();
111            if (oldTopView != null) {
112                oldTop = oldTopView.getTop();
113            }
114
115            detachAndScrapAttachedViews(recycler);
116
117            int top = oldTop;
118            int bottom;
119            final int left = getPaddingLeft();
120            final int right = getWidth() - getPaddingRight();
121
122            final int count = state.getItemCount();
123            for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) {
124                View v = recycler.getViewForPosition(mFirstPosition + i);
125                addView(v, i);
126                measureChildWithMargins(v, 0, 0);
127                bottom = top + getDecoratedMeasuredHeight(v);
128                layoutDecorated(v, left, top, right, bottom);
129            }
130        }
131
132        @Override
133        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
134            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
135                    ViewGroup.LayoutParams.WRAP_CONTENT);
136        }
137
138        @Override
139        public boolean canScrollVertically() {
140            return true;
141        }
142
143        @Override
144        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
145                RecyclerView.State state) {
146            if (getChildCount() == 0) {
147                return 0;
148            }
149
150            int scrolled = 0;
151            final int left = getPaddingLeft();
152            final int right = getWidth() - getPaddingRight();
153            if (dy < 0) {
154                while (scrolled > dy) {
155                    final View topView = getChildAt(0);
156                    final int hangingTop = Math.max(-getDecoratedTop(topView), 0);
157                    final int scrollBy = Math.min(scrolled - dy, hangingTop);
158                    scrolled -= scrollBy;
159                    offsetChildrenVertical(scrollBy);
160                    if (mFirstPosition > 0 && scrolled > dy) {
161                        mFirstPosition--;
162                        View v = recycler.getViewForPosition(mFirstPosition);
163                        addView(v, 0);
164                        measureChildWithMargins(v, 0, 0);
165                        final int bottom = getDecoratedTop(topView);
166                        final int top = bottom - getDecoratedMeasuredHeight(v);
167                        layoutDecorated(v, left, top, right, bottom);
168                    } else {
169                        break;
170                    }
171                }
172            } else if (dy > 0) {
173                final int parentHeight = getHeight();
174                while (scrolled < dy) {
175                    final View bottomView = getChildAt(getChildCount() - 1);
176                    final int hangingBottom =
177                            Math.max(getDecoratedBottom(bottomView) - parentHeight, 0);
178                    final int scrollBy = -Math.min(dy - scrolled, hangingBottom);
179                    scrolled -= scrollBy;
180                    offsetChildrenVertical(scrollBy);
181                    if (scrolled < dy && state.getItemCount() > mFirstPosition + getChildCount()) {
182                        View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
183                        final int top = getDecoratedBottom(getChildAt(getChildCount() - 1));
184                        addView(v);
185                        measureChildWithMargins(v, 0, 0);
186                        final int bottom = top + getDecoratedMeasuredHeight(v);
187                        layoutDecorated(v, left, top, right, bottom);
188                    } else {
189                        break;
190                    }
191                }
192            }
193            recycleViewsOutOfBounds(recycler);
194            return scrolled;
195        }
196
197        @Override
198        public View onFocusSearchFailed(View focused, int direction,
199                RecyclerView.Recycler recycler, RecyclerView.State state) {
200            final int oldCount = getChildCount();
201
202            if (oldCount == 0) {
203                return null;
204            }
205
206            final int left = getPaddingLeft();
207            final int right = getWidth() - getPaddingRight();
208
209            View toFocus = null;
210            int newViewsHeight = 0;
211            if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) {
212                while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) {
213                    mFirstPosition--;
214                    View v = recycler.getViewForPosition(mFirstPosition);
215                    final int bottom = getDecoratedTop(getChildAt(0));
216                    addView(v, 0);
217                    measureChildWithMargins(v, 0, 0);
218                    final int top = bottom - getDecoratedMeasuredHeight(v);
219                    layoutDecorated(v, left, top, right, bottom);
220                    if (v.isFocusable()) {
221                        toFocus = v;
222                        break;
223                    }
224                }
225            }
226            if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) {
227                while (mFirstPosition + getChildCount() < state.getItemCount() &&
228                        newViewsHeight < mScrollDistance) {
229                    View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
230                    final int top = getDecoratedBottom(getChildAt(getChildCount() - 1));
231                    addView(v);
232                    measureChildWithMargins(v, 0, 0);
233                    final int bottom = top + getDecoratedMeasuredHeight(v);
234                    layoutDecorated(v, left, top, right, bottom);
235                    if (v.isFocusable()) {
236                        toFocus = v;
237                        break;
238                    }
239                }
240            }
241
242            return toFocus;
243        }
244
245        public void recycleViewsOutOfBounds(RecyclerView.Recycler recycler) {
246            final int childCount = getChildCount();
247            final int parentWidth = getWidth();
248            final int parentHeight = getHeight();
249            boolean foundFirst = false;
250            int first = 0;
251            int last = 0;
252            for (int i = 0; i < childCount; i++) {
253                final View v = getChildAt(i);
254                if (v.hasFocus() || (getDecoratedRight(v) >= 0 &&
255                        getDecoratedLeft(v) <= parentWidth &&
256                        getDecoratedBottom(v) >= 0 &&
257                        getDecoratedTop(v) <= parentHeight)) {
258                    if (!foundFirst) {
259                        first = i;
260                        foundFirst = true;
261                    }
262                    last = i;
263                }
264            }
265            for (int i = childCount - 1; i > last; i--) {
266                removeAndRecycleViewAt(i, recycler);
267            }
268            for (int i = first - 1; i >= 0; i--) {
269                removeAndRecycleViewAt(i, recycler);
270            }
271            if (getChildCount() == 0) {
272                mFirstPosition = 0;
273            } else {
274                mFirstPosition += first;
275            }
276        }
277    }
278}
279