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.util.Log; 17 import android.view.View; 18 import android.view.ViewGroup; 19 20 import androidx.recyclerview.widget.RecyclerView; 21 22 import java.util.ArrayList; 23 import java.util.List; 24 25 /** 26 * Bridge from {@link Presenter} to {@link RecyclerView.Adapter}. Public to allow use by third 27 * party Presenters. 28 */ 29 public class ItemBridgeAdapter extends RecyclerView.Adapter implements FacetProviderAdapter { 30 static final String TAG = "ItemBridgeAdapter"; 31 static final boolean DEBUG = false; 32 33 /** 34 * Interface for listening to ViewHolder operations. 35 */ 36 public static class AdapterListener { onAddPresenter(Presenter presenter, int type)37 public void onAddPresenter(Presenter presenter, int type) { 38 } 39 onCreate(ViewHolder viewHolder)40 public void onCreate(ViewHolder viewHolder) { 41 } 42 onBind(ViewHolder viewHolder)43 public void onBind(ViewHolder viewHolder) { 44 } 45 onBind(ViewHolder viewHolder, List payloads)46 public void onBind(ViewHolder viewHolder, List payloads) { 47 onBind(viewHolder); 48 } 49 onUnbind(ViewHolder viewHolder)50 public void onUnbind(ViewHolder viewHolder) { 51 } 52 onAttachedToWindow(ViewHolder viewHolder)53 public void onAttachedToWindow(ViewHolder viewHolder) { 54 } 55 onDetachedFromWindow(ViewHolder viewHolder)56 public void onDetachedFromWindow(ViewHolder viewHolder) { 57 } 58 } 59 60 /** 61 * Interface for wrapping a view created by a Presenter into another view. 62 * The wrapper must be the immediate parent of the wrapped view. 63 */ 64 public static abstract class Wrapper { createWrapper(View root)65 public abstract View createWrapper(View root); 66 wrap(View wrapper, View wrapped)67 public abstract void wrap(View wrapper, View wrapped); 68 } 69 70 private ObjectAdapter mAdapter; 71 Wrapper mWrapper; 72 private PresenterSelector mPresenterSelector; 73 FocusHighlightHandler mFocusHighlight; 74 private AdapterListener mAdapterListener; 75 private ArrayList<Presenter> mPresenters = new ArrayList<Presenter>(); 76 77 static final class ChainingFocusChangeListener implements View.OnFocusChangeListener { 78 final View.OnFocusChangeListener mChainedListener; 79 boolean mHasWrapper; 80 FocusHighlightHandler mFocusHighlight; 81 ChainingFocusChangeListener(View.OnFocusChangeListener chainedListener, boolean hasWrapper, FocusHighlightHandler focusHighlight)82 ChainingFocusChangeListener(View.OnFocusChangeListener chainedListener, 83 boolean hasWrapper, FocusHighlightHandler focusHighlight) { 84 mChainedListener = chainedListener; 85 mHasWrapper = hasWrapper; 86 mFocusHighlight = focusHighlight; 87 } 88 update(boolean hasWrapper, FocusHighlightHandler focusHighlight)89 void update(boolean hasWrapper, FocusHighlightHandler focusHighlight) { 90 mHasWrapper = hasWrapper; 91 mFocusHighlight = focusHighlight; 92 } 93 94 @Override onFocusChange(View view, boolean hasFocus)95 public void onFocusChange(View view, boolean hasFocus) { 96 if (DEBUG) { 97 Log.v(TAG, "onFocusChange " + hasFocus + " " + view 98 + " mFocusHighlight" + mFocusHighlight); 99 } 100 if (mHasWrapper) { 101 view = (View) view.getParent(); 102 } 103 mFocusHighlight.onItemFocused(view, hasFocus); 104 if (mChainedListener != null) { 105 mChainedListener.onFocusChange(view, hasFocus); 106 } 107 } 108 } 109 110 /** 111 * ViewHolder for the ItemBridgeAdapter. 112 */ 113 public static class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider { 114 final Presenter mPresenter; 115 final Presenter.ViewHolder mHolder; 116 Object mItem; 117 Object mExtraObject; 118 119 /** 120 * Get {@link Presenter}. 121 */ getPresenter()122 public final Presenter getPresenter() { 123 return mPresenter; 124 } 125 126 /** 127 * Get {@link Presenter.ViewHolder}. 128 */ getViewHolder()129 public final Presenter.ViewHolder getViewHolder() { 130 return mHolder; 131 } 132 133 /** 134 * Get currently bound object. 135 */ getItem()136 public final Object getItem() { 137 return mItem; 138 } 139 140 /** 141 * Get extra object associated with the view. Developer can attach 142 * any customized UI object in addition to {@link Presenter.ViewHolder}. 143 * A typical use case is attaching an animator object. 144 */ getExtraObject()145 public final Object getExtraObject() { 146 return mExtraObject; 147 } 148 149 /** 150 * Set extra object associated with the view. Developer can attach 151 * any customized UI object in addition to {@link Presenter.ViewHolder}. 152 * A typical use case is attaching an animator object. 153 */ setExtraObject(Object object)154 public void setExtraObject(Object object) { 155 mExtraObject = object; 156 } 157 158 @Override getFacet(Class<?> facetClass)159 public Object getFacet(Class<?> facetClass) { 160 return mHolder.getFacet(facetClass); 161 } 162 ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder)163 ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder) { 164 super(view); 165 mPresenter = presenter; 166 mHolder = holder; 167 } 168 } 169 170 private ObjectAdapter.DataObserver mDataObserver = new ObjectAdapter.DataObserver() { 171 @Override 172 public void onChanged() { 173 ItemBridgeAdapter.this.notifyDataSetChanged(); 174 } 175 176 @Override 177 public void onItemRangeChanged(int positionStart, int itemCount) { 178 ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount); 179 } 180 181 @Override 182 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 183 ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount, payload); 184 } 185 186 @Override 187 public void onItemRangeInserted(int positionStart, int itemCount) { 188 ItemBridgeAdapter.this.notifyItemRangeInserted(positionStart, itemCount); 189 } 190 191 @Override 192 public void onItemRangeRemoved(int positionStart, int itemCount) { 193 ItemBridgeAdapter.this.notifyItemRangeRemoved(positionStart, itemCount); 194 } 195 196 @Override 197 public void onItemMoved(int fromPosition, int toPosition) { 198 ItemBridgeAdapter.this.notifyItemMoved(fromPosition, toPosition); 199 } 200 }; 201 ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector)202 public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) { 203 setAdapter(adapter); 204 mPresenterSelector = presenterSelector; 205 } 206 ItemBridgeAdapter(ObjectAdapter adapter)207 public ItemBridgeAdapter(ObjectAdapter adapter) { 208 this(adapter, null); 209 } 210 ItemBridgeAdapter()211 public ItemBridgeAdapter() { 212 } 213 214 /** 215 * Sets the {@link ObjectAdapter}. 216 */ setAdapter(ObjectAdapter adapter)217 public void setAdapter(ObjectAdapter adapter) { 218 if (adapter == mAdapter) { 219 return; 220 } 221 if (mAdapter != null) { 222 mAdapter.unregisterObserver(mDataObserver); 223 } 224 mAdapter = adapter; 225 if (mAdapter == null) { 226 notifyDataSetChanged(); 227 return; 228 } 229 230 mAdapter.registerObserver(mDataObserver); 231 if (hasStableIds() != mAdapter.hasStableIds()) { 232 setHasStableIds(mAdapter.hasStableIds()); 233 } 234 notifyDataSetChanged(); 235 } 236 237 /** 238 * Changes Presenter that creates and binds the view. 239 * 240 * @param presenterSelector Presenter that creates and binds the view. 241 */ setPresenter(PresenterSelector presenterSelector)242 public void setPresenter(PresenterSelector presenterSelector) { 243 mPresenterSelector = presenterSelector; 244 notifyDataSetChanged(); 245 } 246 247 /** 248 * Sets the {@link Wrapper}. 249 */ setWrapper(Wrapper wrapper)250 public void setWrapper(Wrapper wrapper) { 251 mWrapper = wrapper; 252 } 253 254 /** 255 * Returns the {@link Wrapper}. 256 */ getWrapper()257 public Wrapper getWrapper() { 258 return mWrapper; 259 } 260 setFocusHighlight(FocusHighlightHandler listener)261 void setFocusHighlight(FocusHighlightHandler listener) { 262 mFocusHighlight = listener; 263 if (DEBUG) Log.v(TAG, "setFocusHighlight " + mFocusHighlight); 264 } 265 266 /** 267 * Clears the adapter. 268 */ clear()269 public void clear() { 270 setAdapter(null); 271 } 272 273 /** 274 * Sets the presenter mapper array. 275 */ setPresenterMapper(ArrayList<Presenter> presenters)276 public void setPresenterMapper(ArrayList<Presenter> presenters) { 277 mPresenters = presenters; 278 } 279 280 /** 281 * Returns the presenter mapper array. 282 */ getPresenterMapper()283 public ArrayList<Presenter> getPresenterMapper() { 284 return mPresenters; 285 } 286 287 @Override getItemCount()288 public int getItemCount() { 289 return mAdapter != null ? mAdapter.size() : 0; 290 } 291 292 @Override getItemViewType(int position)293 public int getItemViewType(int position) { 294 PresenterSelector presenterSelector = mPresenterSelector != null 295 ? mPresenterSelector : mAdapter.getPresenterSelector(); 296 Object item = mAdapter.get(position); 297 Presenter presenter = presenterSelector.getPresenter(item); 298 int type = mPresenters.indexOf(presenter); 299 if (type < 0) { 300 mPresenters.add(presenter); 301 type = mPresenters.indexOf(presenter); 302 if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type); 303 onAddPresenter(presenter, type); 304 if (mAdapterListener != null) { 305 mAdapterListener.onAddPresenter(presenter, type); 306 } 307 } 308 return type; 309 } 310 311 /** 312 * Called when presenter is added to Adapter. 313 */ onAddPresenter(Presenter presenter, int type)314 protected void onAddPresenter(Presenter presenter, int type) { 315 } 316 317 /** 318 * Called when ViewHolder is created. 319 */ onCreate(ViewHolder viewHolder)320 protected void onCreate(ViewHolder viewHolder) { 321 } 322 323 /** 324 * Called when ViewHolder has been bound to data. 325 */ onBind(ViewHolder viewHolder)326 protected void onBind(ViewHolder viewHolder) { 327 } 328 329 /** 330 * Called when ViewHolder has been unbound from data. 331 */ onUnbind(ViewHolder viewHolder)332 protected void onUnbind(ViewHolder viewHolder) { 333 } 334 335 /** 336 * Called when ViewHolder has been attached to window. 337 */ onAttachedToWindow(ViewHolder viewHolder)338 protected void onAttachedToWindow(ViewHolder viewHolder) { 339 } 340 341 /** 342 * Called when ViewHolder has been detached from window. 343 */ onDetachedFromWindow(ViewHolder viewHolder)344 protected void onDetachedFromWindow(ViewHolder viewHolder) { 345 } 346 347 /** 348 * {@link View.OnFocusChangeListener} that assigned in 349 * {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change 350 * {@link View.OnFocusChangeListener} after that. 351 */ 352 @Override onCreateViewHolder(ViewGroup parent, int viewType)353 public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 354 if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType); 355 Presenter presenter = mPresenters.get(viewType); 356 Presenter.ViewHolder presenterVh; 357 View view; 358 if (mWrapper != null) { 359 view = mWrapper.createWrapper(parent); 360 presenterVh = presenter.onCreateViewHolder(parent); 361 mWrapper.wrap(view, presenterVh.view); 362 } else { 363 presenterVh = presenter.onCreateViewHolder(parent); 364 view = presenterVh.view; 365 } 366 ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh); 367 onCreate(viewHolder); 368 if (mAdapterListener != null) { 369 mAdapterListener.onCreate(viewHolder); 370 } 371 View presenterView = viewHolder.mHolder.view; 372 View.OnFocusChangeListener currentListener = presenterView.getOnFocusChangeListener(); 373 if (mFocusHighlight != null) { 374 // update or create ChainingFocusChangeListener 375 if (currentListener instanceof ChainingFocusChangeListener) { 376 ((ChainingFocusChangeListener) currentListener).update( 377 /* hasWrapper= */ mWrapper != null, mFocusHighlight); 378 } else { 379 presenterView.setOnFocusChangeListener(new ChainingFocusChangeListener( 380 currentListener, /* hasWrapper= */ mWrapper != null, mFocusHighlight)); 381 } 382 mFocusHighlight.onInitializeView(view); 383 } else { 384 // restore chained listener 385 if (currentListener instanceof ChainingFocusChangeListener) { 386 presenterView.setOnFocusChangeListener( 387 ((ChainingFocusChangeListener) currentListener).mChainedListener); 388 } 389 } 390 return viewHolder; 391 } 392 393 /** 394 * Sets the AdapterListener. 395 */ setAdapterListener(AdapterListener listener)396 public void setAdapterListener(AdapterListener listener) { 397 mAdapterListener = listener; 398 } 399 400 @Override onBindViewHolder(RecyclerView.ViewHolder holder, int position)401 public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 402 if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position); 403 ViewHolder viewHolder = (ViewHolder) holder; 404 viewHolder.mItem = mAdapter.get(position); 405 406 viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem); 407 408 onBind(viewHolder); 409 if (mAdapterListener != null) { 410 mAdapterListener.onBind(viewHolder); 411 } 412 } 413 414 @Override 415 @SuppressWarnings("unchecked") onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads)416 public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position, 417 List payloads) { 418 if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position); 419 ViewHolder viewHolder = (ViewHolder) holder; 420 viewHolder.mItem = mAdapter.get(position); 421 422 viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem, payloads); 423 424 onBind(viewHolder); 425 if (mAdapterListener != null) { 426 mAdapterListener.onBind(viewHolder, payloads); 427 } 428 } 429 430 @Override onViewRecycled(RecyclerView.ViewHolder holder)431 public final void onViewRecycled(RecyclerView.ViewHolder holder) { 432 ViewHolder viewHolder = (ViewHolder) holder; 433 viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder); 434 onUnbind(viewHolder); 435 if (mAdapterListener != null) { 436 mAdapterListener.onUnbind(viewHolder); 437 } 438 viewHolder.mItem = null; 439 } 440 441 @Override onFailedToRecycleView(RecyclerView.ViewHolder holder)442 public final boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { 443 onViewRecycled(holder); 444 return false; 445 } 446 447 @Override onViewAttachedToWindow(RecyclerView.ViewHolder holder)448 public final void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { 449 ViewHolder viewHolder = (ViewHolder) holder; 450 onAttachedToWindow(viewHolder); 451 if (mAdapterListener != null) { 452 mAdapterListener.onAttachedToWindow(viewHolder); 453 } 454 viewHolder.mPresenter.onViewAttachedToWindow(viewHolder.mHolder); 455 } 456 457 @Override onViewDetachedFromWindow(RecyclerView.ViewHolder holder)458 public final void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) { 459 ViewHolder viewHolder = (ViewHolder) holder; 460 viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder); 461 onDetachedFromWindow(viewHolder); 462 if (mAdapterListener != null) { 463 mAdapterListener.onDetachedFromWindow(viewHolder); 464 } 465 } 466 467 @Override getItemId(int position)468 public long getItemId(int position) { 469 return mAdapter.getId(position); 470 } 471 472 @Override getFacetProvider(int type)473 public FacetProvider getFacetProvider(int type) { 474 return mPresenters.get(type); 475 } 476 } 477