Home | History | Annotate | Download | only in widget
      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.widget;
     15 
     16 import android.content.Context;
     17 import android.util.SparseArray;
     18 import android.view.LayoutInflater;
     19 import android.view.View;
     20 import android.view.ViewGroup;
     21 
     22 import androidx.leanback.R;
     23 
     24 /**
     25  * A presenter that assumes a LinearLayout container for a series
     26  * of control buttons backed by objects of type {@link Action}.
     27  *
     28  * Different layouts may be passed to the presenter constructor.
     29  * The layout must contain a view with id control_bar.
     30  */
     31 class ControlBarPresenter extends Presenter {
     32 
     33     static final int MAX_CONTROLS = 7;
     34 
     35     /**
     36      * The data type expected by this presenter.
     37      */
     38     static class BoundData {
     39         /**
     40          * Adapter containing objects of type {@link Action}.
     41          */
     42         ObjectAdapter adapter;
     43 
     44         /**
     45          * The presenter to be used for the adapter objects.
     46          */
     47         Presenter presenter;
     48     }
     49 
     50     /**
     51      * Listener for control selected events.
     52      */
     53     interface OnControlSelectedListener {
     54         void onControlSelected(Presenter.ViewHolder controlViewHolder, Object item,
     55                 BoundData data);
     56     }
     57 
     58     /**
     59      * Listener for control clicked events.
     60      */
     61     interface OnControlClickedListener {
     62         void onControlClicked(Presenter.ViewHolder controlViewHolder, Object item,
     63                 BoundData data);
     64     }
     65 
     66     class ViewHolder extends Presenter.ViewHolder {
     67         ObjectAdapter mAdapter;
     68         BoundData mData;
     69         Presenter mPresenter;
     70         ControlBar mControlBar;
     71         View mControlsContainer;
     72         SparseArray<Presenter.ViewHolder> mViewHolders =
     73                 new SparseArray<Presenter.ViewHolder>();
     74         ObjectAdapter.DataObserver mDataObserver;
     75 
     76         /**
     77          * Constructor for the ViewHolder.
     78          */
     79         ViewHolder(View rootView) {
     80             super(rootView);
     81             mControlsContainer = rootView.findViewById(R.id.controls_container);
     82             mControlBar = (ControlBar) rootView.findViewById(R.id.control_bar);
     83             if (mControlBar == null) {
     84                 throw new IllegalStateException("Couldn't find control_bar");
     85             }
     86             mControlBar.setDefaultFocusToMiddle(mDefaultFocusToMiddle);
     87             mControlBar.setOnChildFocusedListener(new ControlBar.OnChildFocusedListener() {
     88                 @Override
     89                 public void onChildFocusedListener(View child, View focused) {
     90                     if (mOnControlSelectedListener == null) {
     91                         return;
     92                     }
     93                     for (int position = 0; position < mViewHolders.size(); position++) {
     94                         if (mViewHolders.get(position).view == child) {
     95                             mOnControlSelectedListener.onControlSelected(
     96                                     mViewHolders.get(position),
     97                                     getDisplayedAdapter().get(position), mData);
     98                             break;
     99                         }
    100                     }
    101                 }
    102             });
    103             mDataObserver = new ObjectAdapter.DataObserver() {
    104                 @Override
    105                 public void onChanged() {
    106                     if (mAdapter == getDisplayedAdapter()) {
    107                         showControls(mPresenter);
    108                     }
    109                 }
    110                 @Override
    111                 public void onItemRangeChanged(int positionStart, int itemCount) {
    112                     if (mAdapter == getDisplayedAdapter()) {
    113                         for (int i = 0; i < itemCount; i++) {
    114                             bindControlToAction(positionStart + i, mPresenter);
    115                         }
    116                     }
    117                 }
    118             };
    119         }
    120 
    121         int getChildMarginFromCenter(Context context, int numControls) {
    122             // Includes margin between icons plus two times half the icon width.
    123             return getChildMarginDefault(context) + getControlIconWidth(context);
    124         }
    125 
    126         void showControls(Presenter presenter) {
    127             ObjectAdapter adapter = getDisplayedAdapter();
    128             int adapterSize = adapter == null ? 0 : adapter.size();
    129             // Shrink the number of attached views
    130             View focusedView = mControlBar.getFocusedChild();
    131             if (focusedView != null && adapterSize > 0
    132                     && mControlBar.indexOfChild(focusedView) >= adapterSize) {
    133                 mControlBar.getChildAt(adapter.size() - 1).requestFocus();
    134             }
    135             for (int i = mControlBar.getChildCount() - 1; i >= adapterSize; i--) {
    136                 mControlBar.removeViewAt(i);
    137             }
    138             for (int position = 0; position < adapterSize && position < MAX_CONTROLS;
    139                     position++) {
    140                 bindControlToAction(position, adapter, presenter);
    141             }
    142             mControlBar.setChildMarginFromCenter(
    143                     getChildMarginFromCenter(mControlBar.getContext(), adapterSize));
    144         }
    145 
    146         void bindControlToAction(int position, Presenter presenter) {
    147             bindControlToAction(position, getDisplayedAdapter(), presenter);
    148         }
    149 
    150         private void bindControlToAction(final int position,
    151                 ObjectAdapter adapter, Presenter presenter) {
    152             Presenter.ViewHolder vh = mViewHolders.get(position);
    153             Object item = adapter.get(position);
    154             if (vh == null) {
    155                 vh = presenter.onCreateViewHolder(mControlBar);
    156                 mViewHolders.put(position, vh);
    157 
    158                 final Presenter.ViewHolder itemViewHolder = vh;
    159                 presenter.setOnClickListener(vh, new View.OnClickListener() {
    160                     @Override
    161                     public void onClick(View v) {
    162                         Object item = getDisplayedAdapter().get(position);
    163                         if (mOnControlClickedListener != null) {
    164                             mOnControlClickedListener.onControlClicked(itemViewHolder, item,
    165                                     mData);
    166                         }
    167                     }
    168                 });
    169             }
    170             if (vh.view.getParent() == null) {
    171                 mControlBar.addView(vh.view);
    172             }
    173             presenter.onBindViewHolder(vh, item);
    174         }
    175 
    176         /**
    177          * Returns the adapter currently bound to the displayed controls.
    178          * May be overridden in a subclass.
    179          */
    180         ObjectAdapter getDisplayedAdapter() {
    181             return mAdapter;
    182         }
    183     }
    184 
    185     OnControlClickedListener mOnControlClickedListener;
    186     OnControlSelectedListener mOnControlSelectedListener;
    187     private int mLayoutResourceId;
    188     private static int sChildMarginDefault;
    189     private static int sControlIconWidth;
    190     boolean mDefaultFocusToMiddle = true;
    191 
    192     /**
    193      * Constructor for a ControlBarPresenter.
    194      *
    195      * @param layoutResourceId The resource id of the layout for this presenter.
    196      */
    197     public ControlBarPresenter(int layoutResourceId) {
    198         mLayoutResourceId = layoutResourceId;
    199     }
    200 
    201     /**
    202      * Returns the layout resource id.
    203      */
    204     public int getLayoutResourceId() {
    205         return mLayoutResourceId;
    206     }
    207 
    208     /**
    209      * Sets the listener for control clicked events.
    210      */
    211     public void setOnControlClickedListener(OnControlClickedListener listener) {
    212         mOnControlClickedListener = listener;
    213     }
    214 
    215     /**
    216      * Returns the listener for control clicked events.
    217      */
    218     public OnControlClickedListener getOnItemViewClickedListener() {
    219         return mOnControlClickedListener;
    220     }
    221 
    222     /**
    223      * Sets the listener for control selection.
    224      */
    225     public void setOnControlSelectedListener(OnControlSelectedListener listener) {
    226         mOnControlSelectedListener = listener;
    227     }
    228 
    229     /**
    230      * Returns the listener for control selection.
    231      */
    232     public OnControlSelectedListener getOnItemControlListener() {
    233         return mOnControlSelectedListener;
    234     }
    235 
    236     public void setBackgroundColor(ViewHolder vh, int color) {
    237         vh.mControlsContainer.setBackgroundColor(color);
    238     }
    239 
    240     @Override
    241     public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
    242         View v = LayoutInflater.from(parent.getContext())
    243             .inflate(getLayoutResourceId(), parent, false);
    244         return new ViewHolder(v);
    245     }
    246 
    247     @Override
    248     public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
    249         ViewHolder vh = (ViewHolder) holder;
    250         BoundData data = (BoundData) item;
    251         if (vh.mAdapter != data.adapter) {
    252             vh.mAdapter = data.adapter;
    253             if (vh.mAdapter != null) {
    254                 vh.mAdapter.registerObserver(vh.mDataObserver);
    255             }
    256         }
    257         vh.mPresenter = data.presenter;
    258         vh.mData = data;
    259         vh.showControls(vh.mPresenter);
    260     }
    261 
    262     @Override
    263     public void onUnbindViewHolder(Presenter.ViewHolder holder) {
    264         ViewHolder vh = (ViewHolder) holder;
    265         if (vh.mAdapter != null) {
    266             vh.mAdapter.unregisterObserver(vh.mDataObserver);
    267             vh.mAdapter = null;
    268         }
    269         vh.mData = null;
    270     }
    271 
    272     int getChildMarginDefault(Context context) {
    273         if (sChildMarginDefault == 0) {
    274             sChildMarginDefault = context.getResources().getDimensionPixelSize(
    275                     R.dimen.lb_playback_controls_child_margin_default);
    276         }
    277         return sChildMarginDefault;
    278     }
    279 
    280     int getControlIconWidth(Context context) {
    281         if (sControlIconWidth == 0) {
    282             sControlIconWidth = context.getResources().getDimensionPixelSize(
    283                     R.dimen.lb_control_icon_width);
    284         }
    285         return sControlIconWidth;
    286     }
    287 
    288     /**
    289      * @param defaultFocusToMiddle True for middle item, false for 0.
    290      */
    291     void setDefaultFocusToMiddle(boolean defaultFocusToMiddle) {
    292         mDefaultFocusToMiddle = defaultFocusToMiddle;
    293     }
    294 
    295 }
    296