2 * Copyright (C) 2014 The Android Open Source Project
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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
14 package android
.support
.v17
.leanback
.app
;
16 import android
.animation
.TimeAnimator
;
17 import android
.animation
.TimeAnimator
.TimeListener
;
18 import android
.os
.Bundle
;
19 import android
.support
.v17
.leanback
.R
;
20 import android
.support
.v17
.leanback
.widget
.HorizontalGridView
;
21 import android
.support
.v17
.leanback
.widget
.ItemBridgeAdapter
;
22 import android
.support
.v17
.leanback
.widget
.ListRowPresenter
;
23 import android
.support
.v17
.leanback
.widget
.ObjectAdapter
;
24 import android
.support
.v17
.leanback
.widget
.BaseOnItemViewClickedListener
;
25 import android
.support
.v17
.leanback
.widget
.BaseOnItemViewSelectedListener
;
26 import android
.support
.v17
.leanback
.widget
.OnItemViewClickedListener
;
27 import android
.support
.v17
.leanback
.widget
.OnItemViewSelectedListener
;
28 import android
.support
.v17
.leanback
.widget
.Presenter
;
29 import android
.support
.v17
.leanback
.widget
.PresenterSelector
;
30 import android
.support
.v17
.leanback
.widget
.RowPresenter
;
31 import android
.support
.v17
.leanback
.widget
.VerticalGridView
;
32 import android
.support
.v17
.leanback
.widget
.ViewHolderTask
;
33 import android
.support
.v7
.widget
.RecyclerView
;
34 import android
.util
.Log
;
35 import android
.view
.LayoutInflater
;
36 import android
.view
.View
;
37 import android
.view
.ViewGroup
;
38 import android
.view
.animation
.DecelerateInterpolator
;
39 import android
.view
.animation
.Interpolator
;
41 import java
.util
.ArrayList
;
44 * An ordered set of rows of leanback widgets.
46 * A RowsFragment renders the elements of its
47 * {@link android.support.v17.leanback.widget.ObjectAdapter} as a set
48 * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
49 * of {@link RowPresenter}.
52 public class RowsFragment
extends BaseRowFragment
implements
53 BrowseFragment
.MainFragmentRowsAdapterProvider
,
54 BrowseFragment
.MainFragmentAdapterProvider
{
56 private MainFragmentAdapter mMainFragmentAdapter
;
57 private MainFragmentRowsAdapter mMainFragmentRowsAdapter
;
60 public BrowseFragment
.MainFragmentAdapter
getMainFragmentAdapter() {
61 if (mMainFragmentAdapter
== null) {
62 mMainFragmentAdapter
= new MainFragmentAdapter(this);
64 return mMainFragmentAdapter
;
68 public BrowseFragment
.MainFragmentRowsAdapter
getMainFragmentRowsAdapter() {
69 if (mMainFragmentRowsAdapter
== null) {
70 mMainFragmentRowsAdapter
= new MainFragmentRowsAdapter(this);
72 return mMainFragmentRowsAdapter
;
76 * Internal helper class that manages row select animation and apply a default
79 final class RowViewHolderExtra
implements TimeListener
{
80 final RowPresenter mRowPresenter
;
81 final Presenter
.ViewHolder mRowViewHolder
;
83 final TimeAnimator mSelectAnimator
= new TimeAnimator();
85 int mSelectAnimatorDurationInUse
;
86 Interpolator mSelectAnimatorInterpolatorInUse
;
87 float mSelectLevelAnimStart
;
88 float mSelectLevelAnimDelta
;
90 RowViewHolderExtra(ItemBridgeAdapter
.ViewHolder ibvh
) {
91 mRowPresenter
= (RowPresenter
) ibvh
.getPresenter();
92 mRowViewHolder
= ibvh
.getViewHolder();
93 mSelectAnimator
.setTimeListener(this);
97 public void onTimeUpdate(TimeAnimator animation
, long totalTime
, long deltaTime
) {
98 if (mSelectAnimator
.isRunning()) {
99 updateSelect(totalTime
, deltaTime
);
103 void updateSelect(long totalTime
, long deltaTime
) {
105 if (totalTime
>= mSelectAnimatorDurationInUse
) {
107 mSelectAnimator
.end();
109 fraction
= (float) (totalTime
/ (double) mSelectAnimatorDurationInUse
);
111 if (mSelectAnimatorInterpolatorInUse
!= null) {
112 fraction
= mSelectAnimatorInterpolatorInUse
.getInterpolation(fraction
);
114 float level
= mSelectLevelAnimStart
+ fraction
* mSelectLevelAnimDelta
;
115 mRowPresenter
.setSelectLevel(mRowViewHolder
, level
);
118 void animateSelect(boolean select
, boolean immediate
) {
119 mSelectAnimator
.end();
120 final float end
= select ?
1 : 0;
122 mRowPresenter
.setSelectLevel(mRowViewHolder
, end
);
123 } else if (mRowPresenter
.getSelectLevel(mRowViewHolder
) != end
) {
124 mSelectAnimatorDurationInUse
= mSelectAnimatorDuration
;
125 mSelectAnimatorInterpolatorInUse
= mSelectAnimatorInterpolator
;
126 mSelectLevelAnimStart
= mRowPresenter
.getSelectLevel(mRowViewHolder
);
127 mSelectLevelAnimDelta
= end
- mSelectLevelAnimStart
;
128 mSelectAnimator
.start();
134 static final String TAG
= "RowsFragment";
135 static final boolean DEBUG
= false;
137 ItemBridgeAdapter
.ViewHolder mSelectedViewHolder
;
138 private int mSubPosition
;
139 boolean mExpand
= true;
140 boolean mViewsCreated
;
141 private int mAlignedTop
;
142 boolean mAfterEntranceTransition
= true;
144 BaseOnItemViewSelectedListener mOnItemViewSelectedListener
;
145 BaseOnItemViewClickedListener mOnItemViewClickedListener
;
147 // Select animation and interpolator are not intended to be
148 // exposed at this moment. They might be synced with vertical scroll
150 int mSelectAnimatorDuration
;
151 Interpolator mSelectAnimatorInterpolator
= new DecelerateInterpolator(2);
153 private RecyclerView
.RecycledViewPool mRecycledViewPool
;
154 private ArrayList
<Presenter
> mPresenterMapper
;
156 ItemBridgeAdapter
.AdapterListener mExternalAdapterListener
;
159 protected VerticalGridView
findGridViewFromRoot(View view
) {
160 return (VerticalGridView
) view
.findViewById(R
.id
.container_list
);
164 * Sets an item clicked listener on the fragment.
165 * OnItemViewClickedListener will override {@link View.OnClickListener} that
166 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
167 * So in general, developer should choose one of the listeners but not both.
169 public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener
) {
170 mOnItemViewClickedListener
= listener
;
172 throw new IllegalStateException(
173 "Item clicked listener must be set before views are created");
178 * Returns the item clicked listener.
180 public BaseOnItemViewClickedListener
getOnItemViewClickedListener() {
181 return mOnItemViewClickedListener
;
185 * @deprecated use {@link BrowseFragment#enableRowScaling(boolean)} instead.
187 * @param enable true to enable row scaling
190 public void enableRowScaling(boolean enable
) {
194 * Set the visibility of titles/hovercard of browse rows.
196 public void setExpand(boolean expand
) {
198 VerticalGridView listView
= getVerticalGridView();
199 if (listView
!= null) {
200 final int count
= listView
.getChildCount();
201 if (DEBUG
) Log
.v(TAG
, "setExpand " + expand
+ " count " + count
);
202 for (int i
= 0; i
< count
; i
++) {
203 View view
= listView
.getChildAt(i
);
204 ItemBridgeAdapter
.ViewHolder vh
205 = (ItemBridgeAdapter
.ViewHolder
) listView
.getChildViewHolder(view
);
206 setRowViewExpanded(vh
, mExpand
);
212 * Sets an item selection listener.
214 public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener
) {
215 mOnItemViewSelectedListener
= listener
;
216 VerticalGridView listView
= getVerticalGridView();
217 if (listView
!= null) {
218 final int count
= listView
.getChildCount();
219 for (int i
= 0; i
< count
; i
++) {
220 View view
= listView
.getChildAt(i
);
221 ItemBridgeAdapter
.ViewHolder ibvh
= (ItemBridgeAdapter
.ViewHolder
)
222 listView
.getChildViewHolder(view
);
223 getRowViewHolder(ibvh
).setOnItemViewSelectedListener(mOnItemViewSelectedListener
);
229 * Returns an item selection listener.
231 public BaseOnItemViewSelectedListener
getOnItemViewSelectedListener() {
232 return mOnItemViewSelectedListener
;
236 void onRowSelected(RecyclerView parent
, RecyclerView
.ViewHolder viewHolder
,
237 int position
, int subposition
) {
238 if (mSelectedViewHolder
!= viewHolder
|| mSubPosition
!= subposition
) {
239 if (DEBUG
) Log
.v(TAG
, "new row selected position " + position
+ " subposition "
240 + subposition
+ " view " + viewHolder
.itemView
);
241 mSubPosition
= subposition
;
242 if (mSelectedViewHolder
!= null) {
243 setRowViewSelected(mSelectedViewHolder
, false, false);
245 mSelectedViewHolder
= (ItemBridgeAdapter
.ViewHolder
) viewHolder
;
246 if (mSelectedViewHolder
!= null) {
247 setRowViewSelected(mSelectedViewHolder
, true, false);
250 // When RowsFragment is embedded inside a page fragment, we want to show
251 // the title view only when we're on the first row or there is no data.
252 if (mMainFragmentAdapter
!= null) {
253 mMainFragmentAdapter
.getFragmentHost().showTitleView(position
<= 0);
258 * Get row ViewHolder at adapter position. Returns null if the row object is not in adapter or
259 * the row object has not been bound to a row view.
261 * @param position Position of row in adapter.
262 * @return Row ViewHolder at a given adapter position.
264 public RowPresenter
.ViewHolder
getRowViewHolder(int position
) {
265 VerticalGridView verticalView
= getVerticalGridView();
266 if (verticalView
== null) {
269 return getRowViewHolder((ItemBridgeAdapter
.ViewHolder
)
270 verticalView
.findViewHolderForAdapterPosition(position
));
274 int getLayoutResourceId() {
275 return R
.layout
.lb_rows_fragment
;
279 public void onCreate(Bundle savedInstanceState
) {
280 super.onCreate(savedInstanceState
);
281 mSelectAnimatorDuration
= getResources().getInteger(
282 R
.integer
.lb_browse_rows_anim_duration
);
286 public void onViewCreated(View view
, Bundle savedInstanceState
) {
287 if (DEBUG
) Log
.v(TAG
, "onViewCreated");
288 super.onViewCreated(view
, savedInstanceState
);
289 // Align the top edge of child with id row_content.
290 // Need set this for directly using RowsFragment.
291 getVerticalGridView().setItemAlignmentViewId(R
.id
.row_content
);
292 getVerticalGridView().setSaveChildrenPolicy(VerticalGridView
.SAVE_LIMITED_CHILD
);
294 setAlignment(mAlignedTop
);
296 mRecycledViewPool
= null;
297 mPresenterMapper
= null;
298 if (mMainFragmentAdapter
!= null) {
299 mMainFragmentAdapter
.getFragmentHost().notifyViewCreated(mMainFragmentAdapter
);
305 public void onDestroyView() {
306 mViewsCreated
= false;
307 super.onDestroyView();
310 void setExternalAdapterListener(ItemBridgeAdapter
.AdapterListener listener
) {
311 mExternalAdapterListener
= listener
;
314 static void setRowViewExpanded(ItemBridgeAdapter
.ViewHolder vh
, boolean expanded
) {
315 ((RowPresenter
) vh
.getPresenter()).setRowViewExpanded(vh
.getViewHolder(), expanded
);
318 static void setRowViewSelected(ItemBridgeAdapter
.ViewHolder vh
, boolean selected
,
320 RowViewHolderExtra extra
= (RowViewHolderExtra
) vh
.getExtraObject();
321 extra
.animateSelect(selected
, immediate
);
322 ((RowPresenter
) vh
.getPresenter()).setRowViewSelected(vh
.getViewHolder(), selected
);
325 private final ItemBridgeAdapter
.AdapterListener mBridgeAdapterListener
=
326 new ItemBridgeAdapter
.AdapterListener() {
328 public void onAddPresenter(Presenter presenter
, int type
) {
329 if (mExternalAdapterListener
!= null) {
330 mExternalAdapterListener
.onAddPresenter(presenter
, type
);
335 public void onCreate(ItemBridgeAdapter
.ViewHolder vh
) {
336 VerticalGridView listView
= getVerticalGridView();
337 if (listView
!= null) {
338 // set clip children false for slide animation
339 listView
.setClipChildren(false);
341 setupSharedViewPool(vh
);
342 mViewsCreated
= true;
343 vh
.setExtraObject(new RowViewHolderExtra(vh
));
344 // selected state is initialized to false, then driven by grid view onChildSelected
345 // events. When there is rebind, grid view fires onChildSelected event properly.
346 // So we don't need do anything special later in onBind or onAttachedToWindow.
347 setRowViewSelected(vh
, false, true);
348 if (mExternalAdapterListener
!= null) {
349 mExternalAdapterListener
.onCreate(vh
);
354 public void onAttachedToWindow(ItemBridgeAdapter
.ViewHolder vh
) {
355 if (DEBUG
) Log
.v(TAG
, "onAttachToWindow");
356 // All views share the same mExpand value. When we attach a view to grid view,
357 // we should make sure it pick up the latest mExpand value we set early on other
358 // attached views. For no-structure-change update, the view is rebound to new data,
359 // but again it should use the unchanged mExpand value, so we don't need do any
361 setRowViewExpanded(vh
, mExpand
);
362 RowPresenter rowPresenter
= (RowPresenter
) vh
.getPresenter();
363 RowPresenter
.ViewHolder rowVh
= rowPresenter
.getRowViewHolder(vh
.getViewHolder());
364 rowVh
.setOnItemViewSelectedListener(mOnItemViewSelectedListener
);
365 rowVh
.setOnItemViewClickedListener(mOnItemViewClickedListener
);
366 rowPresenter
.setEntranceTransitionState(rowVh
, mAfterEntranceTransition
);
367 if (mExternalAdapterListener
!= null) {
368 mExternalAdapterListener
.onAttachedToWindow(vh
);
373 public void onDetachedFromWindow(ItemBridgeAdapter
.ViewHolder vh
) {
374 if (mSelectedViewHolder
== vh
) {
375 setRowViewSelected(mSelectedViewHolder
, false, true);
376 mSelectedViewHolder
= null;
378 if (mExternalAdapterListener
!= null) {
379 mExternalAdapterListener
.onDetachedFromWindow(vh
);
384 public void onBind(ItemBridgeAdapter
.ViewHolder vh
) {
385 if (mExternalAdapterListener
!= null) {
386 mExternalAdapterListener
.onBind(vh
);
391 public void onUnbind(ItemBridgeAdapter
.ViewHolder vh
) {
392 setRowViewSelected(vh
, false, true);
393 if (mExternalAdapterListener
!= null) {
394 mExternalAdapterListener
.onUnbind(vh
);
399 void setupSharedViewPool(ItemBridgeAdapter
.ViewHolder bridgeVh
) {
400 RowPresenter rowPresenter
= (RowPresenter
) bridgeVh
.getPresenter();
401 RowPresenter
.ViewHolder rowVh
= rowPresenter
.getRowViewHolder(bridgeVh
.getViewHolder());
403 if (rowVh
instanceof ListRowPresenter
.ViewHolder
) {
404 HorizontalGridView view
= ((ListRowPresenter
.ViewHolder
) rowVh
).getGridView();
405 // Recycled view pool is shared between all list rows
406 if (mRecycledViewPool
== null) {
407 mRecycledViewPool
= view
.getRecycledViewPool();
409 view
.setRecycledViewPool(mRecycledViewPool
);
412 ItemBridgeAdapter bridgeAdapter
=
413 ((ListRowPresenter
.ViewHolder
) rowVh
).getBridgeAdapter();
414 if (mPresenterMapper
== null) {
415 mPresenterMapper
= bridgeAdapter
.getPresenterMapper();
417 bridgeAdapter
.setPresenterMapper(mPresenterMapper
);
423 void updateAdapter() {
424 super.updateAdapter();
425 mSelectedViewHolder
= null;
426 mViewsCreated
= false;
428 ItemBridgeAdapter adapter
= getBridgeAdapter();
429 if (adapter
!= null) {
430 adapter
.setAdapterListener(mBridgeAdapterListener
);
435 public boolean onTransitionPrepare() {
436 boolean prepared
= super.onTransitionPrepare();
444 public void onTransitionEnd() {
445 super.onTransitionEnd();
449 private void freezeRows(boolean freeze
) {
450 VerticalGridView verticalView
= getVerticalGridView();
451 if (verticalView
!= null) {
452 final int count
= verticalView
.getChildCount();
453 for (int i
= 0; i
< count
; i
++) {
454 ItemBridgeAdapter
.ViewHolder ibvh
= (ItemBridgeAdapter
.ViewHolder
)
455 verticalView
.getChildViewHolder(verticalView
.getChildAt(i
));
456 RowPresenter rowPresenter
= (RowPresenter
) ibvh
.getPresenter();
457 RowPresenter
.ViewHolder vh
= rowPresenter
.getRowViewHolder(ibvh
.getViewHolder());
458 rowPresenter
.freeze(vh
, freeze
);
464 * For rows that willing to participate entrance transition, this function
465 * hide views if afterTransition is true, show views if afterTransition is false.
467 public void setEntranceTransitionState(boolean afterTransition
) {
468 mAfterEntranceTransition
= afterTransition
;
469 VerticalGridView verticalView
= getVerticalGridView();
470 if (verticalView
!= null) {
471 final int count
= verticalView
.getChildCount();
472 for (int i
= 0; i
< count
; i
++) {
473 ItemBridgeAdapter
.ViewHolder ibvh
= (ItemBridgeAdapter
.ViewHolder
)
474 verticalView
.getChildViewHolder(verticalView
.getChildAt(i
));
475 RowPresenter rowPresenter
= (RowPresenter
) ibvh
.getPresenter();
476 RowPresenter
.ViewHolder vh
= rowPresenter
.getRowViewHolder(ibvh
.getViewHolder());
477 rowPresenter
.setEntranceTransitionState(vh
, mAfterEntranceTransition
);
483 * Selects a Row and perform an optional task on the Row. For example
484 * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
485 * Scroll to 11th row and selects 6th item on that row. The method will be ignored if
486 * RowsFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
487 * ViewGroup, Bundle)}).
489 * @param rowPosition Which row to select.
490 * @param smooth True to scroll to the row, false for no animation.
491 * @param rowHolderTask Task to perform on the Row.
493 public void setSelectedPosition(int rowPosition
, boolean smooth
,
494 final Presenter
.ViewHolderTask rowHolderTask
) {
495 VerticalGridView verticalView
= getVerticalGridView();
496 if (verticalView
== null) {
499 ViewHolderTask task
= null;
500 if (rowHolderTask
!= null) {
501 // This task will execute once the scroll completes. Once the scrolling finishes,
502 // we will get a success callback to update selected row position. Since the
503 // update to selected row position happens in a post, we want to ensure that this
504 // gets called after that.
505 task
= new ViewHolderTask() {
507 public void run(final RecyclerView
.ViewHolder rvh
) {
508 rvh
.itemView
.post(new Runnable() {
512 getRowViewHolder((ItemBridgeAdapter
.ViewHolder
) rvh
));
520 verticalView
.setSelectedPositionSmooth(rowPosition
, task
);
522 verticalView
.setSelectedPosition(rowPosition
, task
);
526 static RowPresenter
.ViewHolder
getRowViewHolder(ItemBridgeAdapter
.ViewHolder ibvh
) {
530 RowPresenter rowPresenter
= (RowPresenter
) ibvh
.getPresenter();
531 return rowPresenter
.getRowViewHolder(ibvh
.getViewHolder());
534 public boolean isScrolling() {
535 if (getVerticalGridView() == null) {
538 return getVerticalGridView().getScrollState() != HorizontalGridView
.SCROLL_STATE_IDLE
;
542 public void setAlignment(int windowAlignOffsetFromTop
) {
543 mAlignedTop
= windowAlignOffsetFromTop
;
544 final VerticalGridView gridView
= getVerticalGridView();
546 if (gridView
!= null) {
547 gridView
.setItemAlignmentOffset(0);
548 gridView
.setItemAlignmentOffsetPercent(
549 VerticalGridView
.ITEM_ALIGN_OFFSET_PERCENT_DISABLED
);
550 gridView
.setItemAlignmentOffsetWithPadding(true);
551 gridView
.setWindowAlignmentOffset(mAlignedTop
);
552 // align to a fixed position from top
553 gridView
.setWindowAlignmentOffsetPercent(
554 VerticalGridView
.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED
);
555 gridView
.setWindowAlignment(VerticalGridView
.WINDOW_ALIGN_NO_EDGE
);
559 public static class MainFragmentAdapter
extends BrowseFragment
.MainFragmentAdapter
<RowsFragment
> {
561 public MainFragmentAdapter(RowsFragment fragment
) {
563 setScalingEnabled(true);
567 public boolean isScrolling() {
568 return getFragment().isScrolling();
572 public void setExpand(boolean expand
) {
573 getFragment().setExpand(expand
);
577 public void setEntranceTransitionState(boolean state
) {
578 getFragment().setEntranceTransitionState(state
);
582 public void setAlignment(int windowAlignOffsetFromTop
) {
583 getFragment().setAlignment(windowAlignOffsetFromTop
);
587 public boolean onTransitionPrepare() {
588 return getFragment().onTransitionPrepare();
592 public void onTransitionStart() {
593 getFragment().onTransitionStart();
597 public void onTransitionEnd() {
598 getFragment().onTransitionEnd();
603 public static class MainFragmentRowsAdapter
604 extends BrowseFragment
.MainFragmentRowsAdapter
<RowsFragment
> {
606 public MainFragmentRowsAdapter(RowsFragment fragment
) {
611 public void setAdapter(ObjectAdapter adapter
) {
612 getFragment().setAdapter(adapter
);
616 * Sets an item clicked listener on the fragment.
619 public void setOnItemViewClickedListener(OnItemViewClickedListener listener
) {
620 getFragment().setOnItemViewClickedListener(listener
);
624 public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener
) {
625 getFragment().setOnItemViewSelectedListener(listener
);
629 public void setSelectedPosition(int rowPosition
,
631 final Presenter
.ViewHolderTask rowHolderTask
) {
632 getFragment().setSelectedPosition(rowPosition
, smooth
, rowHolderTask
);
636 public void setSelectedPosition(int rowPosition
, boolean smooth
) {
637 getFragment().setSelectedPosition(rowPosition
, smooth
);
641 public int getSelectedPosition() {
642 return getFragment().getSelectedPosition();