PreferenceFragment.java revision 0dac8d82e2a249d7c9c42ab259389e11cac15400
1/* 2 * Copyright (C) 2015 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 17package android.support.v14.preference; 18 19import android.app.DialogFragment; 20import android.app.Fragment; 21import android.content.Context; 22import android.content.res.TypedArray; 23import android.graphics.Canvas; 24import android.graphics.Rect; 25import android.graphics.drawable.Drawable; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.Message; 29import android.support.annotation.Nullable; 30import android.support.annotation.XmlRes; 31import android.support.v4.content.res.TypedArrayUtils; 32import android.support.v4.view.ViewCompat; 33import android.support.v7.preference.AndroidResources; 34import android.support.v7.preference.DialogPreference; 35import android.support.v7.preference.EditTextPreference; 36import android.support.v7.preference.ListPreference; 37import android.support.v7.preference.Preference; 38import android.support.v7.preference.PreferenceGroupAdapter; 39import android.support.v7.preference.PreferenceManager; 40import android.support.v7.preference.PreferenceScreen; 41import android.support.v7.preference.PreferenceViewHolder; 42import android.support.v7.widget.LinearLayoutManager; 43import android.support.v7.widget.RecyclerView; 44import android.util.TypedValue; 45import android.view.ContextThemeWrapper; 46import android.view.LayoutInflater; 47import android.view.View; 48import android.view.ViewGroup; 49 50/** 51 * Shows a hierarchy of {@link Preference} objects as 52 * lists. These preferences will 53 * automatically save to {@link android.content.SharedPreferences} as the user interacts with 54 * them. To retrieve an instance of {@link android.content.SharedPreferences} that the 55 * preference hierarchy in this fragment will use, call 56 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)} 57 * with a context in the same package as this fragment. 58 * <p> 59 * Furthermore, the preferences shown will follow the visual style of system 60 * preferences. It is easy to create a hierarchy of preferences (that can be 61 * shown on multiple screens) via XML. For these reasons, it is recommended to 62 * use this fragment (as a superclass) to deal with preferences in applications. 63 * <p> 64 * A {@link PreferenceScreen} object should be at the top of the preference 65 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy 66 * denote a screen break--that is the preferences contained within subsequent 67 * {@link PreferenceScreen} should be shown on another screen. The preference 68 * framework handles this by calling {@link #onNavigateToScreen(PreferenceScreen)}. 69 * <p> 70 * The preference hierarchy can be formed in multiple ways: 71 * <li> From an XML file specifying the hierarchy 72 * <li> From different {@link android.app.Activity Activities} that each specify its own 73 * preferences in an XML file via {@link android.app.Activity} meta-data 74 * <li> From an object hierarchy rooted with {@link PreferenceScreen} 75 * <p> 76 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The 77 * root element should be a {@link PreferenceScreen}. Subsequent elements can point 78 * to actual {@link Preference} subclasses. As mentioned above, subsequent 79 * {@link PreferenceScreen} in the hierarchy will result in the screen break. 80 * <p> 81 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use 82 * {@link #setPreferenceScreen(PreferenceScreen)}. 83 * <p> 84 * As a convenience, this fragment implements a click listener for any 85 * preference in the current hierarchy, see 86 * {@link #onPreferenceTreeClick(Preference)}. 87 * 88 * <div class="special reference"> 89 * <h3>Developer Guides</h3> 90 * <p>For information about using {@code PreferenceFragment}, 91 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> 92 * guide.</p> 93 * </div> 94 * 95 * <a name="SampleCode"></a> 96 * <h3>Sample Code</h3> 97 * 98 * <p>The following sample code shows a simple preference fragment that is 99 * populated from a resource. The resource it loads is:</p> 100 * 101 * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences} 102 * 103 * <p>The fragment implementation itself simply populates the preferences 104 * when created. Note that the preferences framework takes care of loading 105 * the current values out of the app preferences and writing them when changed:</p> 106 * 107 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java 108 * fragment} 109 * 110 * @see Preference 111 * @see PreferenceScreen 112 */ 113public abstract class PreferenceFragment extends Fragment implements 114 PreferenceManager.OnPreferenceTreeClickListener, 115 PreferenceManager.OnDisplayPreferenceDialogListener, 116 PreferenceManager.OnNavigateToScreenListener, 117 DialogPreference.TargetFragment { 118 119 /** 120 * Fragment argument used to specify the tag of the desired root 121 * {@link android.support.v7.preference.PreferenceScreen} object. 122 */ 123 public static final String ARG_PREFERENCE_ROOT = 124 "android.support.v7.preference.PreferenceFragmentCompat.PREFERENCE_ROOT"; 125 126 private static final String PREFERENCES_TAG = "android:preferences"; 127 128 private static final String DIALOG_FRAGMENT_TAG = 129 "android.support.v14.preference.PreferenceFragment.DIALOG"; 130 131 private PreferenceManager mPreferenceManager; 132 private RecyclerView mList; 133 private boolean mHavePrefs; 134 private boolean mInitDone; 135 136 private Context mStyledContext; 137 138 private int mLayoutResId = android.support.v7.preference.R.layout.preference_list_fragment; 139 140 private final DividerDecoration mDividerDecoration = new DividerDecoration(); 141 142 private static final int MSG_BIND_PREFERENCES = 1; 143 private Handler mHandler = new Handler() { 144 @Override 145 public void handleMessage(Message msg) { 146 switch (msg.what) { 147 148 case MSG_BIND_PREFERENCES: 149 bindPreferences(); 150 break; 151 } 152 } 153 }; 154 155 final private Runnable mRequestFocus = new Runnable() { 156 public void run() { 157 mList.focusableViewAvailable(mList); 158 } 159 }; 160 161 /** 162 * Interface that PreferenceFragment's containing activity should 163 * implement to be able to process preference items that wish to 164 * switch to a specified fragment. 165 */ 166 public interface OnPreferenceStartFragmentCallback { 167 /** 168 * Called when the user has clicked on a Preference that has 169 * a fragment class name associated with it. The implementation 170 * should instantiate and switch to an instance of the given 171 * fragment. 172 * @param caller The fragment requesting navigation. 173 * @param pref The preference requesting the fragment. 174 * @return true if the fragment creation has been handled 175 */ 176 boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref); 177 } 178 179 /** 180 * Interface that PreferenceFragment's containing activity should 181 * implement to be able to process preference items that wish to 182 * switch to a new screen of preferences. 183 */ 184 public interface OnPreferenceStartScreenCallback { 185 /** 186 * Called when the user has clicked on a PreferenceScreen item in order to navigate to a new 187 * screen of preferences. 188 * @param caller The fragment requesting navigation. 189 * @param pref The preference screen to navigate to. 190 * @return true if the screen navigation has been handled 191 */ 192 boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref); 193 } 194 195 public interface OnPreferenceDisplayDialogCallback { 196 197 /** 198 * 199 * @param caller The fragment containing the preference requesting the dialog. 200 * @param pref The preference requesting the dialog. 201 * @return true if the dialog creation has been handled. 202 */ 203 boolean onPreferenceDisplayDialog(PreferenceFragment caller, Preference pref); 204 } 205 206 @Override 207 public void onCreate(Bundle savedInstanceState) { 208 super.onCreate(savedInstanceState); 209 final TypedValue tv = new TypedValue(); 210 getActivity().getTheme().resolveAttribute( 211 android.support.v7.preference.R.attr.preferenceTheme, tv, true); 212 final int theme = tv.resourceId; 213 if (theme <= 0) { 214 throw new IllegalStateException("Must specify preferenceTheme in theme"); 215 } 216 mStyledContext = new ContextThemeWrapper(getActivity(), theme); 217 218 mPreferenceManager = new PreferenceManager(mStyledContext); 219 mPreferenceManager.setOnNavigateToScreenListener(this); 220 final Bundle args = getArguments(); 221 final String rootKey; 222 if (args != null) { 223 rootKey = getArguments().getString(ARG_PREFERENCE_ROOT); 224 } else { 225 rootKey = null; 226 } 227 onCreatePreferences(savedInstanceState, rootKey); 228 } 229 230 /** 231 * Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment. 232 * Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either 233 * directly or via helper methods such as {@link #addPreferencesFromResource(int)}. 234 * 235 * @param savedInstanceState If the fragment is being re-created from 236 * a previous saved state, this is the state. 237 * @param rootKey If non-null, this preference fragment should be rooted at the 238 * {@link android.support.v7.preference.PreferenceScreen} with this key. 239 */ 240 public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey); 241 242 @Override 243 public View onCreateView(LayoutInflater inflater, ViewGroup container, 244 Bundle savedInstanceState) { 245 246 TypedArray a = mStyledContext.obtainStyledAttributes(null, 247 R.styleable.PreferenceFragment, 248 TypedArrayUtils.getAttr(mStyledContext, 249 android.support.v7.preference.R.attr.preferenceFragmentStyle, 250 AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE), 251 0); 252 253 mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId); 254 255 final Drawable divider = a.getDrawable(R.styleable.PreferenceFragment_android_divider); 256 final int dividerHeight = a.getDimensionPixelSize( 257 R.styleable.PreferenceFragment_android_dividerHeight, -1); 258 259 a.recycle(); 260 261 // Need to theme the inflater to pick up the preferenceFragmentListStyle 262 final TypedValue tv = new TypedValue(); 263 getActivity().getTheme().resolveAttribute( 264 android.support.v7.preference.R.attr.preferenceTheme, tv, true); 265 final int theme = tv.resourceId; 266 267 final Context themedContext = new ContextThemeWrapper(inflater.getContext(), theme); 268 final LayoutInflater themedInflater = inflater.cloneInContext(themedContext); 269 270 final View view = themedInflater.inflate(mLayoutResId, container, false); 271 272 final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER); 273 if (!(rawListContainer instanceof ViewGroup)) { 274 throw new RuntimeException("Content has view with id attribute " 275 + "'android.R.id.list_container' that is not a ViewGroup class"); 276 } 277 278 final ViewGroup listContainer = (ViewGroup) rawListContainer; 279 280 final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer, 281 savedInstanceState); 282 if (listView == null) { 283 throw new RuntimeException("Could not create RecyclerView"); 284 } 285 286 mList = listView; 287 288 listView.addItemDecoration(mDividerDecoration); 289 setDivider(divider); 290 if (dividerHeight != -1) { 291 setDividerHeight(dividerHeight); 292 } 293 294 listContainer.addView(mList); 295 mHandler.post(mRequestFocus); 296 return view; 297 } 298 299 /** 300 * Sets the drawable that will be drawn between each item in the list. 301 * <p> 302 * <strong>Note:</strong> If the drawable does not have an intrinsic 303 * height, you should also call {@link #setDividerHeight(int)}. 304 * 305 * @param divider the drawable to use 306 * @attr ref R.styleable#PreferenceFragment_android_divider 307 */ 308 public void setDivider(Drawable divider) { 309 mDividerDecoration.setDivider(divider); 310 } 311 312 /** 313 * Sets the height of the divider that will be drawn between each item in the list. Calling 314 * this will override the intrinsic height as set by {@link #setDivider(Drawable)} 315 * 316 * @param height The new height of the divider in pixels. 317 * @attr ref R.styleable#PreferenceFragment_android_dividerHeight 318 */ 319 public void setDividerHeight(int height) { 320 mDividerDecoration.setDividerHeight(height); 321 } 322 323 @Override 324 public void onViewCreated(View view, Bundle savedInstanceState) { 325 super.onViewCreated(view, savedInstanceState); 326 327 if (mHavePrefs) { 328 bindPreferences(); 329 } 330 331 mInitDone = true; 332 } 333 334 @Override 335 public void onActivityCreated(Bundle savedInstanceState) { 336 super.onActivityCreated(savedInstanceState); 337 338 if (savedInstanceState != null) { 339 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); 340 if (container != null) { 341 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 342 if (preferenceScreen != null) { 343 preferenceScreen.restoreHierarchyState(container); 344 } 345 } 346 } 347 } 348 349 @Override 350 public void onStart() { 351 super.onStart(); 352 mPreferenceManager.setOnPreferenceTreeClickListener(this); 353 mPreferenceManager.setOnDisplayPreferenceDialogListener(this); 354 } 355 356 @Override 357 public void onStop() { 358 super.onStop(); 359 mPreferenceManager.setOnPreferenceTreeClickListener(null); 360 mPreferenceManager.setOnDisplayPreferenceDialogListener(null); 361 } 362 363 @Override 364 public void onDestroyView() { 365 mHandler.removeCallbacks(mRequestFocus); 366 mHandler.removeMessages(MSG_BIND_PREFERENCES); 367 if (mHavePrefs) { 368 unbindPreferences(); 369 } 370 mList = null; 371 super.onDestroyView(); 372 } 373 374 @Override 375 public void onSaveInstanceState(Bundle outState) { 376 super.onSaveInstanceState(outState); 377 378 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 379 if (preferenceScreen != null) { 380 Bundle container = new Bundle(); 381 preferenceScreen.saveHierarchyState(container); 382 outState.putBundle(PREFERENCES_TAG, container); 383 } 384 } 385 386 /** 387 * Returns the {@link PreferenceManager} used by this fragment. 388 * @return The {@link PreferenceManager}. 389 */ 390 public PreferenceManager getPreferenceManager() { 391 return mPreferenceManager; 392 } 393 394 /** 395 * Sets the root of the preference hierarchy that this fragment is showing. 396 * 397 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 398 */ 399 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 400 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 401 onUnbindPreferences(); 402 mHavePrefs = true; 403 if (mInitDone) { 404 postBindPreferences(); 405 } 406 } 407 } 408 409 /** 410 * Gets the root of the preference hierarchy that this fragment is showing. 411 * 412 * @return The {@link PreferenceScreen} that is the root of the preference 413 * hierarchy. 414 */ 415 public PreferenceScreen getPreferenceScreen() { 416 return mPreferenceManager.getPreferenceScreen(); 417 } 418 419 /** 420 * Inflates the given XML resource and adds the preference hierarchy to the current 421 * preference hierarchy. 422 * 423 * @param preferencesResId The XML resource ID to inflate. 424 */ 425 public void addPreferencesFromResource(@XmlRes int preferencesResId) { 426 requirePreferenceManager(); 427 428 setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext, 429 preferencesResId, getPreferenceScreen())); 430 } 431 432 /** 433 * Inflates the given XML resource and replaces the current preference hierarchy (if any) with 434 * the preference hierarchy rooted at {@code key}. 435 * 436 * @param preferencesResId The XML resource ID to inflate. 437 * @param key The preference key of the {@link android.support.v7.preference.PreferenceScreen} 438 * to use as the root of the preference hierarchy, or null to use the root 439 * {@link android.support.v7.preference.PreferenceScreen}. 440 */ 441 public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) { 442 requirePreferenceManager(); 443 444 final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(mStyledContext, 445 preferencesResId, null); 446 447 final Preference root; 448 if (key != null) { 449 root = xmlRoot.findPreference(key); 450 if (!(root instanceof PreferenceScreen)) { 451 throw new IllegalArgumentException("Preference object with key " + key 452 + " is not a PreferenceScreen"); 453 } 454 } else { 455 root = xmlRoot; 456 } 457 458 setPreferenceScreen((PreferenceScreen) root); 459 } 460 461 /** 462 * {@inheritDoc} 463 */ 464 public boolean onPreferenceTreeClick(Preference preference) { 465 if (preference.getFragment() != null) { 466 boolean handled = false; 467 if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) { 468 handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment()) 469 .onPreferenceStartFragment(this, preference); 470 } 471 if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback){ 472 handled = ((OnPreferenceStartFragmentCallback) getActivity()) 473 .onPreferenceStartFragment(this, preference); 474 } 475 return handled; 476 } 477 return false; 478 } 479 480 /** 481 * Called by 482 * {@link android.support.v7.preference.PreferenceScreen#onClick()} in order to navigate to a 483 * new screen of preferences. Calls 484 * {@link PreferenceFragment.OnPreferenceStartScreenCallback#onPreferenceStartScreen} 485 * if the target fragment or containing activity implements 486 * {@link PreferenceFragment.OnPreferenceStartScreenCallback}. 487 * @param preferenceScreen The {@link android.support.v7.preference.PreferenceScreen} to 488 * navigate to. 489 */ 490 @Override 491 public void onNavigateToScreen(PreferenceScreen preferenceScreen) { 492 boolean handled = false; 493 if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) { 494 handled = ((OnPreferenceStartScreenCallback) getCallbackFragment()) 495 .onPreferenceStartScreen(this, preferenceScreen); 496 } 497 if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) { 498 ((OnPreferenceStartScreenCallback) getActivity()) 499 .onPreferenceStartScreen(this, preferenceScreen); 500 } 501 } 502 503 /** 504 * Finds a {@link Preference} based on its key. 505 * 506 * @param key The key of the preference to retrieve. 507 * @return The {@link Preference} with the key, or null. 508 * @see android.support.v7.preference.PreferenceGroup#findPreference(CharSequence) 509 */ 510 public Preference findPreference(CharSequence key) { 511 if (mPreferenceManager == null) { 512 return null; 513 } 514 return mPreferenceManager.findPreference(key); 515 } 516 517 private void requirePreferenceManager() { 518 if (mPreferenceManager == null) { 519 throw new RuntimeException("This should be called after super.onCreate."); 520 } 521 } 522 523 private void postBindPreferences() { 524 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 525 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 526 } 527 528 private void bindPreferences() { 529 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 530 if (preferenceScreen != null) { 531 getListView().setAdapter(onCreateAdapter(preferenceScreen)); 532 preferenceScreen.onAttached(); 533 } 534 onBindPreferences(); 535 } 536 537 private void unbindPreferences() { 538 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 539 if (preferenceScreen != null) { 540 preferenceScreen.onDetached(); 541 } 542 onUnbindPreferences(); 543 } 544 545 /** @hide */ 546 protected void onBindPreferences() { 547 } 548 549 /** @hide */ 550 protected void onUnbindPreferences() { 551 } 552 553 public final RecyclerView getListView() { 554 return mList; 555 } 556 557 /** 558 * Creates the {@link android.support.v7.widget.RecyclerView} used to display the preferences. 559 * Subclasses may override this to return a customized 560 * {@link android.support.v7.widget.RecyclerView}. 561 * @param inflater The LayoutInflater object that can be used to inflate the 562 * {@link android.support.v7.widget.RecyclerView}. 563 * @param parent The parent {@link android.view.View} that the RecyclerView will be attached to. 564 * This method should not add the view itself, but this can be used to generate 565 * the LayoutParams of the view. 566 * @param savedInstanceState If non-null, this view is being re-constructed from a previous 567 * saved state as given here 568 * @return A new RecyclerView object to be placed into the view hierarchy 569 */ 570 public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, 571 Bundle savedInstanceState) { 572 RecyclerView recyclerView = (RecyclerView) inflater 573 .inflate(android.support.v7.preference.R.layout.preference_recyclerview, 574 parent, false); 575 576 recyclerView.setLayoutManager(onCreateLayoutManager()); 577 578 return recyclerView; 579 } 580 581 /** 582 * Called from {@link #onCreateRecyclerView} to create the 583 * {@link android.support.v7.widget.RecyclerView.LayoutManager} for the created 584 * {@link android.support.v7.widget.RecyclerView}. 585 * @return A new {@link android.support.v7.widget.RecyclerView.LayoutManager} instance. 586 */ 587 public RecyclerView.LayoutManager onCreateLayoutManager() { 588 return new LinearLayoutManager(getActivity()); 589 } 590 591 /** 592 * Creates the root adapter. 593 * 594 * @param preferenceScreen Preference screen object to create the adapter for. 595 * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}. 596 */ 597 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 598 return new PreferenceGroupAdapter(preferenceScreen); 599 } 600 601 /** 602 * Called when a preference in the tree requests to display a dialog. Subclasses should 603 * override this method to display custom dialogs or to handle dialogs for custom preference 604 * classes. 605 * 606 * @param preference The Preference object requesting the dialog. 607 */ 608 @Override 609 public void onDisplayPreferenceDialog(Preference preference) { 610 611 boolean handled = false; 612 if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) { 613 handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment()) 614 .onPreferenceDisplayDialog(this, preference); 615 } 616 if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) { 617 handled = ((OnPreferenceDisplayDialogCallback) getActivity()) 618 .onPreferenceDisplayDialog(this, preference); 619 } 620 621 if (handled) { 622 return; 623 } 624 625 // check if dialog is already showing 626 if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { 627 return; 628 } 629 630 final DialogFragment f; 631 if (preference instanceof EditTextPreference) { 632 f = EditTextPreferenceDialogFragment.newInstance(preference.getKey()); 633 } else if (preference instanceof ListPreference) { 634 f = ListPreferenceDialogFragment.newInstance(preference.getKey()); 635 } else if (preference instanceof MultiSelectListPreference) { 636 f = MultiSelectListPreferenceDialogFragment.newInstance(preference.getKey()); 637 } else { 638 throw new IllegalArgumentException("Tried to display dialog for unknown " + 639 "preference type. Did you forget to override onDisplayPreferenceDialog()?"); 640 } 641 f.setTargetFragment(this, 0); 642 f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); 643 } 644 645 /** 646 * Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib. 647 * @return Fragment to possibly use as a callback 648 * @hide 649 */ 650 public Fragment getCallbackFragment() { 651 return null; 652 } 653 654 private class DividerDecoration extends RecyclerView.ItemDecoration { 655 656 private Drawable mDivider; 657 private int mDividerHeight; 658 659 @Override 660 public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 661 if (mDivider == null) { 662 return; 663 } 664 final int childCount = parent.getChildCount(); 665 final int width = parent.getWidth(); 666 for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { 667 final View view = parent.getChildAt(childViewIndex); 668 if (shouldDrawDividerBelow(view, parent)) { 669 int top = (int) ViewCompat.getY(view) + view.getHeight(); 670 mDivider.setBounds(0, top, width, top + mDividerHeight); 671 mDivider.draw(c); 672 } 673 } 674 } 675 676 @Override 677 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 678 RecyclerView.State state) { 679 if (shouldDrawDividerBelow(view, parent)) { 680 outRect.bottom = mDividerHeight; 681 } 682 } 683 684 private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { 685 final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); 686 final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder 687 && ((PreferenceViewHolder) holder).isDividerAllowedBelow(); 688 if (!dividerAllowedBelow) { 689 return false; 690 } 691 boolean nextAllowed = true; 692 int index = parent.indexOfChild(view); 693 if (index < parent.getChildCount() - 1) { 694 final View nextView = parent.getChildAt(index + 1); 695 final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView); 696 nextAllowed = nextHolder instanceof PreferenceViewHolder 697 && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove(); 698 } 699 return nextAllowed; 700 } 701 702 public void setDivider(Drawable divider) { 703 if (divider != null) { 704 mDividerHeight = divider.getIntrinsicHeight(); 705 } else { 706 mDividerHeight = 0; 707 } 708 mDivider = divider; 709 mList.invalidateItemDecorations(); 710 } 711 712 public void setDividerHeight(int dividerHeight) { 713 mDividerHeight = dividerHeight; 714 mList.invalidateItemDecorations(); 715 } 716 } 717} 718