├── .gitignore
├── LICENSE
├── README-zh.md
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── app
│ │ └── tvrecyclerview
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── app
│ │ │ └── tvrecyclerview
│ │ │ ├── MainActivity.java
│ │ │ ├── Utils.java
│ │ │ ├── bean
│ │ │ ├── Row.java
│ │ │ └── itemPosition.java
│ │ │ ├── irregular
│ │ │ ├── IrregularActivity.java
│ │ │ ├── IrregularPresenter.java
│ │ │ └── IrregularVerticalActivity.java
│ │ │ └── reuglar
│ │ │ ├── RegularActivity.java
│ │ │ ├── RegularPresenter.java
│ │ │ ├── RegularVerticalActivity.java
│ │ │ └── RegularVerticalPresenter.java
│ └── res
│ │ ├── anim
│ │ ├── anim_scale_big.xml
│ │ └── anim_scale_small.xml
│ │ ├── drawable
│ │ ├── default_focus.9.png
│ │ ├── pic0.jpg
│ │ ├── pic1.jpg
│ │ ├── pic2.jpg
│ │ ├── pic3.jpg
│ │ ├── pic4.jpg
│ │ ├── pic5.jpg
│ │ ├── pic6.jpg
│ │ ├── pic7.jpg
│ │ ├── pic8.jpg
│ │ ├── rl_shape_corner.xml
│ │ └── tv_shape_corner.xml
│ │ ├── layout
│ │ ├── activity_irregular.xml
│ │ ├── activity_irregular_vertical.xml
│ │ ├── activity_main.xml
│ │ ├── activity_regular.xml
│ │ └── activity_regular_vertical.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── raw
│ │ ├── horizonal_irregular.json
│ │ ├── horizonal_regular.json
│ │ └── vertical_irregular.json
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── app
│ └── tvrecyclerview
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screen1.png
├── screen2.png
├── settings.gradle
└── tvrecyclerview
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── app
│ └── com
│ └── tvrecyclerview
│ └── ExampleInstrumentedTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── app
│ │ └── com
│ │ └── tvrecyclerview
│ │ ├── BaseGridView.java
│ │ ├── FocusHighlightHandler.java
│ │ ├── FocusHighlightHelper.java
│ │ ├── GridLayoutManager.java
│ │ ├── GridObjectAdapter.java
│ │ ├── HorizontalGridView.java
│ │ ├── ItemBridgeAdapter.java
│ │ ├── OnChildSelectedListener.java
│ │ ├── OnChildViewHolderSelectedListener.java
│ │ ├── OnFocusSearchFailedListener.java
│ │ ├── Presenter.java
│ │ ├── PresenterSelector.java
│ │ ├── RowItem.java
│ │ ├── SinglePresenterSelector.java
│ │ └── VerticalGridView.java
└── res
│ └── values
│ ├── attrs.xml
│ ├── dims.xml
│ └── strings.xml
└── test
└── java
└── app
└── com
└── tvrecyclerview
└── ExampleUnitTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/*
38 |
39 | # Keystore files
40 | *.jks
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 | # TvRecyclerView
2 | 用在android TV端的自定义recyclerView控件, 不支持手机端.
3 |
4 | ## ScreenShot
5 |
152 | */
153 | public void setFocusZoomFactor(int factor) {
154 | mFocusZoomFactor = factor;
155 | Adapter adapter = getAdapter();
156 | if (adapter != null && adapter instanceof ItemBridgeAdapter) {
157 | ItemBridgeAdapter bridgeAdapter = (ItemBridgeAdapter) adapter;
158 | FocusHighlightHelper.setupItemBridgeFocusHighlight(bridgeAdapter, mFocusZoomFactor);
159 | }
160 | }
161 | /**
162 | * Sets the strategy used to scroll in response to item focus changing:
163 | *
164 | *
{@link #FOCUS_SCROLL_ALIGNED} (default)
165 | *
{@link #FOCUS_SCROLL_ITEM}
166 | *
{@link #FOCUS_SCROLL_PAGE}
167 | *
168 | */
169 | public void setFocusScrollStrategy(int scrollStrategy) {
170 | if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
171 | && scrollStrategy != FOCUS_SCROLL_PAGE) {
172 | throw new IllegalArgumentException("Invalid scrollStrategy");
173 | }
174 | mLayoutManager.setFocusScrollStrategy(scrollStrategy);
175 | requestLayout();
176 | }
177 |
178 | /**
179 | * Returns the strategy used to scroll in response to item focus changing.
180 | *
181 | *
{@link #FOCUS_SCROLL_ALIGNED} (default)
182 | *
{@link #FOCUS_SCROLL_ITEM}
183 | *
{@link #FOCUS_SCROLL_PAGE}
184 | *
185 | */
186 | public int getFocusScrollStrategy() {
187 | return mLayoutManager.getFocusScrollStrategy();
188 | }
189 |
190 | // /**
191 | // * Sets the offset in pixels for window alignment.
192 | // *
193 | // * @param offset The number of pixels to offset. If the offset is positive,
194 | // * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
195 | // * if the offset is negative, the absolute value is distance from high
196 | // * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
197 | // * Default value is 0.
198 | // */
199 | // public void setWindowAlignmentOffset(int offset) {
200 | // mLayoutManager.setWindowAlignmentOffset(offset);
201 | // requestLayout();
202 | // }
203 | //
204 | // /**
205 | // * Returns the offset in pixels for window alignment.
206 | // *
207 | // * @return The number of pixels to offset. If the offset is positive,
208 | // * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
209 | // * if the offset is negative, the absolute value is distance from high
210 | // * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
211 | // * Default value is 0.
212 | // */
213 | // public int getWindowAlignmentOffset() {
214 | // return mLayoutManager.getWindowAlignmentOffset();
215 | // }
216 | //
217 | // /**
218 | // * Sets the id of the view to align with. Use {@link android.view.View#NO_ID} (default)
219 | // * for the item view itself.
220 | // * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
221 | // * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
222 | // */
223 | // public void setItemAlignmentViewId(int viewId) {
224 | // mLayoutManager.setItemAlignmentViewId(viewId);
225 | // }
226 | //
227 | // /**
228 | // * Returns the id of the view to align with, or zero for the item view itself.
229 | // */
230 | // public int getItemAlignmentViewId() {
231 | // return mLayoutManager.getItemAlignmentViewId();
232 | // }
233 |
234 | /**
235 | * Registers a callback to be invoked when an item in BaseGridView has
236 | * been selected. Note that the listener may be invoked when there is a
237 | * layout pending on the view, affording the listener an opportunity to
238 | * adjust the upcoming layout based on the selection state.
239 | *
240 | * @param listener The listener to be invoked.
241 | */
242 | public void setOnChildSelectedListener(OnChildSelectedListener listener) {
243 | mLayoutManager.setOnChildSelectedListener(listener);
244 | }
245 |
246 | /**
247 | * Registers a callback to be invoked when an item in BaseGridView has
248 | * been selected. Note that the listener may be invoked when there is a
249 | * layout pending on the view, affording the listener an opportunity to
250 | * adjust the upcoming layout based on the selection state.
251 | *
252 | * @param listener The listener to be invoked.
253 | */
254 | public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
255 | mLayoutManager.setOnChildViewHolderSelectedListener(listener);
256 | }
257 |
258 | /**
259 | * Changes the selected item immediately without animation.
260 | */
261 | public void setSelectedPosition(int position) {
262 | mLayoutManager.setSelection(this, position, 0);
263 | }
264 |
265 | /**
266 | * Changes the selected item and/or subposition immediately without animation.
267 | */
268 | public void setSelectedPositionWithSub(int position, int subposition) {
269 | mLayoutManager.setSelectionWithSub(this, position, subposition, 0);
270 | }
271 |
272 | /**
273 | * Changes the selected item immediately without animation, scrollExtra is
274 | * applied in primary scroll direction. The scrollExtra will be kept until
275 | * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
276 | */
277 | public void setSelectedPosition(int position, int scrollExtra) {
278 | mLayoutManager.setSelection(this, position, scrollExtra);
279 | }
280 |
281 | /**
282 | * Changes the selected item and/or subposition immediately without animation, scrollExtra is
283 | * applied in primary scroll direction. The scrollExtra will be kept until
284 | * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
285 | */
286 | public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) {
287 | mLayoutManager.setSelectionWithSub(this, position, subposition, scrollExtra);
288 | }
289 |
290 | /**
291 | * Changes the selected item and run an animation to scroll to the target
292 | * position.
293 | */
294 | public void setSelectedPositionSmooth(int position) {
295 | mLayoutManager.setSelectionSmooth(this, position);
296 | }
297 |
298 | /**
299 | * Changes the selected item and/or subposition, runs an animation to scroll to the target
300 | * position.
301 | */
302 | public void setSelectedPositionSmoothWithSub(int position, int subposition) {
303 | mLayoutManager.setSelectionSmoothWithSub(this, position, subposition);
304 | }
305 |
306 | /**
307 | * Returns the selected item position.
308 | */
309 | public int getSelectedPosition() {
310 | return mLayoutManager.getSelection();
311 | }
312 |
313 | /**
314 | * Returns the sub selected item position started from zero.
315 | */
316 | public int getSelectedSubPosition() {
317 | return mLayoutManager.getSubSelection();
318 | }
319 |
320 | /**
321 | * Sets whether an animation should run when a child changes size or when adding
322 | * or removing a child.
323 | */
324 | public void setAnimateChildLayout(boolean animateChildLayout) {
325 | if (mAnimateChildLayout != animateChildLayout) {
326 | mAnimateChildLayout = animateChildLayout;
327 | if (!mAnimateChildLayout) {
328 | mSavedItemAnimator = getItemAnimator();
329 | super.setItemAnimator(null);
330 | } else {
331 | super.setItemAnimator(mSavedItemAnimator);
332 | }
333 | }
334 | }
335 |
336 | /**
337 | * Returns true if an animation will run when a child changes size or when
338 | * adding or removing a child.
339 | */
340 | public boolean isChildLayoutAnimated() {
341 | return mAnimateChildLayout;
342 | }
343 |
344 | /**
345 | * Sets the gravity used for child view positioning. Defaults to
346 | * GRAVITY_TOP|GRAVITY_START.
347 | *
348 | * @param gravity See {@link android.view.Gravity}
349 | */
350 | public void setGravity(int gravity) {
351 | mLayoutManager.setGravity(gravity);
352 | requestLayout();
353 | }
354 |
355 | @Override
356 | public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
357 | return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
358 | previouslyFocusedRect);
359 | }
360 |
361 | @Override
362 | public int getChildDrawingOrder(int childCount, int i) {
363 | return mLayoutManager.getChildDrawingOrder(this, childCount, i);
364 | }
365 |
366 | final boolean isChildrenDrawingOrderEnabledInternal() {
367 | return isChildrenDrawingOrderEnabled();
368 | }
369 |
370 | /**
371 | * Disables or enables focus search.
372 | */
373 | public final void setFocusSearchDisabled(boolean disabled) {
374 | // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
375 | // re-gain focus after a BACK key pressed, so block children focus during transition.
376 | setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS);
377 | mLayoutManager.setFocusSearchDisabled(disabled);
378 | }
379 |
380 | /**
381 | * Returns true if focus search is disabled.
382 | */
383 | public final boolean isFocusSearchDisabled() {
384 | return mLayoutManager.isFocusSearchDisabled();
385 | }
386 |
387 | /**
388 | * Changes and overrides children's visibility.
389 | */
390 | public void setChildrenVisibility(int visibility) {
391 | mLayoutManager.setChildrenVisibility(visibility);
392 | }
393 |
394 | /**
395 | * Enables or disables scrolling. Disable is useful during transition.
396 | */
397 | public void setScrollEnabled(boolean scrollEnabled) {
398 | mLayoutManager.setScrollEnabled(scrollEnabled);
399 | }
400 |
401 | /**
402 | * Returns true if scrolling is enabled.
403 | */
404 | public boolean isScrollEnabled() {
405 | return mLayoutManager.isScrollEnabled();
406 | }
407 |
408 | /**
409 | * Enables or disables the default "focus draw at last" order rule.
410 | */
411 | public void setFocusDrawingOrderEnabled(boolean enabled) {
412 | super.setChildrenDrawingOrderEnabled(enabled);
413 | }
414 |
415 | /**
416 | * Returns true if default "focus draw at last" order rule is enabled.
417 | */
418 | public boolean isFocusDrawingOrderEnabled() {
419 | return super.isChildrenDrawingOrderEnabled();
420 | }
421 |
422 | /**
423 | * Sets the touch intercept listener.
424 | */
425 | public void setOnTouchInterceptListener(OnTouchInterceptListener listener) {
426 | mOnTouchInterceptListener = listener;
427 | }
428 |
429 | /**
430 | * Sets the generic motion intercept listener.
431 | */
432 | public void setOnMotionInterceptListener(OnMotionInterceptListener listener) {
433 | mOnMotionInterceptListener = listener;
434 | }
435 |
436 | /**
437 | * Sets the key intercept listener.
438 | */
439 | public void setOnKeyInterceptListener(OnKeyInterceptListener listener) {
440 | mOnKeyInterceptListener = listener;
441 | }
442 |
443 | /**
444 | * Sets the unhandled key listener.
445 | */
446 | public void setOnUnhandledKeyListener(OnUnhandledKeyListener listener) {
447 | mOnUnhandledKeyListener = listener;
448 | }
449 |
450 | /**
451 | * Returns the unhandled key listener.
452 | */
453 | public OnUnhandledKeyListener getOnUnhandledKeyListener() {
454 | return mOnUnhandledKeyListener;
455 | }
456 |
457 | @Override
458 | public boolean dispatchKeyEvent(KeyEvent event) {
459 | if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
460 | return true;
461 | }
462 | if (super.dispatchKeyEvent(event)) {
463 | return true;
464 | }
465 | if (mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event)) {
466 | return true;
467 | }
468 | return false;
469 | }
470 |
471 | @Override
472 | public boolean dispatchTouchEvent(MotionEvent event) {
473 | if (mOnTouchInterceptListener != null) {
474 | if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
475 | return true;
476 | }
477 | }
478 | return super.dispatchTouchEvent(event);
479 | }
480 |
481 | @Override
482 | public boolean dispatchGenericFocusedEvent(MotionEvent event) {
483 | if (mOnMotionInterceptListener != null) {
484 | if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
485 | return true;
486 | }
487 | }
488 | return super.dispatchGenericFocusedEvent(event);
489 | }
490 |
491 | @Override
492 | public boolean hasOverlappingRendering() {
493 | return mHasOverlappingRendering;
494 | }
495 |
496 | public void setHasOverlappingRendering(boolean hasOverlapping) {
497 | mHasOverlappingRendering = hasOverlapping;
498 | }
499 |
500 | @Override
501 | public void setRecyclerListener(RecyclerView.RecyclerListener listener) {
502 | mChainedRecyclerListener = listener;
503 | }
504 |
505 |
506 | public int getSelection() {
507 | return mLayoutManager.getSelection();
508 | }
509 |
510 | public void setOnFocusSearchFailedListener(OnFocusSearchFailedListener listener) {
511 | mLayoutManager.setOnFocusSearchFailedListener(listener);
512 | }
513 | }
514 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/FocusHighlightHandler.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * Interface for highlighting the item that has focus.
7 | */
8 | interface FocusHighlightHandler {
9 | /**
10 | * Called when an item gains or loses focus.
11 | *
12 | * @param view The view whose focus is changing.
13 | * @param hasFocus True if focus is gained; false otherwise.
14 | */
15 | void onItemFocused(View view, boolean hasFocus);
16 |
17 | /**
18 | * Called when the view is being created.
19 | */
20 | void onInitializeView(View view);
21 | }
22 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/FocusHighlightHelper.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | import android.animation.TimeAnimator;
4 | import android.content.res.Resources;
5 | import android.view.View;
6 | import android.view.animation.AccelerateDecelerateInterpolator;
7 | import android.view.animation.Interpolator;
8 |
9 |
10 | /**
11 | * Sets up the highlighting behavior when an item gains focus.
12 | */
13 | public class FocusHighlightHelper {
14 |
15 | public static final int ZOOM_FACTOR_NONE = 0;
16 |
17 | /**
18 | * A small zoom factor, recommended for large item views.
19 | */
20 | public static final int ZOOM_FACTOR_SMALL = 1;
21 |
22 | /**
23 | * A medium zoom factor, recommended for medium sized item views.
24 | */
25 | public static final int ZOOM_FACTOR_MEDIUM = 2;
26 |
27 | /**
28 | * A large zoom factor, recommended for small item views.
29 | */
30 | public static final int ZOOM_FACTOR_LARGE = 3;
31 |
32 | /**
33 | * An extra small zoom factor.
34 | */
35 | public static final int ZOOM_FACTOR_XSMALL = 4;
36 |
37 |
38 | private static boolean isValidZoomIndex(int zoomIndex) {
39 | return zoomIndex == ZOOM_FACTOR_NONE || getResId(zoomIndex) > 0;
40 | }
41 |
42 | private static int getResId(int zoomIndex) {
43 | switch (zoomIndex) {
44 | case ZOOM_FACTOR_SMALL:
45 | return R.fraction.tr_focus_zoom_factor_small;
46 | case ZOOM_FACTOR_XSMALL:
47 | return R.fraction.tr_focus_zoom_factor_xsmall;
48 | case ZOOM_FACTOR_MEDIUM:
49 | return R.fraction.tr_focus_zoom_factor_medium;
50 | case ZOOM_FACTOR_LARGE:
51 | return R.fraction.tr_focus_zoom_factor_large;
52 | default:
53 | return 0;
54 | }
55 | }
56 |
57 |
58 | private static class FocusAnimator implements TimeAnimator.TimeListener {
59 | private final View mView;
60 | private final int mDuration;
61 | private final float mScaleDiff;
62 | private float mFocusLevel = 0f;
63 | private float mFocusLevelStart;
64 | private float mFocusLevelDelta;
65 | private final TimeAnimator mAnimator = new TimeAnimator();
66 | private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
67 |
68 | void animateFocus(boolean select, boolean immediate) {
69 | endAnimation();
70 | final float end = select ? 1 : 0;
71 | if (immediate) {
72 | setFocusLevel(end);
73 | } else if (mFocusLevel != end) {
74 | mFocusLevelStart = mFocusLevel;
75 | mFocusLevelDelta = end - mFocusLevelStart;
76 | mAnimator.start();
77 | }
78 | }
79 |
80 | FocusAnimator(View view, float scale, int duration) {
81 | mView = view;
82 | mDuration = duration;
83 | mScaleDiff = scale - 1f;
84 | mAnimator.setTimeListener(this);
85 | }
86 |
87 | void setFocusLevel(float level) {
88 | mFocusLevel = level;
89 | float scale = 1f + mScaleDiff * level;
90 | mView.setScaleX(scale);
91 | mView.setScaleY(scale);
92 | }
93 |
94 | float getFocusLevel() {
95 | return mFocusLevel;
96 | }
97 |
98 | void endAnimation() {
99 | mAnimator.end();
100 | }
101 |
102 | @Override
103 | public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
104 | float fraction;
105 | if (totalTime >= mDuration) {
106 | fraction = 1;
107 | mAnimator.end();
108 | } else {
109 | fraction = (float) (totalTime / (double) mDuration);
110 | }
111 | if (mInterpolator != null) {
112 | fraction = mInterpolator.getInterpolation(fraction);
113 | }
114 | setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
115 | }
116 | }
117 |
118 | private static class ItemBridgeFocusHighlight implements FocusHighlightHandler {
119 | private static final int DURATION_MS = 150;
120 |
121 | private int mScaleIndex;
122 |
123 | public ItemBridgeFocusHighlight(int zoomIndex) {
124 | if (!isValidZoomIndex(zoomIndex)) {
125 | throw new IllegalArgumentException("Unhandled zoom index");
126 | }
127 | mScaleIndex = zoomIndex;
128 | }
129 |
130 | private float getScale(Resources res) {
131 | return mScaleIndex == ZOOM_FACTOR_NONE ? 1f :
132 | res.getFraction(getResId(mScaleIndex), 1, 1);
133 | }
134 |
135 | @Override
136 | public void onItemFocused(View view, boolean hasFocus) {
137 | view.setSelected(hasFocus);
138 | getOrCreateAnimator(view).animateFocus(hasFocus, false);
139 | }
140 |
141 | @Override
142 | public void onInitializeView(View view) {
143 | getOrCreateAnimator(view).animateFocus(false, true);
144 | }
145 |
146 | private FocusAnimator getOrCreateAnimator(View view) {
147 | FocusAnimator animator = (FocusAnimator) view.getTag(R.id.tr_focus_animator);
148 | if (animator == null) {
149 | animator = new FocusAnimator(
150 | view, getScale(view.getResources()), DURATION_MS);
151 | view.setTag(R.id.tr_focus_animator, animator);
152 | }
153 | return animator;
154 | }
155 |
156 | }
157 |
158 | /**
159 | * Sets up the focus highlight behavior of a focused item in browse list row.
160 | * @param zoomIndex scale type
161 | * @param adapter adapter of the list row.
162 | */
163 | public static void setupItemBridgeFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex) {
164 | adapter.setFocusHighlight(new ItemBridgeFocusHighlight(zoomIndex));
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/GridObjectAdapter.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | import android.database.Observable;
4 |
5 | import java.util.ArrayList;
6 |
7 | public class GridObjectAdapter {
8 |
9 | /**
10 | * A DataObserver can be notified when an ObjectAdapter's underlying data
11 | * changes. Separate methods provide notifications about different types of
12 | * changes.
13 | */
14 | public static abstract class DataObserver {
15 | /**
16 | * Called whenever the ObjectAdapter's data has changed in some manner
17 | * outside of the set of changes covered by the other range-based change
18 | * notification methods.
19 | */
20 | public void onChanged() {
21 | }
22 |
23 | /**
24 | * Called when a range of items in the ObjectAdapter has changed. The
25 | * basic ordering and structure of the ObjectAdapter has not changed.
26 | *
27 | * @param positionStart The position of the first item that changed.
28 | * @param itemCount The number of items changed.
29 | */
30 | public void onItemRangeChanged(int positionStart, int itemCount) {
31 | onChanged();
32 | }
33 |
34 | /**
35 | * Called when a range of items is inserted into the ObjectAdapter.
36 | *
37 | * @param positionStart The position of the first inserted item.
38 | * @param itemCount The number of items inserted.
39 | */
40 | public void onItemRangeInserted(int positionStart, int itemCount) {
41 | onChanged();
42 | }
43 |
44 | /**
45 | * Called when a range of items is removed from the ObjectAdapter.
46 | *
47 | * @param positionStart The position of the first removed item.
48 | * @param itemCount The number of items removed.
49 | */
50 | public void onItemRangeRemoved(int positionStart, int itemCount) {
51 | onChanged();
52 | }
53 | }
54 |
55 | private static final class DataObservable extends Observable {
56 |
57 | public void notifyChanged() {
58 | for (int i = mObservers.size() - 1; i >= 0; i--) {
59 | mObservers.get(i).onChanged();
60 | }
61 | }
62 |
63 | public void notifyItemRangeChanged(int positionStart, int itemCount) {
64 | for (int i = mObservers.size() - 1; i >= 0; i--) {
65 | mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
66 | }
67 | }
68 |
69 | public void notifyItemRangeInserted(int positionStart, int itemCount) {
70 | for (int i = mObservers.size() - 1; i >= 0; i--) {
71 | mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
72 | }
73 | }
74 |
75 | public void notifyItemRangeRemoved(int positionStart, int itemCount) {
76 | for (int i = mObservers.size() - 1; i >= 0; i--) {
77 | mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
78 | }
79 | }
80 | }
81 |
82 | private ArrayList mItems = new ArrayList<>();
83 |
84 | private int mRowSpacing = 0;
85 |
86 | private int mColumnSpacing = 0;
87 |
88 | private int mColumns = 0;
89 |
90 | private float mAspectRatio = 1.0f;
91 |
92 | private final DataObservable mObservable = new DataObservable();
93 |
94 | private PresenterSelector mPresenterSelector;
95 |
96 | public GridObjectAdapter() {
97 | }
98 |
99 | /**
100 | * Constructs an adapter with the given {@link PresenterSelector}.
101 | */
102 | public GridObjectAdapter(PresenterSelector presenterSelector) {
103 | setPresenterSelector(presenterSelector);
104 | }
105 |
106 | /**
107 | * Constructs an adapter that uses the given {@link Presenter} for all items.
108 | */
109 | public GridObjectAdapter(Presenter presenter) {
110 | setPresenterSelector(new SinglePresenterSelector(presenter));
111 | }
112 |
113 | public GridObjectAdapter(Presenter presenter,
114 | int rowSpacing, int columnSpacing, int columns,
115 | float aspectRatio) {
116 | mRowSpacing = rowSpacing;
117 | mColumnSpacing = columnSpacing;
118 | mColumns = columns;
119 | mAspectRatio = aspectRatio;
120 | setPresenterSelector(new SinglePresenterSelector(presenter));
121 | }
122 |
123 | public GridObjectAdapter(Presenter presenter, int columns,
124 | float aspectRatio) {
125 | mColumns = columns;
126 | mAspectRatio = aspectRatio;
127 | setPresenterSelector(new SinglePresenterSelector(presenter));
128 | }
129 |
130 | public GridObjectAdapter(PresenterSelector presenterSelector, int columns,
131 | float aspectRatio) {
132 | mColumns = columns;
133 | mAspectRatio = aspectRatio;
134 | setPresenterSelector(presenterSelector);
135 | }
136 |
137 | /**
138 | * Constructs an adapter.
139 | */
140 | public GridObjectAdapter(PresenterSelector presenterSelector,
141 | int rowSpacing, int columnSpacing, int columns,
142 | float aspectRatio) {
143 | mRowSpacing = rowSpacing;
144 | mColumnSpacing = columnSpacing;
145 | mColumns = columns;
146 | mAspectRatio = aspectRatio;
147 | setPresenterSelector(presenterSelector);
148 | }
149 |
150 | public void setGridStyle(int rowSpacing, int columnSpacing, int numRowOrColumns,
151 | float aspectRatio) {
152 | mRowSpacing = rowSpacing;
153 | mColumnSpacing = columnSpacing;
154 | mColumns = numRowOrColumns;
155 | mAspectRatio = aspectRatio;
156 | }
157 |
158 | /**
159 | * Sets the presenter selector. May not be null.
160 | */
161 | public final void setPresenterSelector(PresenterSelector presenterSelector) {
162 | if (presenterSelector == null) {
163 | throw new IllegalArgumentException("Presenter selector must not be null");
164 | }
165 | final boolean update = (mPresenterSelector != null);
166 | mPresenterSelector = presenterSelector;
167 |
168 | if (update) {
169 | notifyChanged();
170 | }
171 | }
172 |
173 | /**
174 | * Returns the presenter selector for this ObjectAdapter.
175 | */
176 | public final PresenterSelector getPresenterSelector() {
177 | return mPresenterSelector;
178 | }
179 |
180 | public int size() {
181 | return mItems.size();
182 | }
183 |
184 | public RowItem get(int position) {
185 | return mItems.get(position);
186 | }
187 |
188 | /**
189 | * Returns the index for the first occurrence of item in the adapter, or -1 if
190 | * not found.
191 | *
192 | * @param item The item to find in the list.
193 | * @return Index of the first occurrence of the item in the adapter, or -1
194 | * if not found.
195 | */
196 | public int indexOf(RowItem item) {
197 | return mItems.indexOf(item);
198 | }
199 |
200 | /**
201 | * Adds an item to the end of the adapter.
202 | *
203 | * @param item The item to add to the end of the adapter.
204 | */
205 | public void add(RowItem item) {
206 | add(mItems.size(), item);
207 | }
208 |
209 | /**
210 | * Inserts an item into this adapter at the specified index.
211 | *
212 | * @param index The index at which the item should be inserted.
213 | * @param item The item to insert into the adapter.
214 | */
215 | public void add(int index, RowItem item) {
216 | mItems.add(index, item);
217 | notifyItemRangeInserted(index, 1);
218 | }
219 |
220 | /**
221 | * Replaces item at position with a new item and calls notifyItemRangeChanged()
222 | * at the given position. Note that this method does not compare new item to
223 | * existing item.
224 | * @param position The index of item to replace.
225 | * @param item The new item to be placed at given position.
226 | */
227 | public void replace(int position, RowItem item) {
228 | mItems.set(position, item);
229 | notifyItemRangeChanged(position, 1);
230 | }
231 |
232 | /**
233 | * Removes all items from this adapter, leaving it empty.
234 | */
235 | public void clear() {
236 | int itemCount = mItems.size();
237 | if (itemCount == 0) {
238 | return;
239 | }
240 | mItems.clear();
241 | notifyItemRangeRemoved(0, itemCount);
242 | }
243 |
244 | public long getId(int position) {
245 | return position;
246 | }
247 |
248 | /**
249 | * Registers a DataObserver for data change notifications.
250 | */
251 | public final void registerObserver(DataObserver observer) {
252 | mObservable.registerObserver(observer);
253 | }
254 |
255 | /**
256 | * Unregisters a DataObserver for data change notifications.
257 | */
258 | public final void unregisterObserver(DataObserver observer) {
259 | mObservable.unregisterObserver(observer);
260 | }
261 |
262 | /**
263 | * Unregisters all DataObservers for this ObjectAdapter.
264 | */
265 | public final void unregisterAllObservers() {
266 | mObservable.unregisterAll();
267 | }
268 |
269 | final protected void notifyItemRangeChanged(int positionStart, int itemCount) {
270 | mObservable.notifyItemRangeChanged(positionStart, itemCount);
271 | }
272 |
273 | final protected void notifyItemRangeInserted(int positionStart, int itemCount) {
274 | mObservable.notifyItemRangeInserted(positionStart, itemCount);
275 | }
276 |
277 | final protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
278 | mObservable.notifyItemRangeRemoved(positionStart, itemCount);
279 | }
280 |
281 | final protected void notifyChanged() {
282 | mObservable.notifyChanged();
283 | }
284 |
285 | public int getItemLeftIndex(int position) {
286 | if (mItems == null || position < 0) {
287 | return 0;
288 | }
289 | if (position < mItems.size()) {
290 | return mItems.get(position).getX();
291 | } else {
292 | return 0;
293 | }
294 | }
295 |
296 | public int getItemTopIndex(int position) {
297 | if (mItems == null || position < 0) {
298 | return 0;
299 | }
300 | if (position < mItems.size()) {
301 | return mItems.get(position).getY();
302 | } else {
303 | return 0;
304 | }
305 | }
306 |
307 | public int getItemRowSize(int position) {
308 | if (mItems == null || position < 0) {
309 | return 0;
310 | }
311 | if (position < mItems.size()) {
312 | return mItems.get(position).getHeight();
313 | } else {
314 | return 1;
315 | }
316 | }
317 |
318 | public int getItemColumnSize(int position) {
319 | if (mItems == null || position < 0) {
320 | return 0;
321 | }
322 | if (position < mItems.size()) {
323 | return mItems.get(position).getWidth();
324 | } else {
325 | return 1;
326 | }
327 | }
328 |
329 | public int getColumnSpacing() {
330 | return mColumnSpacing;
331 | }
332 |
333 | public int getRowSpacing() {
334 | return mRowSpacing;
335 | }
336 |
337 | public int getColumns() {
338 | return mColumns;
339 | }
340 |
341 | public float getAspectRatio() {
342 | return mAspectRatio;
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/HorizontalGridView.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.LinearGradient;
9 | import android.graphics.Paint;
10 | import android.graphics.PorterDuff;
11 | import android.graphics.PorterDuffXfermode;
12 | import android.graphics.Rect;
13 | import android.graphics.Shader;
14 | import android.util.AttributeSet;
15 | import android.util.TypedValue;
16 | import android.view.View;
17 |
18 |
19 | public class HorizontalGridView extends BaseGridView {
20 |
21 | private boolean mFadingLowEdge;
22 | private boolean mFadingHighEdge;
23 |
24 | private Paint mTempPaint = new Paint();
25 | private Bitmap mTempBitmapLow;
26 | private LinearGradient mLowFadeShader;
27 | private int mLowFadeShaderLength;
28 | private int mLowFadeShaderOffset;
29 | private Bitmap mTempBitmapHigh;
30 | private LinearGradient mHighFadeShader;
31 | private int mHighFadeShaderLength;
32 | private int mHighFadeShaderOffset;
33 | private Rect mTempRect = new Rect();
34 |
35 | public HorizontalGridView(Context context) {
36 | this(context, null);
37 | }
38 |
39 | public HorizontalGridView(Context context, AttributeSet attrs) {
40 | this(context, attrs, 0);
41 | }
42 |
43 | public HorizontalGridView(Context context, AttributeSet attrs, int defStyle) {
44 | super(context, attrs, defStyle);
45 | initAttributes(context, attrs);
46 | }
47 |
48 | @Override
49 | public GridLayoutManager getLayoutManager() {
50 | return new GridLayoutManager(this, HORIZONTAL);
51 | }
52 |
53 | protected void initAttributes(Context context, AttributeSet attrs) {
54 | initBaseGridViewAttributes(context, attrs);
55 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorizontalGridView);
56 | setNumRows(a.getInt(R.styleable.HorizontalGridView_numberOfRows, 1));
57 | a.recycle();
58 | updateLayerType();
59 | mTempPaint = new Paint();
60 | mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
61 | }
62 |
63 | /**
64 | * Sets the number of rows. Defaults to one.
65 | */
66 | public void setNumRows(int numRows) {
67 | mLayoutManager.setNumRows(numRows);
68 | requestLayout();
69 | }
70 |
71 | /**
72 | * Sets the fade out left edge to transparent. Note turn on fading edge is very expensive
73 | * that you should turn off when HorizontalGridView is scrolling.
74 | */
75 | public final void setFadingLeftEdge(boolean fading) {
76 | if (mFadingLowEdge != fading) {
77 | mFadingLowEdge = fading;
78 | if (!mFadingLowEdge) {
79 | mTempBitmapLow = null;
80 | }
81 | invalidate();
82 | updateLayerType();
83 | }
84 | }
85 |
86 | /**
87 | * Returns true if left edge fading is enabled.
88 | */
89 | public final boolean getFadingLeftEdge() {
90 | return mFadingLowEdge;
91 | }
92 |
93 | /**
94 | * Sets the left edge fading length in pixels.
95 | */
96 | public final void setFadingLeftEdgeLength(int fadeLength) {
97 | if (mLowFadeShaderLength != fadeLength) {
98 | mLowFadeShaderLength = fadeLength;
99 | if (mLowFadeShaderLength != 0) {
100 | mLowFadeShader = new LinearGradient(0, 0, mLowFadeShaderLength, 0,
101 | Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
102 | } else {
103 | mLowFadeShader = null;
104 | }
105 | invalidate();
106 | }
107 | }
108 |
109 | /**
110 | * Returns the left edge fading length in pixels.
111 | */
112 | public final int getFadingLeftEdgeLength() {
113 | return mLowFadeShaderLength;
114 | }
115 |
116 | /**
117 | * Sets the distance in pixels between fading start position and left padding edge.
118 | * The fading start position is positive when start position is inside left padding
119 | * area. Default value is 0, means that the fading starts from left padding edge.
120 | */
121 | public final void setFadingLeftEdgeOffset(int fadeOffset) {
122 | if (mLowFadeShaderOffset != fadeOffset) {
123 | mLowFadeShaderOffset = fadeOffset;
124 | invalidate();
125 | }
126 | }
127 |
128 | /**
129 | * Returns the distance in pixels between fading start position and left padding edge.
130 | * The fading start position is positive when start position is inside left padding
131 | * area. Default value is 0, means that the fading starts from left padding edge.
132 | */
133 | public final int getFadingLeftEdgeOffset() {
134 | return mLowFadeShaderOffset;
135 | }
136 |
137 | /**
138 | * Sets the fade out right edge to transparent. Note turn on fading edge is very expensive
139 | * that you should turn off when HorizontalGridView is scrolling.
140 | */
141 | public final void setFadingRightEdge(boolean fading) {
142 | if (mFadingHighEdge != fading) {
143 | mFadingHighEdge = fading;
144 | if (!mFadingHighEdge) {
145 | mTempBitmapHigh = null;
146 | }
147 | invalidate();
148 | updateLayerType();
149 | }
150 | }
151 |
152 | /**
153 | * Returns true if fading right edge is enabled.
154 | */
155 | public final boolean getFadingRightEdge() {
156 | return mFadingHighEdge;
157 | }
158 |
159 | /**
160 | * Sets the right edge fading length in pixels.
161 | */
162 | public final void setFadingRightEdgeLength(int fadeLength) {
163 | if (mHighFadeShaderLength != fadeLength) {
164 | mHighFadeShaderLength = fadeLength;
165 | if (mHighFadeShaderLength != 0) {
166 | mHighFadeShader = new LinearGradient(0, 0, mHighFadeShaderLength, 0,
167 | Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP);
168 | } else {
169 | mHighFadeShader = null;
170 | }
171 | invalidate();
172 | }
173 | }
174 |
175 | /**
176 | * Returns the right edge fading length in pixels.
177 | */
178 | public final int getFadingRightEdgeLength() {
179 | return mHighFadeShaderLength;
180 | }
181 |
182 | /**
183 | * Returns the distance in pixels between fading start position and right padding edge.
184 | * The fading start position is positive when start position is inside right padding
185 | * area. Default value is 0, means that the fading starts from right padding edge.
186 | */
187 | public final void setFadingRightEdgeOffset(int fadeOffset) {
188 | if (mHighFadeShaderOffset != fadeOffset) {
189 | mHighFadeShaderOffset = fadeOffset;
190 | invalidate();
191 | }
192 | }
193 |
194 | /**
195 | * Sets the distance in pixels between fading start position and right padding edge.
196 | * The fading start position is positive when start position is inside right padding
197 | * area. Default value is 0, means that the fading starts from right padding edge.
198 | */
199 | public final int getFadingRightEdgeOffset() {
200 | return mHighFadeShaderOffset;
201 | }
202 |
203 | private boolean needsFadingLowEdge() {
204 | if (!mFadingLowEdge) {
205 | return false;
206 | }
207 | final int c = getChildCount();
208 | for (int i = 0; i < c; i++) {
209 | View view = getChildAt(i);
210 | if (mLayoutManager.getOpticalLeft(view) <
211 | getPaddingLeft() - mLowFadeShaderOffset) {
212 | return true;
213 | }
214 | }
215 | return false;
216 | }
217 |
218 | private boolean needsFadingHighEdge() {
219 | if (!mFadingHighEdge) {
220 | return false;
221 | }
222 | final int c = getChildCount();
223 | for (int i = c - 1; i >= 0; i--) {
224 | View view = getChildAt(i);
225 | if (mLayoutManager.getOpticalRight(view) > getWidth()
226 | - getPaddingRight() + mHighFadeShaderOffset) {
227 | return true;
228 | }
229 | }
230 | return false;
231 | }
232 |
233 | private Bitmap getTempBitmapLow() {
234 | if (mTempBitmapLow == null
235 | || mTempBitmapLow.getWidth() != mLowFadeShaderLength
236 | || mTempBitmapLow.getHeight() != getHeight()) {
237 | mTempBitmapLow = Bitmap.createBitmap(mLowFadeShaderLength, getHeight(),
238 | Bitmap.Config.ARGB_8888);
239 | }
240 | return mTempBitmapLow;
241 | }
242 |
243 | private Bitmap getTempBitmapHigh() {
244 | if (mTempBitmapHigh == null
245 | || mTempBitmapHigh.getWidth() != mHighFadeShaderLength
246 | || mTempBitmapHigh.getHeight() != getHeight()) {
247 | mTempBitmapHigh = Bitmap.createBitmap(mHighFadeShaderLength, getHeight(),
248 | Bitmap.Config.ARGB_8888);
249 | }
250 | return mTempBitmapHigh;
251 | }
252 |
253 | @Override
254 | public void draw(Canvas canvas) {
255 | final boolean needsFadingLow = needsFadingLowEdge();
256 | final boolean needsFadingHigh = needsFadingHighEdge();
257 | if (!needsFadingLow) {
258 | mTempBitmapLow = null;
259 | }
260 | if (!needsFadingHigh) {
261 | mTempBitmapHigh = null;
262 | }
263 | if (!needsFadingLow && !needsFadingHigh) {
264 | super.draw(canvas);
265 | return;
266 | }
267 |
268 | int lowEdge = mFadingLowEdge? getPaddingLeft() - mLowFadeShaderOffset - mLowFadeShaderLength : 0;
269 | int highEdge = mFadingHighEdge ? getWidth() - getPaddingRight()
270 | + mHighFadeShaderOffset + mHighFadeShaderLength : getWidth();
271 |
272 | // draw not-fade content
273 | int save = canvas.save();
274 | canvas.clipRect(lowEdge + (mFadingLowEdge ? mLowFadeShaderLength : 0), 0,
275 | highEdge - (mFadingHighEdge ? mHighFadeShaderLength : 0), getHeight());
276 | super.draw(canvas);
277 | canvas.restoreToCount(save);
278 |
279 | Canvas tmpCanvas = new Canvas();
280 | mTempRect.top = 0;
281 | mTempRect.bottom = getHeight();
282 | if (needsFadingLow && mLowFadeShaderLength > 0) {
283 | Bitmap tempBitmap = getTempBitmapLow();
284 | tempBitmap.eraseColor(Color.TRANSPARENT);
285 | tmpCanvas.setBitmap(tempBitmap);
286 | // draw original content
287 | int tmpSave = tmpCanvas.save();
288 | tmpCanvas.clipRect(0, 0, mLowFadeShaderLength, getHeight());
289 | tmpCanvas.translate(-lowEdge, 0);
290 | super.draw(tmpCanvas);
291 | tmpCanvas.restoreToCount(tmpSave);
292 | // draw fading out
293 | mTempPaint.setShader(mLowFadeShader);
294 | tmpCanvas.drawRect(0, 0, mLowFadeShaderLength, getHeight(), mTempPaint);
295 | // copy back to canvas
296 | mTempRect.left = 0;
297 | mTempRect.right = mLowFadeShaderLength;
298 | canvas.translate(lowEdge, 0);
299 | canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null);
300 | canvas.translate(-lowEdge, 0);
301 | }
302 | if (needsFadingHigh && mHighFadeShaderLength > 0) {
303 | Bitmap tempBitmap = getTempBitmapHigh();
304 | tempBitmap.eraseColor(Color.TRANSPARENT);
305 | tmpCanvas.setBitmap(tempBitmap);
306 | // draw original content
307 | int tmpSave = tmpCanvas.save();
308 | tmpCanvas.clipRect(0, 0, mHighFadeShaderLength, getHeight());
309 | tmpCanvas.translate(-(highEdge - mHighFadeShaderLength), 0);
310 | super.draw(tmpCanvas);
311 | tmpCanvas.restoreToCount(tmpSave);
312 | // draw fading out
313 | mTempPaint.setShader(mHighFadeShader);
314 | tmpCanvas.drawRect(0, 0, mHighFadeShaderLength, getHeight(), mTempPaint);
315 | // copy back to canvas
316 | mTempRect.left = 0;
317 | mTempRect.right = mHighFadeShaderLength;
318 | canvas.translate(highEdge - mHighFadeShaderLength, 0);
319 | canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null);
320 | canvas.translate(-(highEdge - mHighFadeShaderLength), 0);
321 | }
322 | }
323 |
324 | /**
325 | * Updates the layer type for this view.
326 | * If fading edges are needed, use a hardware layer. This works around the problem
327 | * that when a child invalidates itself (for example has an animated background),
328 | * the parent view must also be invalidated to refresh the display list which
329 | * updates the the caching bitmaps used to draw the fading edges.
330 | */
331 | private void updateLayerType() {
332 | if (mFadingLowEdge || mFadingHighEdge) {
333 | setLayerType(View.LAYER_TYPE_HARDWARE, null);
334 | setWillNotDraw(false);
335 | } else {
336 | setLayerType(View.LAYER_TYPE_NONE, null);
337 | setWillNotDraw(true);
338 | }
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/ItemBridgeAdapter.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 |
4 | import android.support.annotation.NonNull;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import java.util.ArrayList;
10 |
11 |
12 | public class ItemBridgeAdapter extends RecyclerView.Adapter {
13 |
14 | /**
15 | * Interface for listening to ViewHolder operations.
16 | */
17 | public static class AdapterListener {
18 | public void onAddPresenter(Presenter presenter, int type) {
19 | }
20 | public void onCreate(ViewHolder viewHolder) {
21 | }
22 | public void onBind(ViewHolder viewHolder) {
23 | }
24 | public void onUnbind(ViewHolder viewHolder) {
25 | }
26 | public void onAttachedToWindow(ViewHolder viewHolder) {
27 | }
28 | public void onDetachedFromWindow(ViewHolder viewHolder) {
29 | }
30 | }
31 |
32 | private GridObjectAdapter mAdapter;
33 | private ArrayList mPresenters = new ArrayList<>();
34 | private PresenterSelector mPresenterSelector;
35 | private FocusHighlightHandler mFocusHighlight;
36 | private AdapterListener mAdapterListener;
37 |
38 |
39 | private GridObjectAdapter.DataObserver mDataObserver = new GridObjectAdapter.DataObserver() {
40 | @Override
41 | public void onChanged() {
42 | ItemBridgeAdapter.this.notifyDataSetChanged();
43 | }
44 | @Override
45 | public void onItemRangeChanged(int positionStart, int itemCount) {
46 | ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
47 | }
48 | @Override
49 | public void onItemRangeInserted(int positionStart, int itemCount) {
50 | ItemBridgeAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
51 | }
52 | @Override
53 | public void onItemRangeRemoved(int positionStart, int itemCount) {
54 | ItemBridgeAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
55 | }
56 | };
57 |
58 | public ItemBridgeAdapter() {
59 | }
60 |
61 | public ItemBridgeAdapter(GridObjectAdapter adapter) {
62 | this(adapter, null);
63 | }
64 |
65 | public ItemBridgeAdapter(GridObjectAdapter adapter, PresenterSelector presenterSelector) {
66 | setAdapter(adapter);
67 | mPresenterSelector = presenterSelector;
68 | }
69 |
70 | /**
71 | * Sets the {@link GridObjectAdapter}.
72 | */
73 | public void setAdapter(GridObjectAdapter adapter) {
74 | if (mAdapter != null) {
75 | mAdapter.unregisterObserver(mDataObserver);
76 | }
77 | mAdapter = adapter;
78 | if (mAdapter == null) {
79 | return;
80 | }
81 |
82 | mAdapter.registerObserver(mDataObserver);
83 | }
84 |
85 | /**
86 | * Returns the {@link GridObjectAdapter}.
87 | */
88 | public GridObjectAdapter getAdapter() {
89 | return mAdapter;
90 | }
91 |
92 | /**
93 | * Sets the AdapterListener.
94 | */
95 | public void setAdapterListener(AdapterListener listener) {
96 | mAdapterListener = listener;
97 | }
98 |
99 | void setFocusHighlight(FocusHighlightHandler listener) {
100 | mFocusHighlight = listener;
101 | }
102 |
103 | /**
104 | * Clears the adapter.
105 | */
106 | public void clear() {
107 | setAdapter(null);
108 | }
109 |
110 | @Override
111 | public int getItemViewType(int position) {
112 | PresenterSelector presenterSelector = mPresenterSelector != null ?
113 | mPresenterSelector : mAdapter.getPresenterSelector();
114 | Object item = mAdapter.get(position);
115 | Presenter presenter = presenterSelector.getPresenter(item);
116 | int type = mPresenters.indexOf(presenter);
117 | if (type < 0) {
118 | mPresenters.add(presenter);
119 | type = mPresenters.indexOf(presenter);
120 | if (mAdapterListener != null) {
121 | mAdapterListener.onAddPresenter(presenter, type);
122 | }
123 | }
124 | return type;
125 | }
126 |
127 | @NonNull
128 | @Override
129 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
130 | Presenter presenter = mPresenters.get(viewType);
131 | Presenter.ViewHolder presenterVh = presenter.onCreateViewHolder(parent);
132 | View view = presenterVh.view;
133 | ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
134 | if (mAdapterListener != null) {
135 | mAdapterListener.onCreate(viewHolder);
136 | }
137 | View presenterView = viewHolder.mHolder.view;
138 | if (presenterView != null) {
139 | viewHolder.mFocusChangeListener.mChainedListener = presenterView.getOnFocusChangeListener();
140 | presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
141 | }
142 | if (mFocusHighlight != null) {
143 | mFocusHighlight.onInitializeView(view);
144 | }
145 | return viewHolder;
146 | }
147 |
148 | @Override
149 | public int getItemCount() {
150 | return mAdapter.size();
151 | }
152 |
153 | @Override
154 | public final void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
155 | ViewHolder viewHolder = (ViewHolder) holder;
156 | viewHolder.mItem = mAdapter.get(position);
157 | viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
158 | if (mAdapterListener != null) {
159 | mAdapterListener.onBind(viewHolder);
160 | }
161 | }
162 |
163 | @Override
164 | public final void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
165 | ViewHolder viewHolder = (ViewHolder) holder;
166 | viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder);
167 | if (mAdapterListener != null) {
168 | mAdapterListener.onUnbind(viewHolder);
169 | }
170 | viewHolder.mItem = null;
171 | }
172 |
173 | @Override
174 | public final void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
175 | ViewHolder viewHolder = (ViewHolder) holder;
176 | if (mAdapterListener != null) {
177 | mAdapterListener.onAttachedToWindow(viewHolder);
178 | }
179 | viewHolder.mPresenter.onViewAttachedToWindow(viewHolder.mHolder);
180 | }
181 |
182 | @Override
183 | public final void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
184 | ViewHolder viewHolder = (ViewHolder) holder;
185 | viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder);
186 | if (mAdapterListener != null) {
187 | mAdapterListener.onDetachedFromWindow(viewHolder);
188 | }
189 | }
190 |
191 | @Override
192 | public long getItemId(int position) {
193 | return mAdapter.getId(position);
194 | }
195 |
196 | private final class OnFocusChangeListener implements View.OnFocusChangeListener {
197 | View.OnFocusChangeListener mChainedListener;
198 |
199 | @Override
200 | public void onFocusChange(View view, boolean hasFocus) {
201 | if (mFocusHighlight != null) {
202 | mFocusHighlight.onItemFocused(view, hasFocus);
203 | }
204 | if (mChainedListener != null) {
205 | mChainedListener.onFocusChange(view, hasFocus);
206 | }
207 | }
208 | }
209 |
210 | /**
211 | * ViewHolder for the ItemBridgeAdapter.
212 | */
213 | public class ViewHolder extends RecyclerView.ViewHolder{
214 | final Presenter mPresenter;
215 | final Presenter.ViewHolder mHolder;
216 | final OnFocusChangeListener mFocusChangeListener = new OnFocusChangeListener();
217 | Object mItem;
218 |
219 | /**
220 | * Get {@link Presenter}.
221 | */
222 | public final Presenter getPresenter() {
223 | return mPresenter;
224 | }
225 |
226 | /**
227 | * Get {@link Presenter.ViewHolder}.
228 | */
229 | public final Presenter.ViewHolder getViewHolder() {
230 | return mHolder;
231 | }
232 |
233 | /**
234 | * Get currently bound object.
235 | */
236 | public final Object getItem() {
237 | return mItem;
238 | }
239 |
240 | ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder) {
241 | super(view);
242 | mPresenter = presenter;
243 | mHolder = holder;
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/OnChildSelectedListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package app.com.tvrecyclerview;
15 |
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 |
19 | /**
20 | * Interface for receiving notification when a child of this
21 | * ViewGroup has been selected.
22 | * @deprecated Use {@link OnChildViewHolderSelectedListener}
23 | */
24 | @Deprecated
25 | public interface OnChildSelectedListener {
26 | /**
27 | * Callback method to be invoked when a child of this ViewGroup has been
28 | * selected.
29 | *
30 | * @param parent The ViewGroup where the selection happened.
31 | * @param view The view within the ViewGroup that is selected, or null if no
32 | * view is selected.
33 | * @param position The position of the view in the adapter, or NO_POSITION
34 | * if no view is selected.
35 | * @param id The id of the child that is selected, or NO_ID if no view is
36 | * selected.
37 | */
38 | void onChildSelected(ViewGroup parent, View view, int position, long id);
39 | }
40 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/OnChildViewHolderSelectedListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package app.com.tvrecyclerview;
15 |
16 | import android.support.v7.widget.RecyclerView;
17 |
18 | /**
19 | * Interface for receiving notification when a child of this
20 | * ViewGroup has been selected.
21 | */
22 | public abstract class OnChildViewHolderSelectedListener {
23 | /**
24 | * Callback method to be invoked when a child of this ViewGroup has been
25 | * selected.
26 | *
27 | * @param parent The RecyclerView where the selection happened.
28 | * @param child The ViewHolder within the RecyclerView that is selected, or null if no
29 | * view is selected.
30 | * @param position The position of the view in the adapter, or NO_POSITION
31 | * if no view is selected.
32 | * @param subposition The index of which being used,
33 | * 0 if there is no ItemAlignmentDef defined for the item.
34 | */
35 | public void onChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child,
36 | int position, int subposition) {
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/OnFocusSearchFailedListener.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 |
4 | import android.view.View;
5 |
6 | /**
7 | * Interface for search next focus fail listener.
8 | */
9 | public interface OnFocusSearchFailedListener {
10 | /**
11 | * Listener for focus search failed events
12 | * @param focused focused view
13 | * @param direction next search direct
14 | * @param position The position of the current focused view in the adapter
15 | * @param itemCount the ViewGroup child count
16 | */
17 | void onFocusSearchFailed(View focused, int direction, int position, int itemCount);
18 | }
19 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/Presenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package app.com.tvrecyclerview;
15 |
16 | import android.content.Context;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 |
20 |
21 | public abstract class Presenter{
22 |
23 | private final Context mContext;
24 |
25 | /**
26 | * ViewHolder can be subclassed and used to cache any view accessors needed
27 | * to improve binding performance (for example, results of findViewById)
28 | * without needing to subclass a View.
29 | */
30 | public static class ViewHolder{
31 | public final View view;
32 |
33 | public ViewHolder(View view) {
34 | this.view = view;
35 | }
36 | }
37 |
38 | public Presenter(Context context) {
39 | mContext = context;
40 | }
41 |
42 | public Context getContext() {
43 | return mContext;
44 | }
45 |
46 | public abstract View onCreateView();
47 |
48 | /**
49 | * Creates a new {@link View}.
50 | */
51 | public ViewHolder onCreateViewHolder(ViewGroup parent) {
52 | View view = onCreateView();
53 | return new ViewHolder(view);
54 | }
55 |
56 | /**
57 | * Binds a {@link View} to an item.
58 | */
59 | public abstract void onBindViewHolder(ViewHolder viewHolder, Object item);
60 |
61 | /**
62 | * Unbinds a {@link View} from an item. Any expensive references may be
63 | * released here, and any fields that are not bound for every item should be
64 | * cleared here.
65 | */
66 | public abstract void onUnbindViewHolder(ViewHolder viewHolder);
67 |
68 | /**
69 | * Called when a view created by this presenter has been attached to a window.
70 | *
71 | *
This can be used as a reasonable signal that the view is about to be seen
72 | * by the user. If the adapter previously freed any resources in
73 | * {@link #onViewDetachedFromWindow(ViewHolder)}
74 | * those resources should be restored here.
75 | *
76 | * @param holder Holder of the view being attached
77 | */
78 | public void onViewAttachedToWindow(ViewHolder holder) {
79 | }
80 |
81 | /**
82 | * Called when a view created by this presenter has been detached from its window.
83 | *
84 | *
Becoming detached from the window is not necessarily a permanent condition;
85 | * the consumer of an presenter's views may choose to cache views offscreen while they
86 | * are not visible, attaching and detaching them as appropriate.
87 | *
88 | * Any view property animations should be cancelled here or the view may fail
89 | * to be recycled.
90 | *
91 | * @param holder Holder of the view being detached
92 | */
93 | public void onViewDetachedFromWindow(ViewHolder holder) {
94 | // If there are view property animations running then RecyclerView won't recycle.
95 | cancelAnimationsRecursive(holder.view);
96 | }
97 |
98 | /**
99 | * Utility method for removing all running animations on a view.
100 | */
101 | protected static void cancelAnimationsRecursive(View view) {
102 | if (view != null && view.hasTransientState()) {
103 | view.animate().cancel();
104 | if (view instanceof ViewGroup) {
105 | final int count = ((ViewGroup) view).getChildCount();
106 | for (int i = 0; view.hasTransientState() && i < count; i++) {
107 | cancelAnimationsRecursive(((ViewGroup) view).getChildAt(i));
108 | }
109 | }
110 | }
111 | }
112 |
113 | /**
114 | * Called to set a click listener for the given view holder.
115 | *
116 | * The default implementation sets the click listener on the root view in the view holder.
117 | * If the root view isn't focusable this method should be overridden to set the listener
118 | * on the appropriate focusable child view(s).
119 | *
120 | * @param holder The view holder containing the view(s) on which the listener should be set.
121 | * @param listener The click listener to be set.
122 | */
123 | public void setOnClickListener(ViewHolder holder, View.OnClickListener listener) {
124 | holder.view.setOnClickListener(listener);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/PresenterSelector.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | /**
4 | * A PresenterSelector is used to obtain a {@link Presenter} for a given Object.
5 | * Similar to {@link Presenter}, PresenterSelector is stateless.
6 | */
7 | public abstract class PresenterSelector {
8 | /**
9 | * Returns a presenter for the given item.
10 | */
11 | public abstract Presenter getPresenter(Object item);
12 |
13 | /**
14 | * Returns an array of all possible presenters. The returned array should
15 | * not be modified.
16 | */
17 | public Presenter[] getPresenters() {
18 | return null;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/RowItem.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | public class RowItem {
4 | private int mPos;
5 | private int mX;
6 | private int mY;
7 | private int mWidth;
8 | private int mHeight;
9 |
10 | public int getPos() {
11 | return mPos;
12 | }
13 | public void setPos(int pos) {
14 | this.mPos = pos;
15 | }
16 |
17 | public int getX() {
18 | return mX;
19 | }
20 | public void setX(int x) {
21 | this.mX = x;
22 | }
23 |
24 | public int getY() {
25 | return mY;
26 | }
27 | public void setY(int y) {
28 | this.mY = y;
29 | }
30 |
31 | public int getWidth() {
32 | return mWidth;
33 | }
34 | public void setWidth(int width) {
35 | this.mWidth = width;
36 | }
37 |
38 | public int getHeight() {
39 | return mHeight;
40 | }
41 | public void setHeight(int height) {
42 | this.mHeight = height;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/SinglePresenterSelector.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | /**
4 | * A {@link PresenterSelector} that always returns the same {@link Presenter}.
5 | * Useful for rows of items of the same type that are all rendered the same way.
6 | */
7 | public final class SinglePresenterSelector extends PresenterSelector {
8 |
9 | private final Presenter mPresenter;
10 |
11 | /**
12 | * @param presenter The Presenter to return for every item.
13 | */
14 | public SinglePresenterSelector(Presenter presenter) {
15 | mPresenter = presenter;
16 | }
17 |
18 | @Override
19 | public Presenter getPresenter(Object item) {
20 | return mPresenter;
21 | }
22 |
23 | @Override
24 | public Presenter[] getPresenters() {
25 | return new Presenter[]{mPresenter};
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/java/app/com/tvrecyclerview/VerticalGridView.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 |
7 | public class VerticalGridView extends BaseGridView {
8 |
9 | public VerticalGridView(Context context) {
10 | this(context, null);
11 | }
12 |
13 | public VerticalGridView(Context context, AttributeSet attrs) {
14 | this(context, attrs, 0);
15 | }
16 |
17 | public VerticalGridView(Context context, AttributeSet attrs, int defStyle) {
18 | super(context, attrs, defStyle);
19 | initAttributes(context, attrs);
20 | }
21 |
22 | @Override
23 | public GridLayoutManager getLayoutManager() {
24 | return new GridLayoutManager(this, VERTICAL);
25 | }
26 |
27 | protected void initAttributes(Context context, AttributeSet attrs) {
28 | initBaseGridViewAttributes(context, attrs);
29 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VerticalGridView);
30 | setNumColumns(a.getInt(R.styleable.VerticalGridView_numberOfColumns, 1));
31 | a.recycle();
32 | }
33 |
34 | /**
35 | * Sets the number of columns. Defaults to one.
36 | */
37 | public void setNumColumns(int numColumns) {
38 | mLayoutManager.setNumRows(numColumns);
39 | requestLayout();
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/res/values/dims.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 118%
4 | 114%
5 | 110%
6 | 106%
7 | 120%
8 | 150
9 |
10 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | tvRecyclerView
3 |
4 |
--------------------------------------------------------------------------------
/tvrecyclerview/src/test/java/app/com/tvrecyclerview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package app.com.tvrecyclerview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------