38 | implements Filterable, CursorFilter.CursorFilterClient {
39 |
40 | protected static final String TAG = "BaseCursorAdapter";
41 |
42 | protected Cursor mCursor;
43 | protected CursorFilter mCursorFilter;
44 | protected FilterQueryProvider mFilterQueryProvider;
45 | protected boolean mDataValid;
46 | protected int mRowIDColumn;
47 | private boolean mEmptyEnable;
48 |
49 | /**
50 | * Constructor that disallows control over auto-requery. As an alternative,
51 | * use {@link android.app.LoaderManager}/{@link android.support.v4.app.LoaderManager}
52 | * with a {@link android.content.CursorLoader}/{@link android.support.v4.content.CursorLoader}
53 | *
54 | * @param c The cursor from which to get the data.
55 | * @param context The context
56 | */
57 | public BaseCursorAdapter(Context context, Cursor c) {
58 | super(context);
59 | boolean cursorPresent = c != null;
60 | mCursor = c;
61 | mDataValid = cursorPresent;
62 | mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
63 | }
64 |
65 | @Override
66 | public Cursor getItem(int position) {
67 | if (mDataValid && mCursor != null) {
68 | mCursor.moveToPosition(position);
69 | return mCursor;
70 | } else {
71 | return null;
72 | }
73 | }
74 |
75 | @Override
76 | public int getItemCount() {
77 |
78 | int count = 0;
79 | if (mDataValid && mCursor != null) {
80 | count = mCursor.getCount() + getHeaderViewCount() + getFooterViewCount();
81 | }
82 |
83 | mEmptyEnable = false;
84 | if (count == 0) {
85 | mEmptyEnable = true;
86 | count += getEmptyViewCount();
87 | }
88 | return count;
89 | }
90 |
91 | @Override
92 | public long getItemId(int position) {
93 | if (mDataValid && mCursor != null) {
94 | if (mCursor.moveToPosition(position)) {
95 | return mCursor.getLong(mRowIDColumn);
96 | } else {
97 | return -1;
98 | }
99 | } else {
100 | return -1;
101 | }
102 | }
103 |
104 | @Override
105 | protected boolean isEmpty() {
106 | return getHeaderViewCount() + getFooterViewCount() + mCursor.getCount() == 0;
107 | }
108 |
109 | @Override
110 | public final int getItemViewType(int position) {
111 | if (mHeaderView != null && position == 0) {
112 | return TYPE_HEADER_VIEW;
113 | } else if (mEmptyView != null && getItemCount() == 1 && mEmptyEnable) {
114 | return TYPE_EMPTY_VIEW;
115 | } else if (mDataValid && mCursor != null && mFooterView != null
116 | && position == mCursor.getCount() + getHeaderViewCount()) {
117 | return TYPE_FOOTER_VIEW;
118 | }
119 | return getDefItemViewType(position);
120 | }
121 |
122 | @Override
123 | protected final void bindHolder(BaseViewHolder holder, int position) {
124 | if (!mDataValid) {
125 | throw new IllegalStateException("this should only be called when the cursor is valid");
126 | }
127 | if (!mCursor.moveToPosition(position - getHeaderViewCount())) {
128 | throw new IllegalStateException("couldn't move cursor to position " + position);
129 | }
130 | convert((VH) holder, mCursor);
131 | }
132 |
133 |
134 | /**
135 | * Implement this method and use the helper to adapt the view to the given item.
136 | *
137 | * @param holder A fully initialized helper.
138 | * @param cursor The cursor that needs to be displayed.
139 | */
140 | abstract protected void convert(VH holder, Cursor cursor);
141 |
142 |
143 | @Override
144 | public Filter getFilter() {
145 | if (mCursorFilter == null) {
146 | mCursorFilter = new CursorFilter(this);
147 | }
148 | return mCursorFilter;
149 | }
150 |
151 | /**
152 | * Converts the cursor into a CharSequence. Subclasses should override this
153 | * method to convert their results. The default implementation returns an
154 | * empty String for null values or the default String representation of
155 | * the value.
156 | *
157 | * @param cursor the cursor to convert to a CharSequence
158 | * @return a CharSequence representing the value
159 | */
160 | @Override
161 | public CharSequence convertToString(Cursor cursor) {
162 | return cursor == null ? "" : cursor.toString();
163 | }
164 |
165 | /**
166 | * Runs a query with the specified constraint. This query is requested
167 | * by the filter attached to this adapter.
168 | *
169 | * The query is provided by a
170 | * {@link android.widget.FilterQueryProvider}.
171 | * If no provider is specified, the current cursor is not filtered and returned.
172 | *
173 | * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
174 | * and the previous cursor is closed.
175 | *
176 | * This method is always executed on a background thread, not on the
177 | * application's main thread (or UI thread.)
178 | *
179 | * Contract: when constraint is null or empty, the original results,
180 | * prior to any filtering, must be returned.
181 | *
182 | * @param constraint the constraint with which the query must be filtered
183 | * @return a Cursor representing the results of the new query
184 | * @see #getFilter()
185 | * @see #getFilterQueryProvider()
186 | * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
187 | */
188 | @Override
189 | public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
190 | if (mFilterQueryProvider != null) {
191 | return mFilterQueryProvider.runQuery(constraint);
192 | }
193 |
194 | return mCursor;
195 | }
196 |
197 |
198 | /**
199 | * Returns the query filter provider used for filtering. When the
200 | * provider is null, no filtering occurs.
201 | *
202 | * @return the current filter query provider or null if it does not exist
203 | * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
204 | * @see #runQueryOnBackgroundThread(CharSequence)
205 | */
206 | public FilterQueryProvider getFilterQueryProvider() {
207 | return mFilterQueryProvider;
208 | }
209 |
210 | /**
211 | * Sets the query filter provider used to filter the current Cursor.
212 | * The provider's
213 | * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
214 | * method is invoked when filtering is requested by a client of
215 | * this adapter.
216 | *
217 | * @param filterQueryProvider the filter query provider or null to remove it
218 | * @see #getFilterQueryProvider()
219 | * @see #runQueryOnBackgroundThread(CharSequence)
220 | */
221 | public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
222 | mFilterQueryProvider = filterQueryProvider;
223 | }
224 |
225 | @Override
226 | public Cursor getCursor() {
227 | return mCursor;
228 | }
229 |
230 | /**
231 | * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
232 | * closed.
233 | *
234 | * @param cursor The new cursor to be used
235 | */
236 | @Override
237 | public void changeCursor(Cursor cursor) {
238 | Cursor old = swapCursor(cursor);
239 | if (old != null) {
240 | old.close();
241 | }
242 | }
243 |
244 | /**
245 | * Swap in a new Cursor, returning the old Cursor. Unlike
246 | * {@link #changeCursor(Cursor)}, the returned old Cursor is not
247 | * closed.
248 | *
249 | * @param newCursor The new cursor to be used.
250 | * @return Returns the previously set Cursor, or null if there was not one.
251 | * If the given new Cursor is the same instance is the previously set
252 | * Cursor, null is also returned.
253 | */
254 | public Cursor swapCursor(Cursor newCursor) {
255 | if (newCursor == mCursor) {
256 | return null;
257 | }
258 | Cursor oldCursor = mCursor;
259 |
260 | mCursor = newCursor;
261 | if (newCursor != null) {
262 | mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
263 | mDataValid = true;
264 | // notify the observers about the new cursor
265 | notifyDataSetChanged();
266 | } else {
267 | mRowIDColumn = -1;
268 | mDataValid = false;
269 | // notify the observers about the lack of a data set
270 | notifyDataSetChanged();
271 | // notifyItemRangeRemoved(0, oldCursor.getCount() - 1);
272 | }
273 | return oldCursor;
274 | }
275 |
276 | }
277 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/BaseTurboAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo;
17 |
18 | import android.content.Context;
19 | import android.util.Log;
20 |
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | /**
25 | * author: imilk
26 | * https://github.com/Solartisan/TurboRecyclerViewHelper
27 | */
28 | public abstract class BaseTurboAdapter extends AbsTurboAdapter implements OnLoadMoreListener {
29 |
30 | protected static final String TAG = "BaseTurboAdapter";
31 |
32 | protected List mData;
33 | private boolean mLoading = false;
34 | private boolean mEmptyEnable;
35 |
36 |
37 | public BaseTurboAdapter(Context context) {
38 | this(context, null);
39 | }
40 |
41 | /**
42 | * initialization
43 | *
44 | * @param context The context.
45 | * @param data A new list is created out of this one to avoid mutable list
46 | */
47 | public BaseTurboAdapter(Context context, List data) {
48 | super(context);
49 | this.mData = data == null ? new ArrayList() : new ArrayList(data);
50 | }
51 |
52 | public void add(T item) {
53 | boolean isAdd = mData.add(item);
54 | if (isAdd)
55 | notifyItemInserted(mData.size() + getHeaderViewCount());
56 | }
57 |
58 | public void add(int position, T item) {
59 | if (position < 0 || position > mData.size()) {
60 | Log.e(TAG, "add position = " + position + ", IndexOutOfBounds, please check your code!");
61 | return;
62 | }
63 | mData.add(position, item);
64 | notifyItemInserted(position + getHeaderViewCount());
65 | }
66 |
67 | public void remove(T item) {
68 | int index = mData.indexOf(item);
69 | boolean isRemoved = mData.remove(item);
70 | if (isRemoved)
71 | notifyItemRemoved(index + getHeaderViewCount());
72 | }
73 |
74 | public void remove(int position) {
75 | if (position < 0 || position >= mData.size()) {
76 | Log.e(TAG, "remove position = " + position + ", IndexOutOfBounds, please check your code!");
77 | return;
78 | }
79 | mData.remove(position);
80 | notifyItemRemoved(position + getHeaderViewCount());
81 | }
82 |
83 | /**
84 | *
85 | * @param data additional data
86 | */
87 | public void addData(List data) {
88 | if (data != null) {
89 | int pos = getItemCount();
90 | this.mData.addAll(data);
91 | notifyItemRangeInserted(pos, data.size() - 1);
92 | }
93 | }
94 |
95 | public void removeData(List data) {
96 | if (data != null) {
97 | this.mData.removeAll(data);
98 | notifyDataSetChanged();
99 | }
100 | }
101 |
102 | public void resetData(List data) {
103 | mData.clear();
104 | addData(data);
105 | }
106 |
107 |
108 | public List getData() {
109 | return mData;
110 | }
111 |
112 |
113 | /**
114 | * Get the data item associated with the specified position in the data set.
115 | *
116 | * @param position Position of the item whose data we want within the adapter's
117 | * data set.
118 | * @return The data at the specified position.
119 | */
120 | @Override
121 | public T getItem(int position) {
122 | if (position < 0 || position >= mData.size()) {
123 | Log.e(TAG, "getItem position = " + position + ", IndexOutOfBounds, please check your code!");
124 | return null;
125 | }
126 | return mData.get(position);
127 | }
128 |
129 | @Override
130 | public long getItemId(int position) {
131 | return position;
132 | }
133 |
134 | @Override
135 | protected boolean isEmpty() {
136 | return getHeaderViewCount() + getFooterViewCount() + getData().size() == 0;
137 | }
138 |
139 | @Override
140 | public int getItemCount() {
141 |
142 | int count;
143 | if (mLoading) { //if loading ignore footer view
144 | count = mData.size() + 1 + getHeaderViewCount();
145 | } else {
146 | count = mData.size() + getHeaderViewCount() + getFooterViewCount();
147 | }
148 | mEmptyEnable = false;
149 | if (count == 0) {
150 | mEmptyEnable = true;
151 | count += getEmptyViewCount();
152 | }
153 | return count;
154 | }
155 |
156 |
157 | @Override
158 | public final int getItemViewType(int position) {
159 | if (mHeaderView != null && position == 0) {
160 | return TYPE_HEADER_VIEW;
161 | } else if (mEmptyView != null && getItemCount() == 1 && mEmptyEnable) {
162 | return TYPE_EMPTY_VIEW;
163 | } else if (position == mData.size() + getHeaderViewCount()) {
164 | if (mLoading) {
165 | return TYPE_LOADING_VIEW;
166 | } else if (mFooterView != null) {
167 | return TYPE_FOOTER_VIEW;
168 | }
169 | }
170 | return getDefItemViewType(position);
171 | }
172 |
173 | @Override
174 | protected final void bindHolder(BaseViewHolder holder, int position) {
175 | convert((VH) holder, mData.get(holder.getLayoutPosition() - getHeaderViewCount()));
176 | }
177 |
178 | /**
179 | * Implement this method and use the helper to adapt the view to the given item.
180 | *
181 | * @param holder A fully initialized helper.
182 | * @param item The item that needs to be displayed.
183 | */
184 | abstract protected void convert(VH holder, T item);
185 |
186 | @Override
187 | public void onLoadingMore() {
188 | if (!mLoading) {
189 | mLoading = true;
190 | notifyItemChanged(getItemCount());
191 | }
192 | }
193 |
194 | void loadingMoreComplete(List data) {
195 | mLoading = false;
196 | addData(data);
197 | }
198 |
199 | }
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/BaseViewHolder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo;
17 |
18 | import android.support.v7.widget.RecyclerView;
19 | import android.util.SparseArray;
20 | import android.view.View;
21 |
22 | /**
23 | * author: imilk
24 | * https://github.com/Solartisan/TurboRecyclerViewHelper
25 | */
26 | public class BaseViewHolder extends RecyclerView.ViewHolder {
27 |
28 | /**
29 | * Views indexed with their IDs
30 | */
31 | private final SparseArray mViews;
32 |
33 |
34 | public BaseViewHolder(View view) {
35 | super(view);
36 | this.mViews = new SparseArray<>();
37 | }
38 |
39 | @SuppressWarnings("unchecked")
40 | public V findViewById(int viewId) {
41 | View view = mViews.get(viewId);
42 | if (view == null) {
43 | view = itemView.findViewById(viewId);
44 | mViews.put(viewId, view);
45 | }
46 | return (V) view;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/CursorFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is cloned from CursorFilter provided by support library
3 | *
4 | * Copyright (C) 2007 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package cc.solart.turbo;
19 |
20 | import android.database.Cursor;
21 | import android.widget.Filter;
22 |
23 | /**
24 | * The CursorFilter delegates most of the work to the CursorAdapter.
25 | * Subclasses should override these delegate methods to run the queries
26 | * and convert the results into String that can be used by auto-completion
27 | * widgets.
28 | */
29 | class CursorFilter extends Filter {
30 |
31 | CursorFilterClient mClient;
32 |
33 | interface CursorFilterClient {
34 | CharSequence convertToString(Cursor cursor);
35 | Cursor runQueryOnBackgroundThread(CharSequence constraint);
36 | Cursor getCursor();
37 | void changeCursor(Cursor cursor);
38 | }
39 |
40 | CursorFilter(CursorFilterClient client) {
41 | mClient = client;
42 | }
43 |
44 | @Override
45 | public CharSequence convertResultToString(Object resultValue) {
46 | return mClient.convertToString((Cursor) resultValue);
47 | }
48 |
49 | @Override
50 | protected FilterResults performFiltering(CharSequence constraint) {
51 | Cursor cursor = mClient.runQueryOnBackgroundThread(constraint);
52 |
53 | FilterResults results = new FilterResults();
54 | if (cursor != null) {
55 | results.count = cursor.getCount();
56 | results.values = cursor;
57 | } else {
58 | results.count = 0;
59 | results.values = null;
60 | }
61 | return results;
62 | }
63 |
64 | @Override
65 | protected void publishResults(CharSequence constraint, FilterResults results) {
66 | Cursor oldCursor = mClient.getCursor();
67 |
68 | if (results.values != null && results.values != oldCursor) {
69 | mClient.changeCursor((Cursor) results.values);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/OnItemClickListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo;
17 |
18 | import android.support.v7.widget.RecyclerView;
19 |
20 | /**
21 | * author: imilk
22 | * https://github.com/Solartisan/TurboRecyclerViewHelper
23 | */
24 | public interface OnItemClickListener {
25 |
26 | void onItemClick(RecyclerView.ViewHolder vh, int position);
27 | }
28 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/OnItemLongClickListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo;
17 |
18 | import android.support.v7.widget.RecyclerView;
19 |
20 | /**
21 | * author: imilk
22 | * https://github.com/Solartisan/TurboRecyclerViewHelper
23 | */
24 | public interface OnItemLongClickListener {
25 | void onItemLongClick(RecyclerView.ViewHolder vh, int position);
26 | }
27 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/OnLoadMoreListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo;
17 |
18 | /**
19 | * author: imilk
20 | * https://github.com/Solartisan/TurboRecyclerViewHelper
21 | */
22 | public interface OnLoadMoreListener {
23 |
24 | void onLoadingMore();
25 | }
26 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/TurboRecyclerView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo;
17 |
18 | import android.animation.ObjectAnimator;
19 | import android.content.Context;
20 | import android.content.res.TypedArray;
21 | import android.support.annotation.Nullable;
22 | import android.support.v4.view.MotionEventCompat;
23 | import android.support.v4.view.ViewCompat;
24 | import android.support.v7.widget.GridLayoutManager;
25 | import android.support.v7.widget.LinearLayoutManager;
26 | import android.support.v7.widget.RecyclerView;
27 | import android.support.v7.widget.StaggeredGridLayoutManager;
28 | import android.util.AttributeSet;
29 | import android.util.Log;
30 | import android.view.MotionEvent;
31 | import android.view.ViewConfiguration;
32 | import android.view.animation.DecelerateInterpolator;
33 | import android.view.animation.Interpolator;
34 |
35 | import java.util.ArrayList;
36 | import java.util.List;
37 |
38 | /**
39 | * A subclass of RecyclerView responsible for providing views that refresh new data set.
40 | *
41 | * author: imilk
42 | * https://github.com/Solartisan/TurboRecyclerViewHelper
43 | */
44 | public class TurboRecyclerView extends RecyclerView {
45 | private static final String TAG = "TurboRecyclerView";
46 |
47 | private static final int DRAG_MAX_DISTANCE = 100;
48 | private static final float DRAG_RATE = .5f;
49 | private static final int INVALID_POINTER = -1;
50 |
51 | private final ArrayList mOnItemTouchListeners =
52 | new ArrayList<>();
53 | private final ArrayList mOnLoadMoreListeners =
54 | new ArrayList<>();
55 |
56 | private OnItemTouchListener mActiveOnItemTouchListener;
57 |
58 | private int mInitialMotionX, mInitialMotionY;
59 | private int mTouchSlop;
60 |
61 | private int mLastVisibleItemPosition;
62 | private int[] mLastPositions;
63 |
64 | private int mTotalDragDistance;
65 |
66 | private boolean mIsLoading;
67 | private boolean mLoadEnabled;
68 |
69 | private int mActivePointerId = INVALID_POINTER;
70 |
71 | private ObjectAnimator mResetAnimator;
72 | private Interpolator mInterpolator = new DecelerateInterpolator();
73 |
74 | private static int convertDpToPixel(Context context, int dp) {
75 | float density = context.getResources().getDisplayMetrics().density;
76 | return Math.round((float) dp * density);
77 | }
78 |
79 | public TurboRecyclerView(Context context) {
80 | this(context, null);
81 | }
82 |
83 | public TurboRecyclerView(Context context, @Nullable AttributeSet attrs) {
84 | this(context, attrs, 0);
85 | }
86 |
87 | public TurboRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
88 | super(context, attrs, defStyle);
89 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
90 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TurboRecyclerView);
91 | int max = ta.getInteger(R.styleable.TurboRecyclerView_maxDragDistance, DRAG_MAX_DISTANCE);
92 | mTotalDragDistance = convertDpToPixel(context, max);
93 | mLoadEnabled = ta.getBoolean(R.styleable.TurboRecyclerView_enableLoad, false);
94 | ta.recycle();
95 | }
96 |
97 | @Override
98 | public void addOnItemTouchListener(OnItemTouchListener listener) {
99 | super.addOnItemTouchListener(listener);
100 | mOnItemTouchListeners.add(listener);
101 | }
102 |
103 | @Override
104 | public void removeOnItemTouchListener(OnItemTouchListener listener) {
105 | super.removeOnItemTouchListener(listener);
106 | mOnItemTouchListeners.remove(listener);
107 | if (mActiveOnItemTouchListener == listener) {
108 | mActiveOnItemTouchListener = null;
109 | }
110 | }
111 |
112 | public void addOnLoadingMoreListener(OnLoadMoreListener listener) {
113 | mOnLoadMoreListeners.add(listener);
114 | }
115 |
116 | public void removeOnLoadingMoreListener(OnLoadMoreListener listener) {
117 | mOnLoadMoreListeners.remove(listener);
118 | }
119 |
120 | public void setLoadMoreEnabled(boolean enabled) {
121 | mLoadEnabled = enabled;
122 | }
123 |
124 | public boolean isLoadMoreEnabled() {
125 | return mLoadEnabled;
126 | }
127 |
128 | @Override
129 | public void setItemAnimator(ItemAnimator animator) {
130 | super.setItemAnimator(animator);
131 | }
132 |
133 | @Override
134 | public void setAdapter(Adapter adapter) {
135 | if (adapter instanceof OnLoadMoreListener) {
136 | addOnLoadingMoreListener((OnLoadMoreListener) adapter);
137 | }
138 | super.setAdapter(adapter);
139 | }
140 |
141 | @Override
142 | protected void onScrollChanged(int l, int t, int oldl, int oldt) {
143 | super.onScrollChanged(l, t, oldl, oldt);
144 | if (getLayoutManager() instanceof LinearLayoutManager) {
145 | mLastVisibleItemPosition = ((LinearLayoutManager) getLayoutManager())
146 | .findLastVisibleItemPosition();
147 | } else if (getLayoutManager() instanceof GridLayoutManager) {
148 | mLastVisibleItemPosition = ((GridLayoutManager) getLayoutManager())
149 | .findLastVisibleItemPosition();
150 | } else if (getLayoutManager() instanceof StaggeredGridLayoutManager) {
151 | StaggeredGridLayoutManager staggeredGridLayoutManager
152 | = (StaggeredGridLayoutManager) getLayoutManager();
153 | if (mLastPositions == null) {
154 | mLastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
155 | }
156 | staggeredGridLayoutManager.findLastVisibleItemPositions(mLastPositions);
157 | mLastVisibleItemPosition = findMax(mLastPositions);
158 | } else {
159 | throw new RuntimeException(
160 | "Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager");
161 | }
162 | }
163 |
164 | private int findMax(int[] lastPositions) {
165 | int max = lastPositions[0];
166 | for (int value : lastPositions) {
167 | if (value > max) {
168 | max = value;
169 | }
170 | }
171 | return max;
172 | }
173 |
174 | private int getMotionEventX(MotionEvent e, int pointerIndex) {
175 | return (int) (MotionEventCompat.getX(e, pointerIndex) + 0.5f);
176 | }
177 |
178 | private int getMotionEventY(MotionEvent e, int pointerIndex) {
179 | return (int) (MotionEventCompat.getY(e, pointerIndex) + 0.5f);
180 | }
181 |
182 | /**
183 | * @return Whether it is possible for the child view of this layout to
184 | * scroll up. Override this if the child view is a custom view.
185 | */
186 | private boolean canScrollEnd() {
187 | return ViewCompat.canScrollVertically(this, 1) || ViewCompat.canScrollHorizontally(this, 1);
188 | }
189 |
190 |
191 | private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
192 | final int action = e.getAction();
193 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
194 | mActiveOnItemTouchListener = null;
195 | }
196 |
197 | final int listenerCount = mOnItemTouchListeners.size();
198 | for (int i = 0; i < listenerCount; i++) {
199 | final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
200 | if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
201 | mActiveOnItemTouchListener = listener;
202 | return true;
203 | }
204 | }
205 | return false;
206 | }
207 |
208 | private boolean dispatchOnItemTouch(MotionEvent e) {
209 | final int action = e.getAction();
210 | if (mActiveOnItemTouchListener != null) {
211 | if (action == MotionEvent.ACTION_DOWN) {
212 | // Stale state from a previous gesture, we're starting a new one. Clear it.
213 | mActiveOnItemTouchListener = null;
214 | } else {
215 | mActiveOnItemTouchListener.onTouchEvent(this, e);
216 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
217 | // Clean up for the next gesture.
218 | mActiveOnItemTouchListener = null;
219 | }
220 | return true;
221 | }
222 | }
223 |
224 | // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
225 | // as called from onInterceptTouchEvent; skip it.
226 | if (action != MotionEvent.ACTION_DOWN) {
227 | final int listenerCount = mOnItemTouchListeners.size();
228 | for (int i = 0; i < listenerCount; i++) {
229 | final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
230 | if (listener.onInterceptTouchEvent(this, e)) {
231 | mActiveOnItemTouchListener = listener;
232 | return true;
233 | }
234 | }
235 | }
236 | return false;
237 | }
238 |
239 | @Override
240 | public boolean onInterceptTouchEvent(MotionEvent e) {
241 | if (!mLoadEnabled || canScrollEnd() || mIsLoading || isEmpty()) {
242 | return super.onInterceptTouchEvent(e);
243 | }
244 |
245 | if (dispatchOnItemTouchIntercept(e)) {
246 | return true;
247 | }
248 |
249 | final int action = MotionEventCompat.getActionMasked(e);
250 | final int actionIndex = MotionEventCompat.getActionIndex(e);
251 | switch (action) {
252 | case MotionEvent.ACTION_DOWN: {
253 | mActivePointerId = MotionEventCompat.getPointerId(e, 0);
254 | mInitialMotionX = getMotionEventX(e, actionIndex);
255 | mInitialMotionY = getMotionEventY(e, actionIndex);
256 | }
257 | break;
258 |
259 | case MotionEvent.ACTION_POINTER_DOWN: {
260 | mActivePointerId = MotionEventCompat.getPointerId(e, actionIndex);
261 | mInitialMotionX = getMotionEventX(e, actionIndex);
262 | mInitialMotionY = getMotionEventY(e, actionIndex);
263 | }
264 | break;
265 |
266 | case MotionEvent.ACTION_UP:
267 | case MotionEvent.ACTION_CANCEL:
268 | mActivePointerId = INVALID_POINTER;
269 | break;
270 | case MotionEventCompat.ACTION_POINTER_UP: {
271 | onPointerUp(e);
272 | }
273 | break;
274 | }
275 | return super.onInterceptTouchEvent(e);
276 | }
277 |
278 | private void onPointerUp(MotionEvent e) {
279 | final int actionIndex = MotionEventCompat.getActionIndex(e);
280 | if (MotionEventCompat.getPointerId(e, actionIndex) == mActivePointerId) {
281 | // Pick a new pointer to pick up the slack.
282 | final int newIndex = actionIndex == 0 ? 1 : 0;
283 | mActivePointerId = MotionEventCompat.getPointerId(e, newIndex);
284 | mInitialMotionX = getMotionEventX(e, newIndex);
285 | mInitialMotionY = getMotionEventY(e, newIndex);
286 | }
287 | }
288 |
289 | private boolean isEmpty() {
290 | if (getAdapter() == null || !(getAdapter() instanceof AbsTurboAdapter)) {
291 | return true;
292 | }
293 | return ((AbsTurboAdapter) getAdapter()).isEmpty();
294 | }
295 |
296 | @Override
297 | public boolean onTouchEvent(MotionEvent e) {
298 | if (!mLoadEnabled || canScrollEnd() || mIsLoading || isEmpty()) {
299 | return super.onTouchEvent(e);
300 | }
301 |
302 | if (dispatchOnItemTouch(e)) {
303 | return true;
304 | }
305 |
306 | if (getLayoutManager() == null) {
307 | return false;
308 | }
309 |
310 | final boolean canScrollHorizontally = getLayoutManager().canScrollHorizontally();
311 | final boolean canScrollVertically = getLayoutManager().canScrollVertically();
312 |
313 | final int action = MotionEventCompat.getActionMasked(e);
314 | switch (action) {
315 | case MotionEvent.ACTION_DOWN: {
316 | final int index = MotionEventCompat.getActionIndex(e);
317 | mActivePointerId = MotionEventCompat.getPointerId(e, 0);
318 | mInitialMotionX = getMotionEventX(e, index);
319 | mInitialMotionY = getMotionEventY(e, index);
320 | }
321 | break;
322 |
323 | case MotionEventCompat.ACTION_POINTER_DOWN: {
324 | final int index = MotionEventCompat.getActionIndex(e);
325 | mActivePointerId = MotionEventCompat.getPointerId(e, index);
326 | mInitialMotionX = getMotionEventX(e, index);
327 | mInitialMotionY = getMotionEventY(e, index);
328 | }
329 | break;
330 |
331 | case MotionEvent.ACTION_MOVE: {
332 |
333 | final int index = MotionEventCompat.findPointerIndex(e, mActivePointerId);
334 | if (index < 0) {
335 | Log.w(TAG, "pointer index for id " + index + " not found. return super");
336 | return super.onTouchEvent(e);
337 | }
338 |
339 | final int x = getMotionEventX(e, index);
340 | final int y = getMotionEventY(e, index);
341 |
342 | int deltaY = y - mInitialMotionY;
343 | if (canScrollVertically && Math.abs(deltaY) > mTouchSlop && deltaY < 0) {
344 | float targetEnd = -dampAxis(deltaY);
345 | setTranslationY(targetEnd);
346 | return true;
347 | }
348 |
349 | int deltaX = x - mInitialMotionX;
350 | if (canScrollHorizontally && Math.abs(deltaX) > mTouchSlop && deltaX < 0) {
351 | float targetEnd = -dampAxis(deltaX);
352 | setTranslationX(targetEnd);
353 | return true;
354 | }
355 | }
356 | break;
357 | case MotionEventCompat.ACTION_POINTER_UP: {
358 | onPointerUp(e);
359 | }
360 | break;
361 | case MotionEvent.ACTION_UP:
362 | case MotionEvent.ACTION_CANCEL: {
363 | if (canScrollHorizontally)
364 | animateOffsetToEnd("translationX", mInterpolator, 0f);
365 | if (canScrollVertically)
366 | animateOffsetToEnd("translationY", mInterpolator, 0f);
367 | final int index = MotionEventCompat.findPointerIndex(e, mActivePointerId);
368 | if (index < 0) {
369 | Log.e(TAG, "Got ACTION_UP event but don't have an active pointer id.");
370 | return super.onTouchEvent(e);
371 | }
372 |
373 | final int y = getMotionEventY(e, index);
374 | final int x = getMotionEventX(e, index);
375 | final float overScrollBottom = (mInitialMotionY - y) * DRAG_RATE;
376 | final float overScrollRight = (mInitialMotionX - x) * DRAG_RATE;
377 |
378 | if ((canScrollVertically && overScrollBottom > mTotalDragDistance)
379 | || (canScrollHorizontally && overScrollRight > mTotalDragDistance)) {
380 | Log.i(TAG, "refreshing...");
381 | mIsLoading = true;
382 | dispatchOnLoadingMoreListeners();
383 | smoothScrollToPosition(mLastVisibleItemPosition + 1);
384 | mActivePointerId = INVALID_POINTER;
385 | return true;
386 | } else {
387 | mIsLoading = false;
388 | mActivePointerId = INVALID_POINTER;
389 | }
390 |
391 | }
392 | break;
393 | }
394 | return super.onTouchEvent(e);
395 | }
396 |
397 | private void dispatchOnLoadingMoreListeners() {
398 | if (mOnLoadMoreListeners != null) {
399 | for (int i = 0; i < mOnLoadMoreListeners.size(); i++) {
400 | final OnLoadMoreListener listener = mOnLoadMoreListeners.get(i);
401 | if (listener != null) {
402 | listener.onLoadingMore();
403 | }
404 | }
405 | }
406 | }
407 |
408 | //Calculating the damping distance of any axis
409 | private float dampAxis(int delta) {
410 | final float scrollEnd = delta * DRAG_RATE;
411 | float mCurrentDragPercent = scrollEnd / mTotalDragDistance;
412 | float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent));
413 | float extraOS = Math.abs(scrollEnd) - mTotalDragDistance;
414 | float slingshotDist = mTotalDragDistance;
415 | float tensionSlingshotPercent = Math.max(0,
416 | Math.min(extraOS, slingshotDist * 2) / slingshotDist);
417 | float tensionPercent = (float) ((tensionSlingshotPercent / 4) -
418 | Math.pow((tensionSlingshotPercent / 4), 2)) * 2f;
419 | float extraMove = (slingshotDist) * tensionPercent / 2;
420 | float targetEnd = (slingshotDist * boundedDragPercent) + extraMove;
421 | return targetEnd;
422 | }
423 |
424 | private void animateOffsetToEnd(final String propertyName, final Interpolator interpolator, float... value) {
425 | if (mResetAnimator == null) {
426 | mResetAnimator = new ObjectAnimator();
427 | mResetAnimator.setTarget(this);
428 | }
429 | mResetAnimator.cancel();
430 | mResetAnimator.setPropertyName(propertyName);
431 | mResetAnimator.setFloatValues(value);
432 | mResetAnimator.setInterpolator(interpolator);
433 | mResetAnimator.start();
434 | }
435 |
436 | public void loadMoreComplete(List> data) {
437 | if (mIsLoading) {
438 | mIsLoading = false;
439 | Adapter adapter = getAdapter();
440 | if (adapter instanceof BaseTurboAdapter) {
441 | ((BaseTurboAdapter) adapter).loadingMoreComplete(data);
442 | } else {
443 | Log.e(TAG, "Cannot callback adapter.");
444 | }
445 | }
446 | }
447 |
448 |
449 | }
450 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/decoration/BaseGridItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo.decoration;
17 |
18 | import android.support.v7.widget.GridLayoutManager;
19 | import android.support.v7.widget.RecyclerView;
20 | import android.support.v7.widget.StaggeredGridLayoutManager;
21 |
22 | /**
23 | * author: imilk
24 | * https://github.com/Solartisan/TurboRecyclerViewHelper
25 | */
26 | public abstract class BaseGridItemDecoration extends BaseItemDecoration {
27 |
28 | public BaseGridItemDecoration(int orientation) {
29 | super(orientation);
30 | }
31 |
32 |
33 | protected boolean isFirstColumn(RecyclerView parent, int position, int spanCount, int childCount) {
34 | if (mOrientation == VERTICAL) {
35 | return position % spanCount == 0;
36 | } else {
37 | return position < spanCount;
38 | }
39 | }
40 |
41 | protected boolean isLastColumn(RecyclerView parent, int position, int spanCount, int childCount) {
42 | if (mOrientation == VERTICAL) {
43 | return (position + 1) % spanCount == 0;
44 | } else {
45 | int lastColumnCount = childCount % spanCount;
46 | lastColumnCount = lastColumnCount == 0 ? spanCount : lastColumnCount;
47 | return position >= childCount - lastColumnCount;
48 | }
49 | }
50 |
51 | protected boolean isFirstRow(RecyclerView parent, int position, int spanCount, int childCount) {
52 | if (mOrientation == VERTICAL) {
53 | return position < spanCount;
54 | } else {
55 | return position % spanCount == 0;
56 | }
57 | }
58 |
59 | protected boolean isLastRow(RecyclerView parent, int position, int spanCount, int childCount) {
60 | if (mOrientation == VERTICAL) {
61 | int lastColumnCount = childCount % spanCount;
62 | lastColumnCount = lastColumnCount == 0 ? spanCount : lastColumnCount;
63 | return position >= childCount - lastColumnCount;
64 | } else {
65 | return (position + 1) % spanCount == 0;
66 | }
67 | }
68 |
69 |
70 | protected int getSpanCount(RecyclerView parent) {
71 | RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
72 |
73 | if (layoutManager instanceof GridLayoutManager) {
74 | return ((GridLayoutManager) layoutManager).getSpanCount();
75 | } else if (layoutManager instanceof StaggeredGridLayoutManager) {
76 | return ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
77 | } else {
78 | throw new UnsupportedOperationException("the +" + getClass().getSimpleName() + " can only be used in " +
79 | "the RecyclerView which use a GridLayoutManager or StaggeredGridLayoutManager");
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/decoration/BaseItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo.decoration;
17 |
18 | import android.support.v7.widget.RecyclerView;
19 | import android.widget.LinearLayout;
20 |
21 | /**
22 | * author: imilk
23 | * https://github.com/Solartisan/TurboRecyclerViewHelper
24 | */
25 | public abstract class BaseItemDecoration extends RecyclerView.ItemDecoration {
26 | protected static final String TAG = "BaseItemDecoration";
27 | public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
28 | public static final int VERTICAL = LinearLayout.VERTICAL;
29 | protected int mOrientation;
30 |
31 | public BaseItemDecoration(int orientation) {
32 | setOrientation(orientation);
33 | }
34 |
35 | public void setOrientation(int orientation) {
36 | this.mOrientation = orientation;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/decoration/GridOffsetsItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is cloned from GridOffsetsItemDecoration provided by RecyclerItemDecoration
3 | * Copyright (C) 2016 dinus
4 | * Copyright (C) 2016 solartisan/imilk
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package cc.solart.turbo.decoration;
19 |
20 |
21 | import android.graphics.Rect;
22 | import android.support.v7.widget.RecyclerView;
23 | import android.util.SparseArray;
24 | import android.view.View;
25 |
26 | /**
27 | * This class can only be used in the RecyclerView which use a GridLayoutManager
28 | * or StaggeredGridLayoutManager, but it's not always work for the StaggeredGridLayoutManager,
29 | * because we can't figure out which position should belong to the last column or the last row
30 | */
31 | public class GridOffsetsItemDecoration extends BaseGridItemDecoration {
32 |
33 | private final SparseArray mTypeOffsetsFactories = new SparseArray<>();
34 |
35 | private int mVerticalItemOffsets;
36 | private int mHorizontalItemOffsets;
37 |
38 | public GridOffsetsItemDecoration(int orientation) {
39 | super(orientation);
40 | }
41 |
42 | public void setVerticalItemOffsets(int verticalItemOffsets) {
43 | this.mVerticalItemOffsets = verticalItemOffsets;
44 | }
45 |
46 | public void setHorizontalItemOffsets(int horizontalItemOffsets) {
47 | this.mHorizontalItemOffsets = horizontalItemOffsets;
48 | }
49 |
50 | @Override
51 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
52 | int spanCount = getSpanCount(parent);
53 | int childCount = parent.getAdapter().getItemCount();
54 | int adapterPosition = parent.getChildAdapterPosition(view);
55 |
56 | outRect.set(getHorizontalOffsets(parent, view) / 2, 0, getHorizontalOffsets(parent, view) / 2, getVerticalOffsets(parent, view));
57 |
58 | if (isFirstColumn(parent, adapterPosition, spanCount, childCount)) {
59 | outRect.left = getHorizontalOffsets(parent, view);
60 | }
61 |
62 | if (isLastColumn(parent, adapterPosition, spanCount, childCount)) {
63 | outRect.right = getHorizontalOffsets(parent, view);
64 | }
65 |
66 | if (isLastRow(parent, adapterPosition, spanCount, childCount)) {
67 | outRect.bottom = 0;
68 | }
69 | }
70 |
71 | protected int getHorizontalOffsets(RecyclerView parent, View view) {
72 | if (mTypeOffsetsFactories.size() == 0) {
73 | return mHorizontalItemOffsets;
74 | }
75 |
76 | final int adapterPosition = parent.getChildAdapterPosition(view);
77 | final int itemType = parent.getAdapter().getItemViewType(adapterPosition);
78 | final IOffsetsCreator offsetsCreator = mTypeOffsetsFactories.get(itemType);
79 |
80 | if (offsetsCreator != null) {
81 | return offsetsCreator.createHorizontal(parent, adapterPosition);
82 | }
83 |
84 | return mHorizontalItemOffsets;
85 | }
86 |
87 | protected int getVerticalOffsets(RecyclerView parent, View view) {
88 | if (mTypeOffsetsFactories.size() == 0) {
89 | return mVerticalItemOffsets;
90 | }
91 |
92 | final int adapterPosition = parent.getChildAdapterPosition(view);
93 | final int itemType = parent.getAdapter().getItemViewType(adapterPosition);
94 | final IOffsetsCreator offsetsCreator = mTypeOffsetsFactories.get(itemType);
95 |
96 | if (offsetsCreator != null) {
97 | return offsetsCreator.createVertical(parent, adapterPosition);
98 | }
99 |
100 | return mVerticalItemOffsets;
101 | }
102 |
103 | public void registerTypeOffsets(int itemType, IOffsetsCreator offsetsCreator) {
104 | mTypeOffsetsFactories.put(itemType, offsetsCreator);
105 | }
106 |
107 | public interface IOffsetsCreator {
108 | int createVertical(RecyclerView parent, int adapterPosition);
109 |
110 | int createHorizontal(RecyclerView parent, int adapterPosition);
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/decoration/GridSpanOffsetsItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 solartisan/imilk
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 | package cc.solart.turbo.decoration;
17 |
18 | import android.support.v7.widget.GridLayoutManager;
19 | import android.support.v7.widget.RecyclerView;
20 | import android.support.v7.widget.StaggeredGridLayoutManager;
21 | import android.util.SparseIntArray;
22 | import android.view.View;
23 | import android.view.ViewGroup;
24 |
25 | import cc.solart.turbo.BaseTurboAdapter;
26 |
27 | /**
28 | * author: imilk
29 | * https://github.com/Solartisan/TurboRecyclerViewHelper
30 | */
31 | public class GridSpanOffsetsItemDecoration extends GridOffsetsItemDecoration {
32 | private SparseIntArray mFullSpanRecorder;
33 |
34 | public GridSpanOffsetsItemDecoration(int orientation) {
35 | super(orientation);
36 | }
37 |
38 |
39 | private int findFullSpanCountByPosition(int position) {
40 | if (mFullSpanRecorder == null) {
41 | return 0;
42 | }
43 | int size = mFullSpanRecorder.size();
44 | for (int i = size - 1; i >= 0; i--) {
45 | int target = mFullSpanRecorder.keyAt(i);
46 | if (target < position) { //look up last full span position and get the full span count
47 | return mFullSpanRecorder.valueAt(i);
48 | }
49 | }
50 | return 0; // if non return zero
51 | }
52 |
53 | private boolean isFullSpan(RecyclerView parent, int position, int spanCount) {
54 | RecyclerView.LayoutManager manager = parent.getLayoutManager();
55 | if (manager instanceof GridLayoutManager) {
56 | GridLayoutManager.SpanSizeLookup lookup = ((GridLayoutManager) manager).getSpanSizeLookup();
57 | int spanSize = lookup.getSpanSize(position);
58 | if (spanSize > 1 || spanSize == spanCount) {
59 | if (parent.getAdapter().getItemViewType(position) != BaseTurboAdapter.TYPE_FOOTER_VIEW
60 | && parent.getAdapter().getItemViewType(position) != BaseTurboAdapter.TYPE_LOADING_VIEW) {
61 | int count = findFullSpanCountByPosition(position);
62 | if (mFullSpanRecorder != null) {
63 | mFullSpanRecorder.put(position, count + 1);
64 | }
65 | }
66 | return true;
67 | } else {
68 | if (mFullSpanRecorder != null) {
69 | mFullSpanRecorder.delete(position);
70 | }
71 | }
72 | } else if (manager instanceof StaggeredGridLayoutManager) {
73 | View view = manager.findViewByPosition(position);
74 | RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
75 | ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
76 | if (layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
77 | if (((StaggeredGridLayoutManager.LayoutParams) layoutParams).isFullSpan()) {
78 | if (parent.getAdapter().getItemViewType(position) != BaseTurboAdapter.TYPE_FOOTER_VIEW
79 | && parent.getAdapter().getItemViewType(position) != BaseTurboAdapter.TYPE_LOADING_VIEW) {
80 | int count = findFullSpanCountByPosition(position);
81 | if (mFullSpanRecorder != null) {
82 | mFullSpanRecorder.put(position, count + 1);
83 | }
84 | }
85 | return true;
86 | } else {
87 | if (mFullSpanRecorder != null) {
88 | mFullSpanRecorder.delete(position);
89 | }
90 | }
91 | }
92 | }else {
93 | throw new UnsupportedOperationException("the GridDividerItemDecoration can only be used in " +
94 | "the RecyclerView which use a GridLayoutManager or StaggeredGridLayoutManager");
95 | }
96 | return false;
97 | }
98 |
99 | @Override
100 | protected boolean isFirstColumn(RecyclerView parent, int position, int spanCount, int childCount) {
101 | if (mOrientation == VERTICAL) {
102 | boolean isFirst = isFullSpan(parent, position, spanCount);
103 | if (isFirst) {
104 | return isFirst;
105 | }
106 | int count = findFullSpanCountByPosition(position);
107 | return ((position - count) % spanCount == 0);
108 | } else {
109 | if(position == 0){
110 | boolean isFirst = isFullSpan(parent, position, spanCount);
111 | if(isFirst){
112 | return true;
113 | }
114 | }
115 | return position < spanCount;
116 | }
117 | }
118 |
119 | @Override
120 | protected boolean isLastColumn(RecyclerView parent, int position, int spanCount, int childCount) {
121 | if (mOrientation == VERTICAL) {
122 | boolean isLast = isFullSpan(parent, position, spanCount);
123 | if (isLast) {
124 | return isLast;
125 | }
126 | int count = findFullSpanCountByPosition(position);
127 | return ((position + 1 - count) % spanCount == 0);
128 | } else {
129 | int count = findFullSpanCountByPosition(position);
130 | int lastColumnCount = (childCount-count) % spanCount;
131 | lastColumnCount = lastColumnCount == 0 ? spanCount : lastColumnCount;
132 | return position >= childCount - lastColumnCount;
133 | }
134 | }
135 |
136 | protected boolean isFirstRow(RecyclerView parent, int position, int spanCount, int childCount) {
137 | if (mOrientation == VERTICAL) {
138 | if(position == 0){
139 | boolean isFirst = isFullSpan(parent, position, spanCount);
140 | if(isFirst){
141 | return true;
142 | }
143 | }
144 | return position < spanCount;
145 | } else {
146 | boolean isFirst = isFullSpan(parent, position, spanCount);
147 | if (isFirst) {
148 | return isFirst;
149 | }
150 | int count = findFullSpanCountByPosition(position);
151 | return ((position - count) % spanCount == 0);
152 | }
153 | }
154 |
155 |
156 | protected boolean isLastRow(RecyclerView parent, int position, int spanCount, int childCount) {
157 | if (mOrientation == VERTICAL) {
158 | int count = findFullSpanCountByPosition(position);
159 | int lastColumnCount = (childCount-count) % spanCount;
160 | lastColumnCount = lastColumnCount == 0 ? spanCount : lastColumnCount;
161 | return position >= childCount - lastColumnCount;
162 | } else {
163 | boolean isLast = isFullSpan(parent, position, spanCount);
164 | if (isLast) {
165 | return isLast;
166 | }
167 | int count = findFullSpanCountByPosition(position);
168 | return ((position + 1 - count) % spanCount == 0);
169 | }
170 | }
171 |
172 | public void registerFullSpanRecorder(SparseIntArray fullSpanRecorder) {
173 | mFullSpanRecorder = fullSpanRecorder;
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/decoration/LinearDividerItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is cloned from LinearDividerItemDecoration provided by RecyclerItemDecoration
3 | * Copyright (C) 2016 dinus
4 | * Copyright (C) 2016 solartisan/imilk
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package cc.solart.turbo.decoration;
19 |
20 | import android.content.Context;
21 | import android.content.res.TypedArray;
22 | import android.graphics.Canvas;
23 | import android.graphics.Rect;
24 | import android.graphics.drawable.Drawable;
25 | import android.support.v4.view.ViewCompat;
26 | import android.support.v7.widget.RecyclerView;
27 | import android.util.SparseArray;
28 | import android.util.SparseIntArray;
29 | import android.view.View;
30 |
31 | /**
32 | * This class can only be used in the RecyclerView which use a LinearLayoutManager or
33 | * its subclass.
34 | */
35 | public class LinearDividerItemDecoration extends BaseItemDecoration {
36 | private static final int[] ATTRS = new int[]{
37 | android.R.attr.listDivider
38 | };
39 |
40 | private final SparseIntArray mDividerOffsets = new SparseIntArray();
41 | private final SparseArray mTypeDrawableFactories = new SparseArray<>();
42 |
43 | private Drawable mDivider;
44 |
45 | public LinearDividerItemDecoration(Context context, int orientation) {
46 | super(orientation);
47 | resolveDivider(context);
48 | }
49 |
50 | private void resolveDivider(Context context) {
51 | final TypedArray a = context.obtainStyledAttributes(ATTRS);
52 | mDivider = a.getDrawable(0);
53 | a.recycle();
54 | }
55 |
56 | public void setDivider(Drawable divider) {
57 | this.mDivider = divider;
58 | }
59 |
60 | @Override
61 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
62 | if (mOrientation == VERTICAL) {
63 | drawVerticalDividers(c, parent);
64 | } else {
65 | drawHorizontalDividers(c, parent);
66 | }
67 | }
68 |
69 | public void drawVerticalDividers(Canvas c, RecyclerView parent) {
70 | final int left = parent.getPaddingLeft();
71 | final int right = parent.getWidth() - parent.getPaddingRight();
72 |
73 | final int childCount = parent.getChildCount();
74 | for (int i = 0; i < childCount; i++) {
75 | final View child = parent.getChildAt(i);
76 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
77 | final Drawable divider = getDivider(parent, params.getViewAdapterPosition());
78 | final int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
79 | final int bottom = top + divider.getIntrinsicHeight();
80 |
81 | mDividerOffsets.put(params.getViewAdapterPosition(), divider.getIntrinsicHeight());
82 |
83 | divider.setBounds(left, top, right, bottom);
84 | divider.draw(c);
85 | }
86 | }
87 |
88 | public void drawHorizontalDividers(Canvas c, RecyclerView parent) {
89 | final int top = parent.getPaddingTop();
90 | final int bottom = parent.getHeight() - parent.getPaddingBottom();
91 |
92 | final int childCount = parent.getChildCount();
93 | for (int i = 0; i < childCount; i++) {
94 | final View child = parent.getChildAt(i);
95 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
96 | final Drawable divider = getDivider(parent, params.getViewAdapterPosition());
97 | final int left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child));
98 | final int right = left + divider.getIntrinsicHeight();
99 |
100 | mDividerOffsets.put(params.getViewAdapterPosition(), divider.getIntrinsicHeight());
101 |
102 | divider.setBounds(left, top, right, bottom);
103 | divider.draw(c);
104 | }
105 | }
106 |
107 | @Override
108 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
109 | final int adapterPosition = parent.getChildAdapterPosition(view);
110 | if (adapterPosition == parent.getAdapter().getItemCount() - 1) {
111 | return;
112 | }
113 |
114 | if (mDividerOffsets.indexOfKey(adapterPosition) < 0) {
115 | mDividerOffsets.put(adapterPosition, getDivider(parent, adapterPosition).getIntrinsicHeight());
116 | }
117 |
118 | if (mOrientation == VERTICAL) {
119 | outRect.set(0, 0, 0, mDividerOffsets.get(parent.getChildAdapterPosition(view)));
120 | } else {
121 | outRect.set(0, 0, mDividerOffsets.get(parent.getChildAdapterPosition(view)), 0);
122 | }
123 | }
124 |
125 | private Drawable getDivider(RecyclerView parent, int adapterPosition) {
126 | final RecyclerView.Adapter adapter = parent.getAdapter();
127 | final int itemType = adapter.getItemViewType(adapterPosition);
128 | final IDrawableCreator drawableCreator = mTypeDrawableFactories.get(itemType);
129 |
130 | if (drawableCreator != null) {
131 | return drawableCreator.create(parent, adapterPosition);
132 | }
133 |
134 | return mDivider;
135 | }
136 |
137 | public void registerTypeDrawable(int itemType, IDrawableCreator drawableCreator) {
138 | mTypeDrawableFactories.put(itemType, drawableCreator);
139 | }
140 |
141 | public interface IDrawableCreator {
142 | Drawable create(RecyclerView parent, int adapterPosition);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/java/cc/solart/turbo/decoration/LinearOffsetsItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is cloned from LinearOffsetsItemDecoration provided by RecyclerItemDecoration
3 | * Copyright (C) 2016 dinus
4 | * Copyright (C) 2016 solartisan/imilk
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package cc.solart.turbo.decoration;
19 |
20 | import android.graphics.Rect;
21 | import android.support.v7.widget.RecyclerView;
22 | import android.util.SparseArray;
23 | import android.view.View;
24 |
25 | /**
26 | * This class can only be used in the RecyclerView which use a LinearLayoutManager or
27 | * its subclass.
28 | */
29 | public class LinearOffsetsItemDecoration extends BaseItemDecoration {
30 |
31 | private final SparseArray mTypeOffsetsFactories = new SparseArray<>();
32 |
33 | private int mItemOffsets;
34 |
35 | public LinearOffsetsItemDecoration(int orientation) {
36 | super(orientation);
37 | }
38 |
39 | public void setItemOffsets(int itemOffsets) {
40 | this.mItemOffsets = itemOffsets;
41 | }
42 |
43 | @Override
44 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
45 | int adapterPosition = parent.getChildAdapterPosition(view);
46 | if (adapterPosition == parent.getAdapter().getItemCount() - 1) {
47 | return;
48 | }
49 |
50 | if (mOrientation == HORIZONTAL) {
51 | outRect.right = getDividerOffsets(parent, view);
52 | } else {
53 | outRect.bottom = getDividerOffsets(parent, view);
54 | }
55 | }
56 |
57 | private int getDividerOffsets(RecyclerView parent, View view) {
58 | if (mTypeOffsetsFactories.size() == 0) {
59 | return mItemOffsets;
60 | }
61 |
62 | final int adapterPosition = parent.getChildAdapterPosition(view);
63 | final int itemType = parent.getAdapter().getItemViewType(adapterPosition);
64 | final IOffsetsCreator offsetsCreator = mTypeOffsetsFactories.get(itemType);
65 |
66 | if (offsetsCreator != null) {
67 | return offsetsCreator.create(parent, adapterPosition);
68 | }
69 |
70 | return 0;
71 | }
72 |
73 | public void registerTypeOffsets(int itemType, IOffsetsCreator offsetsCreator) {
74 | mTypeOffsetsFactories.put(itemType, offsetsCreator);
75 | }
76 |
77 | public interface IOffsetsCreator {
78 | int create(RecyclerView parent, int adapterPosition);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/res/drawable/sample_footer_loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Solartisan/TurboRecyclerViewHelper/09f1c0f5c50a7406233380854e4bcaf9f02dee09/turbo-recyclerview-helper/src/main/res/drawable/sample_footer_loading.png
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/res/drawable/sample_footer_loading_progress.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/res/layout/footer_item_default_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 4dp
3 | 22dp
4 | 48dp
5 | 14sp
6 |
7 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TurboRecyclerViewHelper
3 |
4 |
--------------------------------------------------------------------------------
/turbo-recyclerview-helper/src/test/java/cc/solart/turbo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package cc.solart.turbo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------