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