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