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 { onControlSelected(Presenter.ViewHolder controlViewHolder, Object item, BoundData data)54 void onControlSelected(Presenter.ViewHolder controlViewHolder, Object item, 55 BoundData data); 56 } 57 58 /** 59 * Listener for control clicked events. 60 */ 61 interface OnControlClickedListener { onControlClicked(Presenter.ViewHolder controlViewHolder, Object item, BoundData data)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 */ ViewHolder(View rootView)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 getChildMarginFromCenter(Context context, int numControls)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 showControls(Presenter presenter)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 bindControlToAction(int position, Presenter presenter)146 void bindControlToAction(int position, Presenter presenter) { 147 bindControlToAction(position, getDisplayedAdapter(), presenter); 148 } 149 bindControlToAction(final int position, ObjectAdapter adapter, Presenter presenter)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 */ getDisplayedAdapter()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 */ ControlBarPresenter(int layoutResourceId)197 public ControlBarPresenter(int layoutResourceId) { 198 mLayoutResourceId = layoutResourceId; 199 } 200 201 /** 202 * Returns the layout resource id. 203 */ getLayoutResourceId()204 public int getLayoutResourceId() { 205 return mLayoutResourceId; 206 } 207 208 /** 209 * Sets the listener for control clicked events. 210 */ setOnControlClickedListener(OnControlClickedListener listener)211 public void setOnControlClickedListener(OnControlClickedListener listener) { 212 mOnControlClickedListener = listener; 213 } 214 215 /** 216 * Returns the listener for control clicked events. 217 */ getOnItemViewClickedListener()218 public OnControlClickedListener getOnItemViewClickedListener() { 219 return mOnControlClickedListener; 220 } 221 222 /** 223 * Sets the listener for control selection. 224 */ setOnControlSelectedListener(OnControlSelectedListener listener)225 public void setOnControlSelectedListener(OnControlSelectedListener listener) { 226 mOnControlSelectedListener = listener; 227 } 228 229 /** 230 * Returns the listener for control selection. 231 */ getOnItemControlListener()232 public OnControlSelectedListener getOnItemControlListener() { 233 return mOnControlSelectedListener; 234 } 235 setBackgroundColor(ViewHolder vh, int color)236 public void setBackgroundColor(ViewHolder vh, int color) { 237 vh.mControlsContainer.setBackgroundColor(color); 238 } 239 240 @Override onCreateViewHolder(ViewGroup parent)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 onBindViewHolder(Presenter.ViewHolder holder, Object item)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 onUnbindViewHolder(Presenter.ViewHolder holder)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 getChildMarginDefault(Context context)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 getControlIconWidth(Context context)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 */ setDefaultFocusToMiddle(boolean defaultFocusToMiddle)291 void setDefaultFocusToMiddle(boolean defaultFocusToMiddle) { 292 mDefaultFocusToMiddle = defaultFocusToMiddle; 293 } 294 295 } 296