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