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 android.support.v17.leanback.widget; 15 16 import android.content.Context; 17 import android.support.v17.leanback.R; 18 import android.util.SparseArray; 19 import android.view.LayoutInflater; 20 import android.view.View; 21 import android.view.ViewGroup; 22 import android.widget.LinearLayout; 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 private 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.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 getChildMarginFromCenter(Context context, int numControls)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 showControls(Presenter presenter)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 bindControlToAction(int position, Presenter presenter)145 void bindControlToAction(int position, Presenter presenter) { 146 bindControlToAction(position, getDisplayedAdapter(), presenter); 147 } 148 bindControlToAction(final int position, ObjectAdapter adapter, Presenter presenter)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 */ getDisplayedAdapter()179 ObjectAdapter getDisplayedAdapter() { 180 return mAdapter; 181 } 182 } 183 184 private OnControlClickedListener mOnControlClickedListener; 185 private OnControlSelectedListener mOnControlSelectedListener; 186 private int mLayoutResourceId; 187 private static int sChildMarginDefault; 188 private static int sControlIconWidth; 189 190 /** 191 * Constructor for a ControlBarPresenter. 192 * 193 * @param layoutResourceId The resource id of the layout for this presenter. 194 */ ControlBarPresenter(int layoutResourceId)195 public ControlBarPresenter(int layoutResourceId) { 196 mLayoutResourceId = layoutResourceId; 197 } 198 199 /** 200 * Returns the layout resource id. 201 */ getLayoutResourceId()202 public int getLayoutResourceId() { 203 return mLayoutResourceId; 204 } 205 206 /** 207 * Sets the listener for control clicked events. 208 */ setOnControlClickedListener(OnControlClickedListener listener)209 public void setOnControlClickedListener(OnControlClickedListener listener) { 210 mOnControlClickedListener = listener; 211 } 212 213 /** 214 * Returns the listener for control clicked events. 215 */ getOnItemViewClickedListener()216 public OnControlClickedListener getOnItemViewClickedListener() { 217 return mOnControlClickedListener; 218 } 219 220 /** 221 * Sets the listener for control selection. 222 */ setOnControlSelectedListener(OnControlSelectedListener listener)223 public void setOnControlSelectedListener(OnControlSelectedListener listener) { 224 mOnControlSelectedListener = listener; 225 } 226 227 /** 228 * Returns the listener for control selection. 229 */ getOnItemControlListener()230 public OnControlSelectedListener getOnItemControlListener() { 231 return mOnControlSelectedListener; 232 } 233 setBackgroundColor(ViewHolder vh, int color)234 public void setBackgroundColor(ViewHolder vh, int color) { 235 vh.mControlsContainer.setBackgroundColor(color); 236 } 237 238 @Override onCreateViewHolder(ViewGroup parent)239 public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { 240 View v = LayoutInflater.from(parent.getContext()) 241 .inflate(getLayoutResourceId(), parent, false); 242 return new ViewHolder(v); 243 } 244 245 @Override onBindViewHolder(Presenter.ViewHolder holder, Object item)246 public void onBindViewHolder(Presenter.ViewHolder holder, Object item) { 247 ViewHolder vh = (ViewHolder) holder; 248 BoundData data = (BoundData) item; 249 if (vh.mAdapter != data.adapter) { 250 vh.mAdapter = data.adapter; 251 if (vh.mAdapter != null) { 252 vh.mAdapter.registerObserver(vh.mDataObserver); 253 } 254 } 255 vh.mPresenter = data.presenter; 256 vh.mData = data; 257 vh.showControls(vh.mPresenter); 258 } 259 260 @Override onUnbindViewHolder(Presenter.ViewHolder holder)261 public void onUnbindViewHolder(Presenter.ViewHolder holder) { 262 ViewHolder vh = (ViewHolder) holder; 263 if (vh.mAdapter != null) { 264 vh.mAdapter.unregisterObserver(vh.mDataObserver); 265 vh.mAdapter = null; 266 } 267 vh.mData = null; 268 } 269 getChildMarginDefault(Context context)270 int getChildMarginDefault(Context context) { 271 if (sChildMarginDefault == 0) { 272 sChildMarginDefault = context.getResources().getDimensionPixelSize( 273 R.dimen.lb_playback_controls_child_margin_default); 274 } 275 return sChildMarginDefault; 276 } 277 getControlIconWidth(Context context)278 int getControlIconWidth(Context context) { 279 if (sControlIconWidth == 0) { 280 sControlIconWidth = context.getResources().getDimensionPixelSize( 281 R.dimen.lb_control_icon_width); 282 } 283 return sControlIconWidth; 284 } 285 } 286