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; 22import 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 */ 31class 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 { 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 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 mControlBar = (ControlBar) rootView.findViewById(R.id.control_bar); 81 if (mControlBar == null) { 82 throw new IllegalStateException("Couldn't find control_bar"); 83 } 84 mControlBar.setOnChildFocusedListener(new ControlBar.OnChildFocusedListener() { 85 @Override 86 public void onChildFocusedListener(View child, View focused) { 87 if (mOnControlSelectedListener == null) { 88 return; 89 } 90 for (int position = 0; position < mViewHolders.size(); position++) { 91 if (mViewHolders.get(position).view == child) { 92 mOnControlSelectedListener.onControlSelected( 93 mViewHolders.get(position), 94 getDisplayedAdapter().get(position), mData); 95 break; 96 } 97 } 98 } 99 }); 100 mDataObserver = new ObjectAdapter.DataObserver() { 101 @Override 102 public void onChanged() { 103 if (mAdapter == getDisplayedAdapter()) { 104 showControls(mPresenter); 105 } 106 } 107 @Override 108 public void onItemRangeChanged(int positionStart, int itemCount) { 109 if (mAdapter == getDisplayedAdapter()) { 110 for (int i = 0; i < itemCount; i++) { 111 bindControlToAction(positionStart + i, mPresenter); 112 } 113 } 114 } 115 }; 116 } 117 118 int getChildMarginFromCenter(Context context, int numControls) { 119 // Includes margin between icons plus two times half the icon width. 120 return getChildMarginDefault(context) + getControlIconWidth(context); 121 } 122 123 void showControls(Presenter presenter) { 124 ObjectAdapter adapter = getDisplayedAdapter(); 125 int adapterSize = adapter == null ? 0 : adapter.size(); 126 // Shrink the number of attached views 127 View focusedView = mControlBar.getFocusedChild(); 128 if (focusedView != null && adapterSize > 0 && 129 mControlBar.indexOfChild(focusedView) >= adapterSize) { 130 mControlBar.getChildAt(adapter.size() - 1).requestFocus(); 131 } 132 for (int i = mControlBar.getChildCount() - 1; i >= adapterSize; i--) { 133 mControlBar.removeViewAt(i); 134 } 135 for (int position = 0; position < adapterSize && position < MAX_CONTROLS; 136 position++) { 137 bindControlToAction(position, adapter, presenter); 138 } 139 mControlBar.setChildMarginFromCenter( 140 getChildMarginFromCenter(mControlBar.getContext(), adapterSize)); 141 } 142 143 void bindControlToAction(int position, Presenter presenter) { 144 bindControlToAction(position, getDisplayedAdapter(), presenter); 145 } 146 147 private void bindControlToAction(final int position, 148 ObjectAdapter adapter, Presenter presenter) { 149 Presenter.ViewHolder vh = mViewHolders.get(position); 150 Object item = adapter.get(position); 151 if (vh == null) { 152 vh = presenter.onCreateViewHolder(mControlBar); 153 mViewHolders.put(position, vh); 154 155 final Presenter.ViewHolder itemViewHolder = vh; 156 presenter.setOnClickListener(vh, new View.OnClickListener() { 157 @Override 158 public void onClick(View v) { 159 Object item = getDisplayedAdapter().get(position); 160 if (mOnControlClickedListener != null) { 161 mOnControlClickedListener.onControlClicked(itemViewHolder, item, 162 mData); 163 } 164 } 165 }); 166 } 167 if (vh.view.getParent() == null) { 168 mControlBar.addView(vh.view); 169 } 170 presenter.onBindViewHolder(vh, item); 171 } 172 173 /** 174 * Returns the adapter currently bound to the displayed controls. 175 * May be overridden in a subclass. 176 */ 177 ObjectAdapter getDisplayedAdapter() { 178 return mAdapter; 179 } 180 } 181 182 private OnControlClickedListener mOnControlClickedListener; 183 private OnControlSelectedListener mOnControlSelectedListener; 184 private int mLayoutResourceId; 185 private static int sChildMarginDefault; 186 private static int sControlIconWidth; 187 188 /** 189 * Constructor for a ControlBarPresenter. 190 * 191 * @param layoutResourceId The resource id of the layout for this presenter. 192 */ 193 public ControlBarPresenter(int layoutResourceId) { 194 mLayoutResourceId = layoutResourceId; 195 } 196 197 /** 198 * Returns the layout resource id. 199 */ 200 public int getLayoutResourceId() { 201 return mLayoutResourceId; 202 } 203 204 /** 205 * Sets the listener for control clicked events. 206 */ 207 public void setOnControlClickedListener(OnControlClickedListener listener) { 208 mOnControlClickedListener = listener; 209 } 210 211 /** 212 * Returns the listener for control clicked events. 213 */ 214 public OnControlClickedListener getOnItemViewClickedListener() { 215 return mOnControlClickedListener; 216 } 217 218 /** 219 * Sets the listener for control selection. 220 */ 221 public void setOnControlSelectedListener(OnControlSelectedListener listener) { 222 mOnControlSelectedListener = listener; 223 } 224 225 /** 226 * Returns the listener for control selection. 227 */ 228 public OnControlSelectedListener getOnItemControlListener() { 229 return mOnControlSelectedListener; 230 } 231 232 @Override 233 public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { 234 View v = LayoutInflater.from(parent.getContext()) 235 .inflate(getLayoutResourceId(), parent, false); 236 return new ViewHolder(v); 237 } 238 239 @Override 240 public void onBindViewHolder(Presenter.ViewHolder holder, Object item) { 241 ViewHolder vh = (ViewHolder) holder; 242 BoundData data = (BoundData) item; 243 if (vh.mAdapter != data.adapter) { 244 vh.mAdapter = data.adapter; 245 if (vh.mAdapter != null) { 246 vh.mAdapter.registerObserver(vh.mDataObserver); 247 } 248 } 249 vh.mPresenter = data.presenter; 250 vh.mData = data; 251 vh.showControls(vh.mPresenter); 252 } 253 254 @Override 255 public void onUnbindViewHolder(Presenter.ViewHolder holder) { 256 ViewHolder vh = (ViewHolder) holder; 257 if (vh.mAdapter != null) { 258 vh.mAdapter.unregisterObserver(vh.mDataObserver); 259 vh.mAdapter = null; 260 } 261 vh.mData = null; 262 } 263 264 int getChildMarginDefault(Context context) { 265 if (sChildMarginDefault == 0) { 266 sChildMarginDefault = context.getResources().getDimensionPixelSize( 267 R.dimen.lb_playback_controls_child_margin_default); 268 } 269 return sChildMarginDefault; 270 } 271 272 int getControlIconWidth(Context context) { 273 if (sControlIconWidth == 0) { 274 sControlIconWidth = context.getResources().getDimensionPixelSize( 275 R.dimen.lb_control_icon_width); 276 } 277 return sControlIconWidth; 278 } 279} 280