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 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 60 void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder view, 61 int position, int subposition) { 62 } 63 64 @Override 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 76 VerticalGridView findGridViewFromRoot(View view) { 77 return (VerticalGridView) view; 78 } 79 80 @Override 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 96 LateSelectionObserver() { 97 } 98 99 @Override 100 public void onChanged() { 101 performLateSelection(); 102 } 103 104 @Override 105 public void onItemRangeInserted(int positionStart, int itemCount) { 106 performLateSelection(); 107 } 108 109 void startLateSelection() { 110 mIsLateSelection = true; 111 mBridgeAdapter.registerAdapterDataObserver(this); 112 } 113 114 void performLateSelection() { 115 clear(); 116 if (mVerticalGridView != null) { 117 mVerticalGridView.setSelectedPosition(mSelectedPosition); 118 } 119 } 120 121 void clear() { 122 if (mIsLateSelection) { 123 mIsLateSelection = false; 124 mBridgeAdapter.unregisterAdapterDataObserver(this); 125 } 126 } 127 } 128 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 149 public void onDestroyView() { 150 super.onDestroyView(); 151 mLateSelectionObserver.clear(); 152 mVerticalGridView = null; 153 } 154 155 @Override 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 */ 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 */ 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 */ 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 */ 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 */ 201 public final ItemBridgeAdapter getBridgeAdapter() { 202 return mBridgeAdapter; 203 } 204 205 /** 206 * Sets the selected row position with smooth animation. 207 */ 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 */ 216 public int getSelectedPosition() { 217 return mSelectedPosition; 218 } 219 220 /** 221 * Sets the selected row position. 222 */ 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 240 public final VerticalGridView getVerticalGridView() { 241 return mVerticalGridView; 242 } 243 244 void updateAdapter() { 245 mBridgeAdapter.setAdapter(mAdapter); 246 mBridgeAdapter.setPresenter(mPresenterSelector); 247 248 if (mVerticalGridView != null) { 249 setAdapterAndSelection(); 250 } 251 } 252 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 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 271 public void onTransitionStart() { 272 if (mVerticalGridView != null) { 273 mVerticalGridView.setPruneChild(false); 274 mVerticalGridView.setLayoutFrozen(true); 275 mVerticalGridView.setFocusSearchDisabled(true); 276 } 277 } 278 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 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