• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 androidx.leanback.app;
15 
16 import android.os.Bundle;
17 import android.view.LayoutInflater;
18 import android.view.View;
19 import android.view.ViewGroup;
20 
21 import androidx.annotation.NonNull;
22 import androidx.annotation.Nullable;
23 import androidx.fragment.app.Fragment;
24 import androidx.leanback.widget.ItemBridgeAdapter;
25 import androidx.leanback.widget.ListRow;
26 import androidx.leanback.widget.ObjectAdapter;
27 import androidx.leanback.widget.OnChildViewHolderSelectedListener;
28 import androidx.leanback.widget.PresenterSelector;
29 import androidx.leanback.widget.Row;
30 import androidx.leanback.widget.VerticalGridView;
31 import androidx.recyclerview.widget.RecyclerView;
32 
33 /**
34  * An internal base class for a fragment containing a list of rows.
35  */
36 abstract class BaseRowSupportFragment extends Fragment {
37     private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
38     private ObjectAdapter mAdapter;
39     VerticalGridView mVerticalGridView;
40     private PresenterSelector mPresenterSelector;
41     final ItemBridgeAdapter mBridgeAdapter = new ItemBridgeAdapter();
42     int mSelectedPosition = -1;
43     private boolean mPendingTransitionPrepare;
44     private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
45 
getLayoutResourceId()46     abstract int getLayoutResourceId();
47 
48     private final OnChildViewHolderSelectedListener mRowSelectedListener =
49             new OnChildViewHolderSelectedListener() {
50                 @Override
51                 public void onChildViewHolderSelected(RecyclerView parent,
52                         RecyclerView.ViewHolder view, int position, int subposition) {
53                     if (!mLateSelectionObserver.mIsLateSelection) {
54                         mSelectedPosition = position;
55                         onRowSelected(parent, view, position, subposition);
56                     }
57                 }
58             };
59 
onRowSelected(RecyclerView parent, RecyclerView.ViewHolder view, int position, int subposition)60     void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder view,
61             int position, int subposition) {
62     }
63 
64     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)65     public View onCreateView(LayoutInflater inflater, ViewGroup container,
66             Bundle savedInstanceState) {
67         View view = inflater.inflate(getLayoutResourceId(), container, false);
68         mVerticalGridView = findGridViewFromRoot(view);
69         if (mPendingTransitionPrepare) {
70             mPendingTransitionPrepare = false;
71             onTransitionPrepare();
72         }
73         return view;
74     }
75 
findGridViewFromRoot(View view)76     VerticalGridView findGridViewFromRoot(View view) {
77         return (VerticalGridView) view;
78     }
79 
80     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)81     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
82         if (savedInstanceState != null) {
83             mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
84         }
85         setAdapterAndSelection();
86         mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
87     }
88 
89     /**
90      * This class waits for the adapter to be updated before setting the selected
91      * row.
92      */
93     private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
94         boolean mIsLateSelection = false;
95 
LateSelectionObserver()96         LateSelectionObserver() {
97         }
98 
99         @Override
onChanged()100         public void onChanged() {
101             performLateSelection();
102         }
103 
104         @Override
onItemRangeInserted(int positionStart, int itemCount)105         public void onItemRangeInserted(int positionStart, int itemCount) {
106             performLateSelection();
107         }
108 
startLateSelection()109         void startLateSelection() {
110             mIsLateSelection = true;
111             mBridgeAdapter.registerAdapterDataObserver(this);
112         }
113 
performLateSelection()114         void performLateSelection() {
115             clear();
116             if (mVerticalGridView != null) {
117                 mVerticalGridView.setSelectedPosition(mSelectedPosition);
118             }
119         }
120 
clear()121         void clear() {
122             if (mIsLateSelection) {
123                 mIsLateSelection = false;
124                 mBridgeAdapter.unregisterAdapterDataObserver(this);
125             }
126         }
127     }
128 
setAdapterAndSelection()129     void setAdapterAndSelection() {
130         if (mAdapter == null) {
131             // delay until ItemBridgeAdapter has wrappedAdapter. Once we assign ItemBridgeAdapter
132             // to RecyclerView, it will not be allowed to change "hasStableId" to true.
133             return;
134         }
135         if (mVerticalGridView.getAdapter() != mBridgeAdapter) {
136             // avoid extra layout if ItemBridgeAdapter was already set.
137             mVerticalGridView.setAdapter(mBridgeAdapter);
138         }
139         // We don't set the selected position unless we've data in the adapter.
140         boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
141         if (lateSelection) {
142             mLateSelectionObserver.startLateSelection();
143         } else if (mSelectedPosition >= 0) {
144             mVerticalGridView.setSelectedPosition(mSelectedPosition);
145         }
146     }
147 
148     @Override
onDestroyView()149     public void onDestroyView() {
150         super.onDestroyView();
151         mLateSelectionObserver.clear();
152         mVerticalGridView = null;
153     }
154 
155     @Override
onSaveInstanceState(Bundle outState)156     public void onSaveInstanceState(Bundle outState) {
157         super.onSaveInstanceState(outState);
158         outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
159     }
160 
161     /**
162      * Set the presenter selector used to create and bind views.
163      */
setPresenterSelector(PresenterSelector presenterSelector)164     public final void setPresenterSelector(PresenterSelector presenterSelector) {
165         if (mPresenterSelector != presenterSelector) {
166             mPresenterSelector = presenterSelector;
167             updateAdapter();
168         }
169     }
170 
171     /**
172      * Get the presenter selector used to create and bind views.
173      */
getPresenterSelector()174     public final PresenterSelector getPresenterSelector() {
175         return mPresenterSelector;
176     }
177 
178     /**
179      * Sets the adapter that represents a list of rows.
180      * @param rowsAdapter Adapter that represents list of rows.
181      */
setAdapter(ObjectAdapter rowsAdapter)182     public final void setAdapter(ObjectAdapter rowsAdapter) {
183         if (mAdapter != rowsAdapter) {
184             mAdapter = rowsAdapter;
185             updateAdapter();
186         }
187     }
188 
189     /**
190      * Returns the Adapter that represents list of rows.
191      * @return Adapter that represents list of rows.
192      */
getAdapter()193     public final ObjectAdapter getAdapter() {
194         return mAdapter;
195     }
196 
197     /**
198      * Returns the RecyclerView.Adapter that wraps {@link #getAdapter()}.
199      * @return The RecyclerView.Adapter that wraps {@link #getAdapter()}.
200      */
getBridgeAdapter()201     public final ItemBridgeAdapter getBridgeAdapter() {
202         return mBridgeAdapter;
203     }
204 
205     /**
206      * Sets the selected row position with smooth animation.
207      */
setSelectedPosition(int position)208     public void setSelectedPosition(int position) {
209         setSelectedPosition(position, true);
210     }
211 
212     /**
213      * Gets position of currently selected row.
214      * @return Position of currently selected row.
215      */
getSelectedPosition()216     public int getSelectedPosition() {
217         return mSelectedPosition;
218     }
219 
220     /**
221      * Sets the selected row position.
222      */
setSelectedPosition(int position, boolean smooth)223     public void setSelectedPosition(int position, boolean smooth) {
224         if (mSelectedPosition == position) {
225             return;
226         }
227         mSelectedPosition = position;
228         if (mVerticalGridView != null) {
229             if (mLateSelectionObserver.mIsLateSelection) {
230                 return;
231             }
232             if (smooth) {
233                 mVerticalGridView.setSelectedPositionSmooth(position);
234             } else {
235                 mVerticalGridView.setSelectedPosition(position);
236             }
237         }
238     }
239 
getVerticalGridView()240     public final VerticalGridView getVerticalGridView() {
241         return mVerticalGridView;
242     }
243 
updateAdapter()244     void updateAdapter() {
245         mBridgeAdapter.setAdapter(mAdapter);
246         mBridgeAdapter.setPresenter(mPresenterSelector);
247 
248         if (mVerticalGridView != null) {
249             setAdapterAndSelection();
250         }
251     }
252 
getItem(Row row, int position)253     Object getItem(Row row, int position) {
254         if (row instanceof ListRow) {
255             return ((ListRow) row).getAdapter().get(position);
256         } else {
257             return null;
258         }
259     }
260 
onTransitionPrepare()261     public boolean onTransitionPrepare() {
262         if (mVerticalGridView != null) {
263             mVerticalGridView.setAnimateChildLayout(false);
264             mVerticalGridView.setScrollEnabled(false);
265             return true;
266         }
267         mPendingTransitionPrepare = true;
268         return false;
269     }
270 
onTransitionStart()271     public void onTransitionStart() {
272         if (mVerticalGridView != null) {
273             mVerticalGridView.setPruneChild(false);
274             mVerticalGridView.setLayoutFrozen(true);
275             mVerticalGridView.setFocusSearchDisabled(true);
276         }
277     }
278 
onTransitionEnd()279     public void onTransitionEnd() {
280         // be careful that fragment might be destroyed before header transition ends.
281         if (mVerticalGridView != null) {
282             mVerticalGridView.setLayoutFrozen(false);
283             mVerticalGridView.setAnimateChildLayout(true);
284             mVerticalGridView.setPruneChild(true);
285             mVerticalGridView.setFocusSearchDisabled(false);
286             mVerticalGridView.setScrollEnabled(true);
287         }
288     }
289 
setAlignment(int windowAlignOffsetTop)290     public void setAlignment(int windowAlignOffsetTop) {
291         if (mVerticalGridView != null) {
292             // align the top edge of item
293             mVerticalGridView.setItemAlignmentOffset(0);
294             mVerticalGridView.setItemAlignmentOffsetPercent(
295                     VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
296 
297             // align to a fixed position from top
298             mVerticalGridView.setWindowAlignmentOffset(windowAlignOffsetTop);
299             mVerticalGridView.setWindowAlignmentOffsetPercent(
300                     VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
301             mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
302         }
303     }
304 }
305