(
99 | getActivity(),
100 | android.R.layout.simple_list_item_activated_1,
101 | android.R.id.text1,
102 | new String[]{
103 | getString(R.string.title_section1),
104 | getString(R.string.title_section2),
105 | getString(R.string.title_section3),
106 | getString(R.string.title_section4),
107 | getString(R.string.title_section5),
108 | }));
109 | mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
110 | return mDrawerListView;
111 | }
112 |
113 | public boolean isDrawerOpen() {
114 | return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
115 | }
116 |
117 | /**
118 | * Users of this fragment must call this method to set up the navigation drawer interactions.
119 | *
120 | * @param fragmentId The android:id of this fragment in its activity's layout.
121 | * @param drawerLayout The DrawerLayout containing this fragment's UI.
122 | */
123 | public void setUp(int fragmentId, DrawerLayout drawerLayout) {
124 | mFragmentContainerView = getActivity().findViewById(fragmentId);
125 | mDrawerLayout = drawerLayout;
126 |
127 | // set a custom shadow that overlays the main content when the drawer opens
128 | mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
129 | // set up the drawer's list view with items and click listener
130 |
131 | // ActionBarDrawerToggle ties together the the proper interactions
132 | // between the navigation drawer and the action bar app icon.
133 | mDrawerToggle = new ActionBarDrawerToggle(
134 | getActivity(), /* host Activity */
135 | mDrawerLayout, /* DrawerLayout object */
136 | R.string.navigation_drawer_open, /* "open drawer" description for accessibility */
137 | R.string.navigation_drawer_close /* "close drawer" description for accessibility */
138 | ) {
139 | @Override
140 | public void onDrawerClosed(View drawerView) {
141 | super.onDrawerClosed(drawerView);
142 | if (!isAdded()) {
143 | return;
144 | }
145 |
146 | getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
147 | }
148 |
149 | @Override
150 | public void onDrawerOpened(View drawerView) {
151 | super.onDrawerOpened(drawerView);
152 | if (!isAdded()) {
153 | return;
154 | }
155 |
156 | if (!mUserLearnedDrawer) {
157 | // The user manually opened the drawer; store this flag to prevent auto-showing
158 | // the navigation drawer automatically in the future.
159 | mUserLearnedDrawer = true;
160 | SharedPreferences sp = PreferenceManager
161 | .getDefaultSharedPreferences(getActivity());
162 | sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply();
163 | }
164 |
165 | getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
166 | }
167 | };
168 |
169 | // If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
170 | // per the navigation drawer design guidelines.
171 | if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
172 | mDrawerLayout.openDrawer(mFragmentContainerView);
173 | }
174 |
175 | // Defer code dependent on restoration of previous instance state.
176 | mDrawerLayout.post(new Runnable() {
177 | @Override
178 | public void run() {
179 | mDrawerToggle.syncState();
180 | }
181 | });
182 |
183 | mDrawerLayout.setDrawerListener(mDrawerToggle);
184 | }
185 |
186 | private void selectItem(int position) {
187 | mCurrentSelectedPosition = position;
188 | if (mDrawerListView != null) {
189 | mDrawerListView.setItemChecked(position, true);
190 | }
191 | if (mDrawerLayout != null) {
192 | mDrawerLayout.closeDrawer(mFragmentContainerView);
193 | }
194 | if (mCallbacks != null) {
195 | mCallbacks.onNavigationDrawerItemSelected(position);
196 | }
197 | }
198 |
199 | @Override
200 | public void onAttach(Activity activity) {
201 | super.onAttach(activity);
202 | try {
203 | mCallbacks = (NavigationDrawerCallbacks) activity;
204 | } catch (ClassCastException e) {
205 | throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
206 | }
207 | }
208 |
209 | @Override
210 | public void onDetach() {
211 | super.onDetach();
212 | mCallbacks = null;
213 | }
214 |
215 | @Override
216 | public void onSaveInstanceState(Bundle outState) {
217 | super.onSaveInstanceState(outState);
218 | outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
219 | }
220 |
221 | @Override
222 | public void onConfigurationChanged(Configuration newConfig) {
223 | super.onConfigurationChanged(newConfig);
224 | // Forward the new configuration the drawer toggle component.
225 | mDrawerToggle.onConfigurationChanged(newConfig);
226 | }
227 |
228 | @Override
229 | public boolean onOptionsItemSelected(MenuItem item) {
230 | if (mDrawerToggle.onOptionsItemSelected(item)) {
231 | return true;
232 | }
233 |
234 | return super.onOptionsItemSelected(item);
235 | }
236 |
237 | /**
238 | * Callbacks interface that all activities using this fragment must implement.
239 | */
240 | public static interface NavigationDrawerCallbacks {
241 | /**
242 | * Called when an item in the navigation drawer is selected.
243 | */
244 | void onNavigationDrawerItemSelected(int position);
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/recyclerplayground/fragments/RecyclerFragment.java:
--------------------------------------------------------------------------------
1 | package com.example.android.recyclerplayground.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.Fragment;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.LayoutInflater;
7 | import android.view.Menu;
8 | import android.view.MenuInflater;
9 | import android.view.MenuItem;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.AdapterView;
13 | import android.widget.Toast;
14 |
15 | import com.example.android.recyclerplayground.NumberPickerDialog;
16 | import com.example.android.recyclerplayground.R;
17 | import com.example.android.recyclerplayground.adapters.SimpleAdapter;
18 |
19 | public abstract class RecyclerFragment extends Fragment implements AdapterView.OnItemClickListener {
20 |
21 | private RecyclerView mList;
22 | private SimpleAdapter mAdapter;
23 |
24 | /** Required Overrides for Sample Fragments */
25 |
26 | protected abstract RecyclerView.LayoutManager getLayoutManager();
27 | protected abstract RecyclerView.ItemDecoration getItemDecoration();
28 | protected abstract int getDefaultItemCount();
29 | protected abstract SimpleAdapter getAdapter();
30 |
31 | @Override
32 | public void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setHasOptionsMenu(true);
35 | }
36 |
37 | @Override
38 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
39 | Bundle savedInstanceState) {
40 | View rootView = inflater.inflate(R.layout.fragment_recycler, container, false);
41 |
42 | mList = (RecyclerView) rootView.findViewById(R.id.section_list);
43 | mList.setLayoutManager(getLayoutManager());
44 | mList.addItemDecoration(getItemDecoration());
45 |
46 | mList.getItemAnimator().setAddDuration(1000);
47 | mList.getItemAnimator().setChangeDuration(1000);
48 | mList.getItemAnimator().setMoveDuration(1000);
49 | mList.getItemAnimator().setRemoveDuration(1000);
50 |
51 | mAdapter = getAdapter();
52 | mAdapter.setItemCount(getDefaultItemCount());
53 | mAdapter.setOnItemClickListener(this);
54 | mList.setAdapter(mAdapter);
55 |
56 | return rootView;
57 | }
58 |
59 | @Override
60 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
61 | inflater.inflate(R.menu.grid_options, menu);
62 | }
63 |
64 | @Override
65 | public boolean onOptionsItemSelected(MenuItem item) {
66 | NumberPickerDialog dialog;
67 | switch (item.getItemId()) {
68 | case R.id.action_add:
69 | dialog = new NumberPickerDialog(getActivity());
70 | dialog.setTitle("Position to Add");
71 | dialog.setPickerRange(0, mAdapter.getItemCount());
72 | dialog.setOnNumberSelectedListener(new NumberPickerDialog.OnNumberSelectedListener() {
73 | @Override
74 | public void onNumberSelected(int value) {
75 | mAdapter.addItem(value);
76 | }
77 | });
78 | dialog.show();
79 |
80 | return true;
81 | case R.id.action_remove:
82 | dialog = new NumberPickerDialog(getActivity());
83 | dialog.setTitle("Position to Remove");
84 | dialog.setPickerRange(0, mAdapter.getItemCount()-1);
85 | dialog.setOnNumberSelectedListener(new NumberPickerDialog.OnNumberSelectedListener() {
86 | @Override
87 | public void onNumberSelected(int value) {
88 | mAdapter.removeItem(value);
89 | }
90 | });
91 | dialog.show();
92 |
93 | return true;
94 | case R.id.action_empty:
95 | mAdapter.setItemCount(0);
96 | return true;
97 | case R.id.action_small:
98 | mAdapter.setItemCount(5);
99 | return true;
100 | case R.id.action_medium:
101 | mAdapter.setItemCount(25);
102 | return true;
103 | case R.id.action_large:
104 | mAdapter.setItemCount(196);
105 | return true;
106 | case R.id.action_scroll_zero:
107 | mList.scrollToPosition(0);
108 | return true;
109 | case R.id.action_smooth_zero:
110 | mList.smoothScrollToPosition(0);
111 | return true;
112 | default:
113 | return super.onOptionsItemSelected(item);
114 | }
115 | }
116 |
117 | @Override
118 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
119 | Toast.makeText(getActivity(),
120 | "Clicked: " + position + ", index " + mList.indexOfChild(view),
121 | Toast.LENGTH_SHORT).show();
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/recyclerplayground/fragments/VerticalFragment.java:
--------------------------------------------------------------------------------
1 | package com.example.android.recyclerplayground.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.widget.LinearLayoutManager;
5 | import android.support.v7.widget.RecyclerView;
6 |
7 | import com.example.android.recyclerplayground.DividerDecoration;
8 | import com.example.android.recyclerplayground.adapters.SimpleAdapter;
9 |
10 | public class VerticalFragment extends RecyclerFragment {
11 |
12 | public static VerticalFragment newInstance() {
13 | VerticalFragment fragment = new VerticalFragment();
14 | Bundle args = new Bundle();
15 | fragment.setArguments(args);
16 | return fragment;
17 | }
18 |
19 | @Override
20 | protected RecyclerView.LayoutManager getLayoutManager() {
21 | return new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
22 | }
23 |
24 | @Override
25 | protected RecyclerView.ItemDecoration getItemDecoration() {
26 | //We must draw dividers ourselves if we want them in a list
27 | return new DividerDecoration(getActivity());
28 | }
29 |
30 | @Override
31 | protected int getDefaultItemCount() {
32 | return 100;
33 | }
34 |
35 | @Override
36 | protected SimpleAdapter getAdapter() {
37 | return new SimpleAdapter();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/recyclerplayground/fragments/VerticalGridFragment.java:
--------------------------------------------------------------------------------
1 | package com.example.android.recyclerplayground.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.widget.GridLayoutManager;
5 | import android.support.v7.widget.RecyclerView;
6 |
7 | import com.example.android.recyclerplayground.InsetDecoration;
8 | import com.example.android.recyclerplayground.adapters.SimpleAdapter;
9 |
10 | public class VerticalGridFragment extends RecyclerFragment {
11 |
12 | public static VerticalGridFragment newInstance() {
13 | VerticalGridFragment fragment = new VerticalGridFragment();
14 | Bundle args = new Bundle();
15 | fragment.setArguments(args);
16 | return fragment;
17 | }
18 |
19 | @Override
20 | protected RecyclerView.LayoutManager getLayoutManager() {
21 | return new GridLayoutManager(getActivity(), 2, GridLayoutManager.VERTICAL, false);
22 | }
23 |
24 | @Override
25 | protected RecyclerView.ItemDecoration getItemDecoration() {
26 | return new InsetDecoration(getActivity());
27 | }
28 |
29 | @Override
30 | protected int getDefaultItemCount() {
31 | return 100;
32 | }
33 |
34 | @Override
35 | protected SimpleAdapter getAdapter() {
36 | return new SimpleAdapter();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/recyclerplayground/fragments/VerticalStaggeredGridFragment.java:
--------------------------------------------------------------------------------
1 | package com.example.android.recyclerplayground.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.support.v7.widget.StaggeredGridLayoutManager;
6 |
7 | import com.example.android.recyclerplayground.InsetDecoration;
8 | import com.example.android.recyclerplayground.adapters.SimpleAdapter;
9 | import com.example.android.recyclerplayground.adapters.SimpleStaggeredAdapter;
10 |
11 | public class VerticalStaggeredGridFragment extends RecyclerFragment {
12 |
13 | public static VerticalStaggeredGridFragment newInstance() {
14 | VerticalStaggeredGridFragment fragment = new VerticalStaggeredGridFragment();
15 | Bundle args = new Bundle();
16 | fragment.setArguments(args);
17 | return fragment;
18 | }
19 |
20 | @Override
21 | protected RecyclerView.LayoutManager getLayoutManager() {
22 | return new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
23 | }
24 |
25 | @Override
26 | protected RecyclerView.ItemDecoration getItemDecoration() {
27 | return new InsetDecoration(getActivity());
28 | }
29 |
30 | @Override
31 | protected int getDefaultItemCount() {
32 | return 100;
33 | }
34 |
35 | @Override
36 | protected SimpleAdapter getAdapter() {
37 | return new SimpleStaggeredAdapter();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/recyclerplayground/layout/FixedGridLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.example.android.recyclerplayground.layout;
2 |
3 | import android.content.Context;
4 | import android.graphics.PointF;
5 | import android.support.v7.widget.LinearSmoothScroller;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.util.AttributeSet;
8 | import android.util.Log;
9 | import android.util.SparseArray;
10 | import android.util.SparseIntArray;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 |
14 | import java.util.HashSet;
15 | import java.util.List;
16 |
17 | /**
18 | * A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation
19 | * that places children in a two-dimensional grid, sized to a fixed column count
20 | * value. User scrolling is possible in both horizontal and vertical directions
21 | * to view the data set.
22 | *
23 | * The column count is controllable via {@link #setTotalColumnCount(int)}. The layout manager
24 | * will generate the number of rows necessary to accommodate the data set based on
25 | * the fixed column count.
26 | *
27 | *
This manager does make some assumptions to simplify the implementation:
28 | *
29 | * - All child views are assumed to be the same size
30 | * - The window of visible views is a constant
31 | *
32 | */
33 | public class FixedGridLayoutManager extends RecyclerView.LayoutManager {
34 |
35 | private static final String TAG = FixedGridLayoutManager.class.getSimpleName();
36 |
37 | private static final int DEFAULT_COUNT = 1;
38 |
39 | /* View Removal Constants */
40 | private static final int REMOVE_VISIBLE = 0;
41 | private static final int REMOVE_INVISIBLE = 1;
42 |
43 | /* Fill Direction Constants */
44 | private static final int DIRECTION_NONE = -1;
45 | private static final int DIRECTION_START = 0;
46 | private static final int DIRECTION_END = 1;
47 | private static final int DIRECTION_UP = 2;
48 | private static final int DIRECTION_DOWN = 3;
49 |
50 | /* First (top-left) position visible at any point */
51 | private int mFirstVisiblePosition;
52 | /* Consistent size applied to all child views */
53 | private int mDecoratedChildWidth;
54 | private int mDecoratedChildHeight;
55 | /* Number of columns that exist in the grid */
56 | private int mTotalColumnCount = DEFAULT_COUNT;
57 | /* Metrics for the visible window of our data */
58 | private int mVisibleColumnCount;
59 | private int mVisibleRowCount;
60 |
61 | /* Used for tracking off-screen change events */
62 | private int mFirstChangedPosition;
63 | private int mChangedPositionCount;
64 |
65 | /**
66 | * Set the number of columns the layout manager will use. This will
67 | * trigger a layout update.
68 | * @param count Number of columns.
69 | */
70 | public void setTotalColumnCount(int count) {
71 | mTotalColumnCount = count;
72 | requestLayout();
73 | }
74 |
75 | /*
76 | * You must return true from this method if you want your
77 | * LayoutManager to support anything beyond "simple" item
78 | * animations. Enabling this causes onLayoutChildren() to
79 | * be called twice on each animated change; once for a
80 | * pre-layout, and again for the real layout.
81 | */
82 | @Override
83 | public boolean supportsPredictiveItemAnimations() {
84 | return true;
85 | }
86 |
87 | /*
88 | * Called by RecyclerView when a view removal is triggered. This is called
89 | * before onLayoutChildren() in pre-layout if the views removed are not visible. We
90 | * use it in this case to inform pre-layout that a removal took place.
91 | *
92 | * This method is still called if the views removed were visible, but it will
93 | * happen AFTER pre-layout.
94 | */
95 | @Override
96 | public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
97 | mFirstChangedPosition = positionStart;
98 | mChangedPositionCount = itemCount;
99 | }
100 |
101 | /*
102 | * This method is your initial call from the framework. You will receive it when you
103 | * need to start laying out the initial set of views. This method will not be called
104 | * repeatedly, so don't rely on it to continually process changes during user
105 | * interaction.
106 | *
107 | * This method will be called when the data set in the adapter changes, so it can be
108 | * used to update a layout based on a new item count.
109 | *
110 | * If predictive animations are enabled, you will see this called twice. First, with
111 | * state.isPreLayout() returning true to lay out children in their initial conditions.
112 | * Then again to lay out children in their final locations.
113 | */
114 | @Override
115 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
116 | //We have nothing to show for an empty data set but clear any existing views
117 | if (getItemCount() == 0) {
118 | detachAndScrapAttachedViews(recycler);
119 | return;
120 | }
121 | if (getChildCount() == 0 && state.isPreLayout()) {
122 | //Nothing to do during prelayout when empty
123 | return;
124 | }
125 |
126 | //Clear change tracking state when a real layout occurs
127 | if (!state.isPreLayout()) {
128 | mFirstChangedPosition = mChangedPositionCount = 0;
129 | }
130 |
131 | if (getChildCount() == 0) { //First or empty layout
132 | //Scrap measure one child
133 | View scrap = recycler.getViewForPosition(0);
134 | addView(scrap);
135 | measureChildWithMargins(scrap, 0, 0);
136 |
137 | /*
138 | * We make some assumptions in this code based on every child
139 | * view being the same size (i.e. a uniform grid). This allows
140 | * us to compute the following values up front because they
141 | * won't change.
142 | */
143 | mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);
144 | mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);
145 |
146 | detachAndScrapView(scrap, recycler);
147 | }
148 |
149 | //Always update the visible row/column counts
150 | updateWindowSizing();
151 |
152 | SparseIntArray removedCache = null;
153 | /*
154 | * During pre-layout, we need to take note of any views that are
155 | * being removed in order to handle predictive animations
156 | */
157 | if (state.isPreLayout()) {
158 | removedCache = new SparseIntArray(getChildCount());
159 | for (int i=0; i < getChildCount(); i++) {
160 | final View view = getChildAt(i);
161 | LayoutParams lp = (LayoutParams) view.getLayoutParams();
162 |
163 | if (lp.isItemRemoved()) {
164 | //Track these view removals as visible
165 | removedCache.put(lp.getViewLayoutPosition(), REMOVE_VISIBLE);
166 | }
167 | }
168 |
169 | //Track view removals that happened out of bounds (i.e. off-screen)
170 | if (removedCache.size() == 0 && mChangedPositionCount > 0) {
171 | for (int i = mFirstChangedPosition; i < (mFirstChangedPosition + mChangedPositionCount); i++) {
172 | removedCache.put(i, REMOVE_INVISIBLE);
173 | }
174 | }
175 | }
176 |
177 |
178 | int childLeft;
179 | int childTop;
180 | if (getChildCount() == 0) { //First or empty layout
181 | //Reset the visible and scroll positions
182 | mFirstVisiblePosition = 0;
183 | childLeft = getPaddingLeft();
184 | childTop = getPaddingTop();
185 | } else if (!state.isPreLayout()
186 | && getVisibleChildCount() >= state.getItemCount()) {
187 | //Data set is too small to scroll fully, just reset position
188 | mFirstVisiblePosition = 0;
189 | childLeft = getPaddingLeft();
190 | childTop = getPaddingTop();
191 | } else { //Adapter data set changes
192 | /*
193 | * Keep the existing initial position, and save off
194 | * the current scrolled offset.
195 | */
196 | final View topChild = getChildAt(0);
197 | childLeft = getDecoratedLeft(topChild);
198 | childTop = getDecoratedTop(topChild);
199 |
200 | /*
201 | * When data set is too small to scroll vertically, adjust vertical offset
202 | * and shift position to the first row, preserving current column
203 | */
204 | if (!state.isPreLayout() && getVerticalSpace() > (getTotalRowCount() * mDecoratedChildHeight)) {
205 | mFirstVisiblePosition = mFirstVisiblePosition % getTotalColumnCount();
206 | childTop = getPaddingTop();
207 |
208 | //If the shift overscrolls the column max, back it off
209 | if ((mFirstVisiblePosition + mVisibleColumnCount) > state.getItemCount()) {
210 | mFirstVisiblePosition = Math.max(state.getItemCount() - mVisibleColumnCount, 0);
211 | childLeft = getPaddingLeft();
212 | }
213 | }
214 |
215 | /*
216 | * Adjust the visible position if out of bounds in the
217 | * new layout. This occurs when the new item count in an adapter
218 | * is much smaller than it was before, and you are scrolled to
219 | * a location where no items would exist.
220 | */
221 | int maxFirstRow = getTotalRowCount() - (mVisibleRowCount-1);
222 | int maxFirstCol = getTotalColumnCount() - (mVisibleColumnCount-1);
223 | boolean isOutOfRowBounds = getFirstVisibleRow() > maxFirstRow;
224 | boolean isOutOfColBounds = getFirstVisibleColumn() > maxFirstCol;
225 | if (isOutOfRowBounds || isOutOfColBounds) {
226 | int firstRow;
227 | if (isOutOfRowBounds) {
228 | firstRow = maxFirstRow;
229 | } else {
230 | firstRow = getFirstVisibleRow();
231 | }
232 | int firstCol;
233 | if (isOutOfColBounds) {
234 | firstCol = maxFirstCol;
235 | } else {
236 | firstCol = getFirstVisibleColumn();
237 | }
238 | mFirstVisiblePosition = firstRow * getTotalColumnCount() + firstCol;
239 |
240 | childLeft = getHorizontalSpace() - (mDecoratedChildWidth * mVisibleColumnCount);
241 | childTop = getVerticalSpace() - (mDecoratedChildHeight * mVisibleRowCount);
242 |
243 | //Correct cases where shifting to the bottom-right overscrolls the top-left
244 | // This happens on data sets too small to scroll in a direction.
245 | if (getFirstVisibleRow() == 0) {
246 | childTop = Math.min(childTop, getPaddingTop());
247 | }
248 | if (getFirstVisibleColumn() == 0) {
249 | childLeft = Math.min(childLeft, getPaddingLeft());
250 | }
251 | }
252 | }
253 |
254 | //Clear all attached views into the recycle bin
255 | detachAndScrapAttachedViews(recycler);
256 |
257 | //Fill the grid for the initial layout of views
258 | fillGrid(DIRECTION_NONE, childLeft, childTop, recycler, state, removedCache);
259 |
260 | //Evaluate any disappearing views that may exist
261 | if (!state.isPreLayout() && !recycler.getScrapList().isEmpty()) {
262 | final List scrapList = recycler.getScrapList();
263 | final HashSet disappearingViews = new HashSet(scrapList.size());
264 |
265 | for (RecyclerView.ViewHolder holder : scrapList) {
266 | final View child = holder.itemView;
267 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
268 | if (!lp.isItemRemoved()) {
269 | disappearingViews.add(child);
270 | }
271 | }
272 |
273 | for (View child : disappearingViews) {
274 | layoutDisappearingView(child);
275 | }
276 | }
277 | }
278 |
279 | @Override
280 | public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
281 | //Completely scrap the existing layout
282 | removeAllViews();
283 | }
284 |
285 | /*
286 | * Rather than continuously checking how many views we can fit
287 | * based on scroll offsets, we simplify the math by computing the
288 | * visible grid as what will initially fit on screen, plus one.
289 | */
290 | private void updateWindowSizing() {
291 | mVisibleColumnCount = (getHorizontalSpace() / mDecoratedChildWidth) + 1;
292 | if (getHorizontalSpace() % mDecoratedChildWidth > 0) {
293 | mVisibleColumnCount++;
294 | }
295 |
296 | //Allow minimum value for small data sets
297 | if (mVisibleColumnCount > getTotalColumnCount()) {
298 | mVisibleColumnCount = getTotalColumnCount();
299 | }
300 |
301 |
302 | mVisibleRowCount = (getVerticalSpace()/ mDecoratedChildHeight) + 1;
303 | if (getVerticalSpace() % mDecoratedChildHeight > 0) {
304 | mVisibleRowCount++;
305 | }
306 |
307 | if (mVisibleRowCount > getTotalRowCount()) {
308 | mVisibleRowCount = getTotalRowCount();
309 | }
310 | }
311 |
312 | private void fillGrid(int direction, RecyclerView.Recycler recycler, RecyclerView.State state) {
313 | fillGrid(direction, 0, 0, recycler, state, null);
314 | }
315 |
316 | private void fillGrid(int direction, int emptyLeft, int emptyTop,
317 | RecyclerView.Recycler recycler,
318 | RecyclerView.State state,
319 | SparseIntArray removedPositions) {
320 | if (mFirstVisiblePosition < 0) mFirstVisiblePosition = 0;
321 | if (mFirstVisiblePosition >= getItemCount()) mFirstVisiblePosition = (getItemCount() - 1);
322 |
323 | /*
324 | * First, we will detach all existing views from the layout.
325 | * detachView() is a lightweight operation that we can use to
326 | * quickly reorder views without a full add/remove.
327 | */
328 | SparseArray viewCache = new SparseArray(getChildCount());
329 | int startLeftOffset = emptyLeft;
330 | int startTopOffset = emptyTop;
331 | if (getChildCount() != 0) {
332 | final View topView = getChildAt(0);
333 | startLeftOffset = getDecoratedLeft(topView);
334 | startTopOffset = getDecoratedTop(topView);
335 | switch (direction) {
336 | case DIRECTION_START:
337 | startLeftOffset -= mDecoratedChildWidth;
338 | break;
339 | case DIRECTION_END:
340 | startLeftOffset += mDecoratedChildWidth;
341 | break;
342 | case DIRECTION_UP:
343 | startTopOffset -= mDecoratedChildHeight;
344 | break;
345 | case DIRECTION_DOWN:
346 | startTopOffset += mDecoratedChildHeight;
347 | break;
348 | }
349 |
350 | //Cache all views by their existing position, before updating counts
351 | for (int i=0; i < getChildCount(); i++) {
352 | int position = positionOfIndex(i);
353 | final View child = getChildAt(i);
354 | viewCache.put(position, child);
355 | }
356 |
357 | //Temporarily detach all views.
358 | // Views we still need will be added back at the proper index.
359 | for (int i=0; i < viewCache.size(); i++) {
360 | detachView(viewCache.valueAt(i));
361 | }
362 | }
363 |
364 | /*
365 | * Next, we advance the visible position based on the fill direction.
366 | * DIRECTION_NONE doesn't advance the position in any direction.
367 | */
368 | switch (direction) {
369 | case DIRECTION_START:
370 | mFirstVisiblePosition--;
371 | break;
372 | case DIRECTION_END:
373 | mFirstVisiblePosition++;
374 | break;
375 | case DIRECTION_UP:
376 | mFirstVisiblePosition -= getTotalColumnCount();
377 | break;
378 | case DIRECTION_DOWN:
379 | mFirstVisiblePosition += getTotalColumnCount();
380 | break;
381 | }
382 |
383 | /*
384 | * Next, we supply the grid of items that are deemed visible.
385 | * If these items were previously there, they will simply be
386 | * re-attached. New views that must be created are obtained
387 | * from the Recycler and added.
388 | */
389 | int leftOffset = startLeftOffset;
390 | int topOffset = startTopOffset;
391 |
392 | for (int i = 0; i < getVisibleChildCount(); i++) {
393 | int nextPosition = positionOfIndex(i);
394 |
395 | /*
396 | * When a removal happens out of bounds, the pre-layout positions of items
397 | * after the removal are shifted to their final positions ahead of schedule.
398 | * We have to track off-screen removals and shift those positions back
399 | * so we can properly lay out all current (and appearing) views in their
400 | * initial locations.
401 | */
402 | int offsetPositionDelta = 0;
403 | if (state.isPreLayout()) {
404 | int offsetPosition = nextPosition;
405 |
406 | for (int offset = 0; offset < removedPositions.size(); offset++) {
407 | //Look for off-screen removals that are less-than this
408 | if (removedPositions.valueAt(offset) == REMOVE_INVISIBLE
409 | && removedPositions.keyAt(offset) < nextPosition) {
410 | //Offset position to match
411 | offsetPosition--;
412 | }
413 | }
414 | offsetPositionDelta = nextPosition - offsetPosition;
415 | nextPosition = offsetPosition;
416 | }
417 |
418 | if (nextPosition < 0 || nextPosition >= state.getItemCount()) {
419 | //Item space beyond the data set, don't attempt to add a view
420 | continue;
421 | }
422 |
423 | //Layout this position
424 | View view = viewCache.get(nextPosition);
425 | if (view == null) {
426 | /*
427 | * The Recycler will give us either a newly constructed view,
428 | * or a recycled view it has on-hand. In either case, the
429 | * view will already be fully bound to the data by the
430 | * adapter for us.
431 | */
432 | view = recycler.getViewForPosition(nextPosition);
433 | addView(view);
434 |
435 | /*
436 | * Update the new view's metadata, but only when this is a real
437 | * layout pass.
438 | */
439 | if (!state.isPreLayout()) {
440 | LayoutParams lp = (LayoutParams) view.getLayoutParams();
441 | lp.row = getGlobalRowOfPosition(nextPosition);
442 | lp.column = getGlobalColumnOfPosition(nextPosition);
443 | }
444 |
445 | /*
446 | * It is prudent to measure/layout each new view we
447 | * receive from the Recycler. We don't have to do
448 | * this for views we are just re-arranging.
449 | */
450 | measureChildWithMargins(view, 0, 0);
451 | layoutDecorated(view, leftOffset, topOffset,
452 | leftOffset + mDecoratedChildWidth,
453 | topOffset + mDecoratedChildHeight);
454 |
455 | } else {
456 | //Re-attach the cached view at its new index
457 | attachView(view);
458 | viewCache.remove(nextPosition);
459 | }
460 |
461 | if (i % mVisibleColumnCount == (mVisibleColumnCount - 1)) {
462 | leftOffset = startLeftOffset;
463 | topOffset += mDecoratedChildHeight;
464 |
465 | //During pre-layout, on each column end, apply any additional appearing views
466 | if (state.isPreLayout()) {
467 | layoutAppearingViews(recycler, view, nextPosition, removedPositions.size(), offsetPositionDelta);
468 | }
469 | } else {
470 | leftOffset += mDecoratedChildWidth;
471 | }
472 | }
473 |
474 | /*
475 | * Finally, we ask the Recycler to scrap and store any views
476 | * that we did not re-attach. These are views that are not currently
477 | * necessary because they are no longer visible.
478 | */
479 | for (int i=0; i < viewCache.size(); i++) {
480 | final View removingView = viewCache.valueAt(i);
481 | recycler.recycleView(removingView);
482 | }
483 | }
484 |
485 | /*
486 | * You must override this method if you would like to support external calls
487 | * to shift the view to a given adapter position. In our implementation, this
488 | * is the same as doing a fresh layout with the given position as the top-left
489 | * (or first visible), so we simply set that value and trigger onLayoutChildren()
490 | */
491 | @Override
492 | public void scrollToPosition(int position) {
493 | if (position >= getItemCount()) {
494 | Log.e(TAG, "Cannot scroll to "+position+", item count is "+getItemCount());
495 | return;
496 | }
497 |
498 | //Set requested position as first visible
499 | mFirstVisiblePosition = position;
500 | //Toss all existing views away
501 | removeAllViews();
502 | //Trigger a new view layout
503 | requestLayout();
504 | }
505 |
506 | /*
507 | * You must override this method if you would like to support external calls
508 | * to animate a change to a new adapter position. The framework provides a
509 | * helper scroller implementation (LinearSmoothScroller), which we leverage
510 | * to do the animation calculations.
511 | */
512 | @Override
513 | public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, final int position) {
514 | if (position >= getItemCount()) {
515 | Log.e(TAG, "Cannot scroll to "+position+", item count is "+getItemCount());
516 | return;
517 | }
518 |
519 | /*
520 | * LinearSmoothScroller's default behavior is to scroll the contents until
521 | * the child is fully visible. It will snap to the top-left or bottom-right
522 | * of the parent depending on whether the direction of travel was positive
523 | * or negative.
524 | */
525 | LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
526 | /*
527 | * LinearSmoothScroller, at a minimum, just need to know the vector
528 | * (x/y distance) to travel in order to get from the current positioning
529 | * to the target.
530 | */
531 | @Override
532 | public PointF computeScrollVectorForPosition(int targetPosition) {
533 | final int rowOffset = getGlobalRowOfPosition(targetPosition)
534 | - getGlobalRowOfPosition(mFirstVisiblePosition);
535 | final int columnOffset = getGlobalColumnOfPosition(targetPosition)
536 | - getGlobalColumnOfPosition(mFirstVisiblePosition);
537 |
538 | return new PointF(columnOffset * mDecoratedChildWidth, rowOffset * mDecoratedChildHeight);
539 | }
540 | };
541 | scroller.setTargetPosition(position);
542 | startSmoothScroll(scroller);
543 | }
544 |
545 | /*
546 | * Use this method to tell the RecyclerView if scrolling is even possible
547 | * in the horizontal direction.
548 | */
549 | @Override
550 | public boolean canScrollHorizontally() {
551 | //We do allow scrolling
552 | return true;
553 | }
554 |
555 | /*
556 | * This method describes how far RecyclerView thinks the contents should scroll horizontally.
557 | * You are responsible for verifying edge boundaries, and determining if this scroll
558 | * event somehow requires that new views be added or old views get recycled.
559 | */
560 | @Override
561 | public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
562 | if (getChildCount() == 0) {
563 | return 0;
564 | }
565 |
566 | //Take leftmost measurements from the top-left child
567 | final View topView = getChildAt(0);
568 | //Take rightmost measurements from the top-right child
569 | final View bottomView = getChildAt(mVisibleColumnCount-1);
570 |
571 | //Optimize the case where the entire data set is too small to scroll
572 | int viewSpan = getDecoratedRight(bottomView) - getDecoratedLeft(topView);
573 | if (viewSpan < getHorizontalSpace()) {
574 | //We cannot scroll in either direction
575 | return 0;
576 | }
577 |
578 | int delta;
579 | boolean leftBoundReached = getFirstVisibleColumn() == 0;
580 | boolean rightBoundReached = getLastVisibleColumn() >= getTotalColumnCount();
581 | if (dx > 0) { // Contents are scrolling left
582 | //Check right bound
583 | if (rightBoundReached) {
584 | //If we've reached the last column, enforce limits
585 | int rightOffset = getHorizontalSpace() - getDecoratedRight(bottomView) + getPaddingRight();
586 | delta = Math.max(-dx, rightOffset);
587 | } else {
588 | //No limits while the last column isn't visible
589 | delta = -dx;
590 | }
591 | } else { // Contents are scrolling right
592 | //Check left bound
593 | if (leftBoundReached) {
594 | int leftOffset = -getDecoratedLeft(topView) + getPaddingLeft();
595 | delta = Math.min(-dx, leftOffset);
596 | } else {
597 | delta = -dx;
598 | }
599 | }
600 |
601 | offsetChildrenHorizontal(delta);
602 |
603 | if (dx > 0) {
604 | if (getDecoratedRight(topView) < 0 && !rightBoundReached) {
605 | fillGrid(DIRECTION_END, recycler, state);
606 | } else if (!rightBoundReached) {
607 | fillGrid(DIRECTION_NONE, recycler, state);
608 | }
609 | } else {
610 | if (getDecoratedLeft(topView) > 0 && !leftBoundReached) {
611 | fillGrid(DIRECTION_START, recycler, state);
612 | } else if (!leftBoundReached) {
613 | fillGrid(DIRECTION_NONE, recycler, state);
614 | }
615 | }
616 |
617 | /*
618 | * Return value determines if a boundary has been reached
619 | * (for edge effects and flings). If returned value does not
620 | * match original delta (passed in), RecyclerView will draw
621 | * an edge effect.
622 | */
623 | return -delta;
624 | }
625 |
626 | /*
627 | * Use this method to tell the RecyclerView if scrolling is even possible
628 | * in the vertical direction.
629 | */
630 | @Override
631 | public boolean canScrollVertically() {
632 | //We do allow scrolling
633 | return true;
634 | }
635 |
636 | /*
637 | * This method describes how far RecyclerView thinks the contents should scroll vertically.
638 | * You are responsible for verifying edge boundaries, and determining if this scroll
639 | * event somehow requires that new views be added or old views get recycled.
640 | */
641 | @Override
642 | public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
643 | if (getChildCount() == 0) {
644 | return 0;
645 | }
646 |
647 | //Take top measurements from the top-left child
648 | final View topView = getChildAt(0);
649 | //Take bottom measurements from the bottom-right child.
650 | final View bottomView = getChildAt(getChildCount()-1);
651 |
652 | //Optimize the case where the entire data set is too small to scroll
653 | int viewSpan = getDecoratedBottom(bottomView) - getDecoratedTop(topView);
654 | if (viewSpan < getVerticalSpace()) {
655 | //We cannot scroll in either direction
656 | return 0;
657 | }
658 |
659 | int delta;
660 | int maxRowCount = getTotalRowCount();
661 | boolean topBoundReached = getFirstVisibleRow() == 0;
662 | boolean bottomBoundReached = getLastVisibleRow() >= maxRowCount;
663 | if (dy > 0) { // Contents are scrolling up
664 | //Check against bottom bound
665 | if (bottomBoundReached) {
666 | //If we've reached the last row, enforce limits
667 | int bottomOffset;
668 | if (rowOfIndex(getChildCount() - 1) >= (maxRowCount - 1)) {
669 | //We are truly at the bottom, determine how far
670 | bottomOffset = getVerticalSpace() - getDecoratedBottom(bottomView)
671 | + getPaddingBottom();
672 | } else {
673 | /*
674 | * Extra space added to account for allowing bottom space in the grid.
675 | * This occurs when the overlap in the last row is not large enough to
676 | * ensure that at least one element in that row isn't fully recycled.
677 | */
678 | bottomOffset = getVerticalSpace() - (getDecoratedBottom(bottomView)
679 | + mDecoratedChildHeight) + getPaddingBottom();
680 | }
681 |
682 | delta = Math.max(-dy, bottomOffset);
683 | } else {
684 | //No limits while the last row isn't visible
685 | delta = -dy;
686 | }
687 | } else { // Contents are scrolling down
688 | //Check against top bound
689 | if (topBoundReached) {
690 | int topOffset = -getDecoratedTop(topView) + getPaddingTop();
691 |
692 | delta = Math.min(-dy, topOffset);
693 | } else {
694 | delta = -dy;
695 | }
696 | }
697 |
698 | offsetChildrenVertical(delta);
699 |
700 | if (dy > 0) {
701 | if (getDecoratedBottom(topView) < 0 && !bottomBoundReached) {
702 | fillGrid(DIRECTION_DOWN, recycler, state);
703 | } else if (!bottomBoundReached) {
704 | fillGrid(DIRECTION_NONE, recycler, state);
705 | }
706 | } else {
707 | if (getDecoratedTop(topView) > 0 && !topBoundReached) {
708 | fillGrid(DIRECTION_UP, recycler, state);
709 | } else if (!topBoundReached) {
710 | fillGrid(DIRECTION_NONE, recycler, state);
711 | }
712 | }
713 |
714 | /*
715 | * Return value determines if a boundary has been reached
716 | * (for edge effects and flings). If returned value does not
717 | * match original delta (passed in), RecyclerView will draw
718 | * an edge effect.
719 | */
720 | return -delta;
721 | }
722 |
723 | /*
724 | * This is a helper method used by RecyclerView to determine
725 | * if a specific child view can be returned.
726 | */
727 | @Override
728 | public View findViewByPosition(int position) {
729 | for (int i=0; i < getChildCount(); i++) {
730 | if (positionOfIndex(i) == position) {
731 | return getChildAt(i);
732 | }
733 | }
734 |
735 | return null;
736 | }
737 |
738 | /** Boilerplate to extend LayoutParams for tracking row/column of attached views */
739 |
740 | /*
741 | * Even without extending LayoutParams, we must override this method
742 | * to provide the default layout parameters that each child view
743 | * will receive when added.
744 | */
745 | @Override
746 | public RecyclerView.LayoutParams generateDefaultLayoutParams() {
747 | return new LayoutParams(
748 | ViewGroup.LayoutParams.WRAP_CONTENT,
749 | ViewGroup.LayoutParams.WRAP_CONTENT);
750 | }
751 | @Override
752 | public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
753 | return new LayoutParams(c, attrs);
754 | }
755 | @Override
756 | public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
757 | if (lp instanceof ViewGroup.MarginLayoutParams) {
758 | return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
759 | } else {
760 | return new LayoutParams(lp);
761 | }
762 | }
763 | @Override
764 | public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
765 | return lp instanceof LayoutParams;
766 | }
767 |
768 | public static class LayoutParams extends RecyclerView.LayoutParams {
769 |
770 | //Current row in the grid
771 | public int row;
772 | //Current column in the grid
773 | public int column;
774 |
775 | public LayoutParams(Context c, AttributeSet attrs) {
776 | super(c, attrs);
777 | }
778 | public LayoutParams(int width, int height) {
779 | super(width, height);
780 | }
781 | public LayoutParams(ViewGroup.MarginLayoutParams source) {
782 | super(source);
783 | }
784 | public LayoutParams(ViewGroup.LayoutParams source) {
785 | super(source);
786 | }
787 | public LayoutParams(RecyclerView.LayoutParams source) {
788 | super(source);
789 | }
790 | }
791 |
792 | /** Animation Layout Helpers */
793 |
794 | /* Helper to obtain and place extra appearing views */
795 | private void layoutAppearingViews(RecyclerView.Recycler recycler, View referenceView, int referencePosition, int extraCount, int offset) {
796 | //Nothing to do...
797 | if (extraCount < 1) return;
798 |
799 | //FIXME: This code currently causes double layout of views that are still visible…
800 | for (int extra = 1; extra <= extraCount; extra++) {
801 | //Grab the next position after the reference
802 | final int extraPosition = referencePosition + extra;
803 | if (extraPosition < 0 || extraPosition >= getItemCount()) {
804 | //Can't do anything with this
805 | continue;
806 | }
807 |
808 | /*
809 | * Obtain additional position views that we expect to appear
810 | * as part of the animation.
811 | */
812 | View appearing = recycler.getViewForPosition(extraPosition);
813 | addView(appearing);
814 |
815 | //Find layout delta from reference position
816 | final int newRow = getGlobalRowOfPosition(extraPosition + offset);
817 | final int rowDelta = newRow - getGlobalRowOfPosition(referencePosition + offset);
818 | final int newCol = getGlobalColumnOfPosition(extraPosition + offset);
819 | final int colDelta = newCol - getGlobalColumnOfPosition(referencePosition + offset);
820 |
821 | layoutTempChildView(appearing, rowDelta, colDelta, referenceView);
822 | }
823 | }
824 |
825 | /* Helper to place a disappearing view */
826 | private void layoutDisappearingView(View disappearingChild) {
827 | /*
828 | * LayoutManager has a special method for attaching views that
829 | * will only be around long enough to animate.
830 | */
831 | addDisappearingView(disappearingChild);
832 |
833 | //Adjust each disappearing view to its proper place
834 | final LayoutParams lp = (LayoutParams) disappearingChild.getLayoutParams();
835 |
836 | final int newRow = getGlobalRowOfPosition(lp.getViewAdapterPosition());
837 | final int rowDelta = newRow - lp.row;
838 | final int newCol = getGlobalColumnOfPosition(lp.getViewAdapterPosition());
839 | final int colDelta = newCol - lp.column;
840 |
841 | layoutTempChildView(disappearingChild, rowDelta, colDelta, disappearingChild);
842 | }
843 |
844 |
845 | /* Helper to lay out appearing/disappearing children */
846 | private void layoutTempChildView(View child, int rowDelta, int colDelta, View referenceView) {
847 | //Set the layout position to the global row/column difference from the reference view
848 | int layoutTop = getDecoratedTop(referenceView) + rowDelta * mDecoratedChildHeight;
849 | int layoutLeft = getDecoratedLeft(referenceView) + colDelta * mDecoratedChildWidth;
850 |
851 | measureChildWithMargins(child, 0, 0);
852 | layoutDecorated(child, layoutLeft, layoutTop,
853 | layoutLeft + mDecoratedChildWidth,
854 | layoutTop + mDecoratedChildHeight);
855 | }
856 |
857 | /** Private Helpers and Metrics Accessors */
858 |
859 | /* Return the overall column index of this position in the global layout */
860 | private int getGlobalColumnOfPosition(int position) {
861 | return position % mTotalColumnCount;
862 | }
863 | /* Return the overall row index of this position in the global layout */
864 | private int getGlobalRowOfPosition(int position) {
865 | return position / mTotalColumnCount;
866 | }
867 |
868 | /*
869 | * Mapping between child view indices and adapter data
870 | * positions helps fill the proper views during scrolling.
871 | */
872 | private int positionOfIndex(int childIndex) {
873 | int row = childIndex / mVisibleColumnCount;
874 | int column = childIndex % mVisibleColumnCount;
875 |
876 | return mFirstVisiblePosition + (row * getTotalColumnCount()) + column;
877 | }
878 |
879 | private int rowOfIndex(int childIndex) {
880 | int position = positionOfIndex(childIndex);
881 |
882 | return position / getTotalColumnCount();
883 | }
884 |
885 | private int getFirstVisibleColumn() {
886 | return (mFirstVisiblePosition % getTotalColumnCount());
887 | }
888 |
889 | private int getLastVisibleColumn() {
890 | return getFirstVisibleColumn() + mVisibleColumnCount;
891 | }
892 |
893 | private int getFirstVisibleRow() {
894 | return (mFirstVisiblePosition / getTotalColumnCount());
895 | }
896 |
897 | private int getLastVisibleRow() {
898 | return getFirstVisibleRow() + mVisibleRowCount;
899 | }
900 |
901 | private int getVisibleChildCount() {
902 | return mVisibleColumnCount * mVisibleRowCount;
903 | }
904 |
905 | private int getTotalColumnCount() {
906 | if (getItemCount() < mTotalColumnCount) {
907 | return getItemCount();
908 | }
909 |
910 | return mTotalColumnCount;
911 | }
912 |
913 | private int getTotalRowCount() {
914 | if (getItemCount() == 0 || mTotalColumnCount == 0) {
915 | return 0;
916 | }
917 | int maxRow = getItemCount() / mTotalColumnCount;
918 | //Bump the row count if it's not exactly even
919 | if (getItemCount() % mTotalColumnCount != 0) {
920 | maxRow++;
921 | }
922 |
923 | return maxRow;
924 | }
925 |
926 | private int getHorizontalSpace() {
927 | return getWidth() - getPaddingRight() - getPaddingLeft();
928 | }
929 |
930 | private int getVerticalSpace() {
931 | return getHeight() - getPaddingBottom() - getPaddingTop();
932 | }
933 | }
934 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/drawer_shadow.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/drawable-hdpi/drawer_shadow.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/drawer_shadow.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/drawable-mdpi/drawer_shadow.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_game.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
12 |
16 |
17 |
22 |
24 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_navigation_drawer.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_recycler.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_match_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
30 |
31 |
41 |
42 |
52 |
53 |
60 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/grid_options.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 240dp
5 |
6 | 8dp
7 | 240dp
8 | 240dp
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RecyclerPlayground
5 | Vertical List
6 | Horizontal List
7 | Vertical Grid
8 | Vertical Staggered Grid
9 | Fixed Two-Way List
10 | Open navigation drawer
11 | Close navigation drawer
12 |
13 | Empty
14 | Small Grid
15 | Medium Grid
16 | Large Grid
17 | Scroll Home
18 | Smooth Scroll Home
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Settings specified in this file will override any Gradle settings
5 | # configured through the IDE.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devunwired/recyclerview-playground/299515e0cfe4caea78eaf7ba12f7c9cf926b6063/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Apr 18 11:29:54 MDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------