Home | History | Annotate | Download | only in app
      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