1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.apps.common.widget; 18 19 import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.car.drivingstate.CarUxRestrictions; 22 import android.content.Context; 23 import android.content.res.TypedArray; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.util.AttributeSet; 27 import android.util.Log; 28 import android.util.SparseArray; 29 import android.view.View; 30 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 31 32 import androidx.annotation.IntDef; 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 import androidx.annotation.VisibleForTesting; 36 import androidx.recyclerview.widget.LinearLayoutManager; 37 import androidx.recyclerview.widget.RecyclerView; 38 39 import com.android.car.apps.common.CarUxRestrictionsUtil; 40 import com.android.car.apps.common.R; 41 import com.android.car.apps.common.util.ScrollBarUI; 42 43 import java.lang.annotation.Retention; 44 45 /** 46 * View that extends a {@link RecyclerView} and creates a nested {@code RecyclerView} with an option 47 * to render a custom scroll bar that has page up and down arrows. Interaction with this view is 48 * similar to a {@code RecyclerView} as it takes the same adapter and the layout manager. 49 */ 50 public final class PagedRecyclerView extends RecyclerView { 51 52 private static final boolean DEBUG = false; 53 private static final String TAG = "PagedRecyclerView"; 54 55 private final CarUxRestrictionsUtil mCarUxRestrictionsUtil; 56 private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener; 57 58 private boolean mScrollBarEnabled; 59 private int mScrollBarContainerWidth; 60 private @ScrollBarPosition int mScrollBarPosition; 61 private boolean mScrollBarAboveRecyclerView; 62 private String mScrollBarClass; 63 private int mScrollBarPaddingStart; 64 private int mScrollBarPaddingEnd; 65 private boolean mFullyInitialized; 66 67 @Gutter 68 private int mGutter; 69 private int mGutterSize; 70 private RecyclerView mNestedRecyclerView; 71 private Adapter mAdapter; 72 private ScrollBarUI mScrollBarUI; 73 74 /** 75 * The possible values for @{link #setGutter}. The default value is actually 76 * {@link PagedRecyclerView.Gutter#BOTH}. 77 */ 78 @IntDef({ 79 Gutter.NONE, 80 Gutter.START, 81 Gutter.END, 82 Gutter.BOTH, 83 }) 84 85 @Retention(SOURCE) 86 public @interface Gutter { 87 /** 88 * No gutter on either side of the list items. The items will span the full width of the 89 * RecyclerView 90 */ 91 int NONE = 0; 92 93 /** 94 * Include a gutter only on the start side (that is, the same side as the scroll bar). 95 */ 96 int START = 1; 97 98 /** 99 * Include a gutter only on the end side (that is, the opposite side of the scroll bar). 100 */ 101 int END = 2; 102 103 /** 104 * Include a gutter on both sides of the list items. This is the default behaviour. 105 */ 106 int BOTH = 3; 107 } 108 109 /** 110 * The possible values for setScrollbarPosition. The default value is actually 111 * {@link PagedRecyclerView.ScrollBarPosition#START}. 112 */ 113 @IntDef({ 114 ScrollBarPosition.START, 115 ScrollBarPosition.END, 116 }) 117 118 @Retention(SOURCE) 119 public @interface ScrollBarPosition { 120 /** 121 * Position the scrollbar to the left of the screen. This is default. 122 */ 123 int START = 0; 124 125 /** 126 * Position scrollbar to the right of the screen. 127 */ 128 int END = 2; 129 } 130 131 /** 132 * Interface for a {@link RecyclerView.Adapter} to cap the number of items. 133 * 134 * <p>NOTE: it is still up to the adapter to use maxItems in {@link 135 * RecyclerView.Adapter#getItemCount()}. 136 * 137 * <p>the recommended way would be with: 138 * 139 * <pre>{@code 140 * {@literal@}Override 141 * public int getItemCount() { 142 * return Math.min(super.getItemCount(), mMaxItems); 143 * } 144 * }</pre> 145 */ 146 public interface ItemCap { 147 /** 148 * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit. 149 */ 150 int UNLIMITED = -1; 151 152 /** 153 * Sets the maximum number of items available in the adapter. A value less than '0' means 154 * the list should not be capped. 155 */ setMaxItems(int maxItems)156 void setMaxItems(int maxItems); 157 } 158 159 /** 160 * Custom layout manager for the outer recyclerview. Since paddings should be applied by the 161 * inner recycler view within its bounds, this layout manager should always have 0 padding. 162 */ 163 private class PagedRecyclerViewLayoutManager extends LinearLayoutManager { PagedRecyclerViewLayoutManager(Context context)164 PagedRecyclerViewLayoutManager(Context context) { 165 super(context); 166 } 167 168 @Override getPaddingTop()169 public int getPaddingTop() { 170 return 0; 171 } 172 173 @Override getPaddingBottom()174 public int getPaddingBottom() { 175 return 0; 176 } 177 178 @Override getPaddingStart()179 public int getPaddingStart() { 180 return 0; 181 } 182 183 @Override getPaddingEnd()184 public int getPaddingEnd() { 185 return 0; 186 } 187 188 @Override canScrollHorizontally()189 public boolean canScrollHorizontally() { 190 return false; 191 } 192 193 @Override canScrollVertically()194 public boolean canScrollVertically() { 195 return false; 196 } 197 } 198 PagedRecyclerView(@onNull Context context)199 public PagedRecyclerView(@NonNull Context context) { 200 this(context, null, 0); 201 } 202 PagedRecyclerView(@onNull Context context, @Nullable AttributeSet attrs)203 public PagedRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { 204 this(context, attrs, 0); 205 } 206 PagedRecyclerView(@onNull Context context, @Nullable AttributeSet attrs, int defStyle)207 public PagedRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { 208 super(context, attrs, defStyle); 209 210 mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context); 211 mListener = this::updateCarUxRestrictions; 212 213 init(context, attrs, defStyle); 214 } 215 init(Context context, AttributeSet attrs, int defStyleAttr)216 private void init(Context context, AttributeSet attrs, int defStyleAttr) { 217 TypedArray a = context.obtainStyledAttributes( 218 attrs, R.styleable.PagedRecyclerView, defStyleAttr, 219 R.style.PagedRecyclerView); 220 221 mScrollBarEnabled = a.getBoolean(R.styleable.PagedRecyclerView_scrollBarEnabled, 222 /* defValue= */true); 223 mFullyInitialized = false; 224 225 if (!mScrollBarEnabled) { 226 a.recycle(); 227 mFullyInitialized = true; 228 return; 229 } 230 231 mNestedRecyclerView = new RecyclerView(context, attrs, 232 R.style.PagedRecyclerView_NestedRecyclerView); 233 234 super.setLayoutManager(new PagedRecyclerViewLayoutManager(context)); 235 super.setAdapter(new PagedRecyclerViewAdapter()); 236 super.setNestedScrollingEnabled(false); 237 super.setClipToPadding(false); 238 239 // Gutter 240 mGutter = a.getInt(R.styleable.PagedRecyclerView_gutter, Gutter.BOTH); 241 mGutterSize = getResources().getDimensionPixelSize(R.dimen.car_scroll_bar_margin); 242 243 int carMargin = getResources().getDimensionPixelSize(R.dimen.car_scroll_bar_margin); 244 mScrollBarContainerWidth = a.getDimensionPixelSize( 245 R.styleable.PagedRecyclerView_scrollBarContainerWidth, carMargin); 246 247 mScrollBarPosition = a.getInt(R.styleable.PagedRecyclerView_scrollBarPosition, 248 ScrollBarPosition.START); 249 250 mScrollBarAboveRecyclerView = a.getBoolean( 251 R.styleable.PagedRecyclerView_scrollBarAboveRecyclerView, /* defValue= */true); 252 253 mScrollBarClass = a.getString(R.styleable.PagedRecyclerView_scrollBarCustomClass); 254 a.recycle(); 255 256 // Apply inner RV layout changes after the layout has been calculated for this view. 257 this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 258 @Override 259 public void onGlobalLayout() { 260 // View holder layout is still pending. 261 if (PagedRecyclerView.this.findViewHolderForAdapterPosition(0) == null) return; 262 263 PagedRecyclerView.this.getViewTreeObserver().removeOnGlobalLayoutListener(this); 264 initNestedRecyclerView(); 265 setNestedViewLayout(); 266 267 createScrollBarFromConfig(); 268 269 mNestedRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener( 270 new OnGlobalLayoutListener() { 271 @Override 272 public void onGlobalLayout() { 273 mNestedRecyclerView.getViewTreeObserver() 274 .removeOnGlobalLayoutListener(this); 275 mFullyInitialized = true; 276 } 277 }); 278 } 279 }); 280 } 281 282 /** 283 * Returns {@code true} if the {@PagedRecyclerView} is fully drawn. Using a global layout 284 * listener may not necessarily signify that this view is fully drawn (i.e. when the 285 * scrollbar is enabled). This is because the inner views (scrollbar and inner recycler view) 286 * are drawn after the outer views are finished. 287 */ fullyInitialized()288 public boolean fullyInitialized() { 289 return mFullyInitialized; 290 } 291 292 @Override onAttachedToWindow()293 protected void onAttachedToWindow() { 294 super.onAttachedToWindow(); 295 mCarUxRestrictionsUtil.register(mListener); 296 } 297 298 @Override onDetachedFromWindow()299 protected void onDetachedFromWindow() { 300 super.onDetachedFromWindow(); 301 mCarUxRestrictionsUtil.unregister(mListener); 302 } 303 updateCarUxRestrictions(CarUxRestrictions carUxRestrictions)304 private void updateCarUxRestrictions(CarUxRestrictions carUxRestrictions) { 305 // If the adapter does not implement ItemCap, then the max items on it cannot be updated. 306 if (!(mAdapter instanceof ItemCap)) { 307 return; 308 } 309 310 int maxItems = ItemCap.UNLIMITED; 311 if ((carUxRestrictions.getActiveRestrictions() 312 & CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT) != 0) { 313 maxItems = carUxRestrictions.getMaxCumulativeContentItems(); 314 } 315 316 int originalCount = mAdapter.getItemCount(); 317 ((ItemCap) mAdapter).setMaxItems(maxItems); 318 int newCount = mAdapter.getItemCount(); 319 320 if (newCount == originalCount) { 321 return; 322 } 323 324 if (newCount < originalCount) { 325 mAdapter.notifyItemRangeRemoved( 326 newCount, originalCount - newCount); 327 } else { 328 mAdapter.notifyItemRangeInserted( 329 originalCount, newCount - originalCount); 330 } 331 } 332 333 @Override setClipToPadding(boolean clipToPadding)334 public void setClipToPadding(boolean clipToPadding) { 335 if (mScrollBarEnabled) { 336 mNestedRecyclerView.setClipToPadding(clipToPadding); 337 } else { 338 super.setClipToPadding(clipToPadding); 339 } 340 } 341 342 @Override setAdapter(@ullable Adapter adapter)343 public void setAdapter(@Nullable Adapter adapter) { 344 mAdapter = adapter; 345 if (mScrollBarEnabled) { 346 mNestedRecyclerView.setAdapter(adapter); 347 } else { 348 super.setAdapter(adapter); 349 } 350 } 351 352 @Nullable 353 @Override getAdapter()354 public Adapter getAdapter() { 355 if (mScrollBarEnabled) { 356 return mNestedRecyclerView.getAdapter(); 357 } 358 return super.getAdapter(); 359 } 360 361 @Override setLayoutManager(@ullable LayoutManager layout)362 public void setLayoutManager(@Nullable LayoutManager layout) { 363 if (mScrollBarEnabled) { 364 mNestedRecyclerView.setLayoutManager(layout); 365 } else { 366 super.setLayoutManager(layout); 367 } 368 } 369 370 /** 371 * Returns the {@link LayoutManager} for the {@link RecyclerView} displaying the content. 372 * 373 * <p>In cases where the scroll bar is visible and the nested {@link RecyclerView} is 374 * displaying content, {@link #getLayoutManager()} cannot be used because it returns the 375 * {@link LayoutManager} of the outer {@link RecyclerView}. {@link #getLayoutManager()} could 376 * not be overridden to return the effective manager due to interference with accessibility 377 * node tree traversal. 378 */ 379 @Nullable getEffectiveLayoutManager()380 public LayoutManager getEffectiveLayoutManager() { 381 if (mScrollBarEnabled) { 382 return mNestedRecyclerView.getLayoutManager(); 383 } 384 return super.getLayoutManager(); 385 } 386 387 @Override setOnScrollChangeListener(OnScrollChangeListener l)388 public void setOnScrollChangeListener(OnScrollChangeListener l) { 389 if (mScrollBarEnabled) { 390 mNestedRecyclerView.setOnScrollChangeListener(l); 391 } else { 392 super.setOnScrollChangeListener(l); 393 } 394 } 395 396 @Override setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled)397 public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) { 398 if (mScrollBarEnabled) { 399 mNestedRecyclerView.setVerticalFadingEdgeEnabled(verticalFadingEdgeEnabled); 400 } else { 401 super.setVerticalFadingEdgeEnabled(verticalFadingEdgeEnabled); 402 } 403 } 404 405 @Override setFadingEdgeLength(int length)406 public void setFadingEdgeLength(int length) { 407 if (mScrollBarEnabled) { 408 mNestedRecyclerView.setFadingEdgeLength(length); 409 } else { 410 super.setFadingEdgeLength(length); 411 } 412 } 413 414 @Override addItemDecoration(@onNull ItemDecoration decor, int index)415 public void addItemDecoration(@NonNull ItemDecoration decor, int index) { 416 if (mScrollBarEnabled) { 417 mNestedRecyclerView.addItemDecoration(decor, index); 418 } else { 419 super.addItemDecoration(decor, index); 420 } 421 } 422 423 @Override addItemDecoration(@onNull ItemDecoration decor)424 public void addItemDecoration(@NonNull ItemDecoration decor) { 425 if (mScrollBarEnabled) { 426 mNestedRecyclerView.addItemDecoration(decor); 427 } else { 428 super.addItemDecoration(decor); 429 } 430 } 431 432 @Override setItemAnimator(@ullable ItemAnimator animator)433 public void setItemAnimator(@Nullable ItemAnimator animator) { 434 if (mScrollBarEnabled) { 435 mNestedRecyclerView.setItemAnimator(animator); 436 } else { 437 super.setItemAnimator(animator); 438 } 439 } 440 441 @Override setPadding(int left, int top, int right, int bottom)442 public void setPadding(int left, int top, int right, int bottom) { 443 if (mScrollBarEnabled) { 444 mNestedRecyclerView.setPadding(left, top, right, bottom); 445 if (mScrollBarUI != null) mScrollBarUI.requestLayout(); 446 } else { 447 super.setPadding(left, top, right, bottom); 448 } 449 } 450 451 @Override setPaddingRelative(int start, int top, int end, int bottom)452 public void setPaddingRelative(int start, int top, int end, int bottom) { 453 if (mScrollBarEnabled) { 454 mNestedRecyclerView.setPaddingRelative(start, top, end, bottom); 455 if (mScrollBarUI != null) mScrollBarUI.requestLayout(); 456 } else { 457 super.setPaddingRelative(start, top, end, bottom); 458 } 459 } 460 461 @Override findViewHolderForLayoutPosition(int position)462 public ViewHolder findViewHolderForLayoutPosition(int position) { 463 if (mScrollBarEnabled) { 464 return mNestedRecyclerView.findViewHolderForLayoutPosition(position); 465 } else { 466 return super.findViewHolderForLayoutPosition(position); 467 } 468 } 469 470 @Override findContainingViewHolder(View view)471 public ViewHolder findContainingViewHolder(View view) { 472 if (mScrollBarEnabled) { 473 return mNestedRecyclerView.findContainingViewHolder(view); 474 } else { 475 return super.findContainingViewHolder(view); 476 } 477 } 478 479 @Override 480 @Nullable findChildViewUnder(float x, float y)481 public View findChildViewUnder(float x, float y) { 482 if (mScrollBarEnabled) { 483 return mNestedRecyclerView.findChildViewUnder(x, y); 484 } else { 485 return super.findChildViewUnder(x, y); 486 } 487 } 488 489 @Override addOnScrollListener(@onNull OnScrollListener listener)490 public void addOnScrollListener(@NonNull OnScrollListener listener) { 491 if (mScrollBarEnabled) { 492 mNestedRecyclerView.addOnScrollListener(listener); 493 } else { 494 super.addOnScrollListener(listener); 495 } 496 } 497 498 @Override removeOnScrollListener(@onNull OnScrollListener listener)499 public void removeOnScrollListener(@NonNull OnScrollListener listener) { 500 if (mScrollBarEnabled) { 501 mNestedRecyclerView.removeOnScrollListener(listener); 502 } else { 503 super.removeOnScrollListener(listener); 504 } 505 } 506 507 /** 508 * Calls {@link #layout(int, int, int, int)} for both this RecyclerView and the nested one. 509 */ 510 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) layoutBothForTesting(int l, int t, int r, int b)511 public void layoutBothForTesting(int l, int t, int r, int b) { 512 super.layout(l, t, r, b); 513 if (mScrollBarEnabled) { 514 mNestedRecyclerView.layout(l, t, r, b); 515 } 516 } 517 518 @Override getPaddingStart()519 public int getPaddingStart() { 520 return mScrollBarEnabled ? mNestedRecyclerView.getPaddingStart() : super.getPaddingStart(); 521 } 522 523 @Override getPaddingEnd()524 public int getPaddingEnd() { 525 return mScrollBarEnabled ? mNestedRecyclerView.getPaddingEnd() : super.getPaddingEnd(); 526 } 527 528 @Override getPaddingTop()529 public int getPaddingTop() { 530 return mScrollBarEnabled ? mNestedRecyclerView.getPaddingTop() : super.getPaddingTop(); 531 } 532 533 @Override getPaddingBottom()534 public int getPaddingBottom() { 535 return mScrollBarEnabled ? mNestedRecyclerView.getPaddingBottom() 536 : super.getPaddingBottom(); 537 } 538 539 @Override setVisibility(int visibility)540 public void setVisibility(int visibility) { 541 super.setVisibility(visibility); 542 if (mScrollBarEnabled) { 543 mNestedRecyclerView.setVisibility(visibility); 544 } 545 } 546 initNestedRecyclerView()547 private void initNestedRecyclerView() { 548 PagedRecyclerViewAdapter.NestedRowViewHolder vh = 549 (PagedRecyclerViewAdapter.NestedRowViewHolder) 550 this.findViewHolderForAdapterPosition(0); 551 if (vh == null) { 552 throw new Error("Outer RecyclerView failed to initialize."); 553 } 554 555 vh.mFrameLayout.addView(mNestedRecyclerView); 556 } 557 createScrollBarFromConfig()558 private void createScrollBarFromConfig() { 559 if (DEBUG) Log.d(TAG, "createScrollBarFromConfig"); 560 final String clsName = mScrollBarClass == null 561 ? getContext().getString(R.string.config_scrollBarComponent) : mScrollBarClass; 562 if (clsName == null || clsName.length() == 0) { 563 throw andLog("No scroll bar component configured", null); 564 } 565 566 Class<?> cls; 567 try { 568 cls = getContext().getClassLoader().loadClass(clsName); 569 } catch (Throwable t) { 570 throw andLog("Error loading scroll bar component: " + clsName, t); 571 } 572 try { 573 mScrollBarUI = (ScrollBarUI) cls.newInstance(); 574 } catch (Throwable t) { 575 throw andLog("Error creating scroll bar component: " + clsName, t); 576 } 577 578 mScrollBarUI.initialize(getContext(), mNestedRecyclerView, mScrollBarContainerWidth, 579 mScrollBarPosition, mScrollBarAboveRecyclerView); 580 581 mScrollBarUI.setPadding(mScrollBarPaddingStart, mScrollBarPaddingEnd); 582 583 if (DEBUG) Log.d(TAG, "started " + mScrollBarUI.getClass().getSimpleName()); 584 } 585 586 /** 587 * Sets the scrollbar's padding start (top) and end (bottom). 588 * This padding is applied in addition to the padding of the inner RecyclerView. 589 */ setScrollBarPadding(int paddingStart, int paddingEnd)590 public void setScrollBarPadding(int paddingStart, int paddingEnd) { 591 if (mScrollBarEnabled) { 592 mScrollBarPaddingStart = paddingStart; 593 mScrollBarPaddingEnd = paddingEnd; 594 595 if (mScrollBarUI != null) { 596 mScrollBarUI.setPadding(paddingStart, paddingEnd); 597 } 598 } 599 } 600 601 /** 602 * Set the nested view's layout to the specified value. 603 * 604 * <p>The gutter is the space to the start/end of the list view items and will be equal in size 605 * to the scroll bars. By default, there is a gutter to both the left and right of the list 606 * view items, to account for the scroll bar. 607 */ setNestedViewLayout()608 private void setNestedViewLayout() { 609 int startMargin = 0; 610 int endMargin = 0; 611 if ((mGutter & Gutter.START) != 0) { 612 startMargin = mGutterSize; 613 } 614 if ((mGutter & Gutter.END) != 0) { 615 endMargin = mGutterSize; 616 } 617 618 MarginLayoutParams layoutParams = 619 (MarginLayoutParams) mNestedRecyclerView.getLayoutParams(); 620 621 layoutParams.setMarginStart(startMargin); 622 layoutParams.setMarginEnd(endMargin); 623 624 layoutParams.height = LayoutParams.MATCH_PARENT; 625 layoutParams.width = super.getLayoutManager().getWidth() - startMargin - endMargin; 626 // requestLayout() isn't sufficient because we also need to resolveLayoutParams(). 627 mNestedRecyclerView.setLayoutParams(layoutParams); 628 629 // If there's a gutter, set ClipToPadding to false so that CardView's shadow will still 630 // appear outside of the padding. 631 mNestedRecyclerView.setClipToPadding(startMargin == 0 && endMargin == 0); 632 } 633 andLog(String msg, Throwable t)634 private RuntimeException andLog(String msg, Throwable t) { 635 Log.e(TAG, msg, t); 636 throw new RuntimeException(msg, t); 637 } 638 639 @Override onSaveInstanceState()640 public Parcelable onSaveInstanceState() { 641 Parcelable superState = super.onSaveInstanceState(); 642 SavedState ss = new SavedState(superState, getContext()); 643 if (mScrollBarEnabled) { 644 mNestedRecyclerView.saveHierarchyState(ss.mNestedRecyclerViewState); 645 } 646 return ss; 647 } 648 649 @Override onRestoreInstanceState(Parcelable state)650 public void onRestoreInstanceState(Parcelable state) { 651 if (!(state instanceof SavedState)) { 652 Log.w(TAG, "onRestoreInstanceState called with an unsupported state"); 653 super.onRestoreInstanceState(state); 654 } else { 655 SavedState ss = (SavedState) state; 656 super.onRestoreInstanceState(ss.getSuperState()); 657 if (mScrollBarEnabled) { 658 mNestedRecyclerView.restoreHierarchyState(ss.mNestedRecyclerViewState); 659 } 660 } 661 } 662 663 static class SavedState extends BaseSavedState { 664 SparseArray mNestedRecyclerViewState; 665 Context mContext; 666 SavedState(Parcelable superState, Context c)667 SavedState(Parcelable superState, Context c) { 668 super(superState); 669 mContext = c; 670 mNestedRecyclerViewState = new SparseArray(); 671 } 672 SavedState(Parcel source, ClassLoader loader)673 private SavedState(Parcel source, ClassLoader loader) { 674 super(source, loader); 675 mNestedRecyclerViewState = source.readSparseArray(loader); 676 } 677 678 @Override writeToParcel(Parcel out, int flags)679 public void writeToParcel(Parcel out, int flags) { 680 super.writeToParcel(out, flags); 681 out.writeSparseArray(mNestedRecyclerViewState); 682 } 683 684 public static final Parcelable.Creator<SavedState> CREATOR = 685 new Parcelable.Creator<SavedState>() { 686 public SavedState createFromParcel(Parcel in) { 687 return new SavedState(in, getClass().getClassLoader()); 688 } 689 690 public SavedState[] newArray(int size) { 691 return new SavedState[size]; 692 } 693 }; 694 } 695 } 696