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