PreferenceFragment.java revision 57f39186667b8acef1a0ebeda585c357a751a8b3
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 = 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(R.attr.preferenceTheme, tv, true); 211 final int theme = tv.resourceId; 212 if (theme <= 0) { 213 throw new IllegalStateException("Must specify preferenceTheme in theme"); 214 } 215 mStyledContext = new ContextThemeWrapper(getActivity(), theme); 216 217 mPreferenceManager = new PreferenceManager(mStyledContext); 218 mPreferenceManager.setOnNavigateToScreenListener(this); 219 final Bundle args = getArguments(); 220 final String rootKey; 221 if (args != null) { 222 rootKey = getArguments().getString(ARG_PREFERENCE_ROOT); 223 } else { 224 rootKey = null; 225 } 226 onCreatePreferences(savedInstanceState, rootKey); 227 } 228 229 /** 230 * Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment. 231 * Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either 232 * directly or via helper methods such as {@link #addPreferencesFromResource(int)}. 233 * 234 * @param savedInstanceState If the fragment is being re-created from 235 * a previous saved state, this is the state. 236 * @param rootKey If non-null, this preference fragment should be rooted at the 237 * {@link android.support.v7.preference.PreferenceScreen} with this key. 238 */ 239 public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey); 240 241 @Override 242 public View onCreateView(LayoutInflater inflater, ViewGroup container, 243 Bundle savedInstanceState) { 244 245 TypedArray a = mStyledContext.obtainStyledAttributes(null, 246 R.styleable.PreferenceFragment, 247 TypedArrayUtils.getAttr(mStyledContext, R.attr.preferenceFragmentStyle, 248 AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE), 249 0); 250 251 mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId); 252 253 final Drawable divider = a.getDrawable(R.styleable.PreferenceFragment_android_divider); 254 final int dividerHeight = a.getDimensionPixelSize( 255 R.styleable.PreferenceFragment_android_dividerHeight, -1); 256 257 a.recycle(); 258 259 // Need to theme the inflater to pick up the preferenceFragmentListStyle 260 final TypedValue tv = new TypedValue(); 261 getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true); 262 final int theme = tv.resourceId; 263 264 final Context themedContext = new ContextThemeWrapper(inflater.getContext(), theme); 265 final LayoutInflater themedInflater = inflater.cloneInContext(themedContext); 266 267 final View view = themedInflater.inflate(mLayoutResId, container, false); 268 269 final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER); 270 if (!(rawListContainer instanceof ViewGroup)) { 271 throw new RuntimeException("Content has view with id attribute " 272 + "'android.R.id.list_container' that is not a ViewGroup class"); 273 } 274 275 final ViewGroup listContainer = (ViewGroup) rawListContainer; 276 277 final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer, 278 savedInstanceState); 279 if (listView == null) { 280 throw new RuntimeException("Could not create RecyclerView"); 281 } 282 283 mList = listView; 284 285 listView.addItemDecoration(mDividerDecoration); 286 setDivider(divider); 287 if (dividerHeight != -1) { 288 setDividerHeight(dividerHeight); 289 } 290 291 listContainer.addView(mList); 292 mHandler.post(mRequestFocus); 293 return view; 294 } 295 296 /** 297 * Sets the drawable that will be drawn between each item in the list. 298 * <p> 299 * <strong>Note:</strong> If the drawable does not have an intrinsic 300 * height, you should also call {@link #setDividerHeight(int)}. 301 * 302 * @param divider the drawable to use 303 * @attr ref R.styleable#PreferenceFragment_android_divider 304 */ 305 public void setDivider(Drawable divider) { 306 mDividerDecoration.setDivider(divider); 307 } 308 309 /** 310 * Sets the height of the divider that will be drawn between each item in the list. Calling 311 * this will override the intrinsic height as set by {@link #setDivider(Drawable)} 312 * 313 * @param height The new height of the divider in pixels. 314 * @attr ref R.styleable#PreferenceFragment_android_dividerHeight 315 */ 316 public void setDividerHeight(int height) { 317 mDividerDecoration.setDividerHeight(height); 318 } 319 320 @Override 321 public void onViewCreated(View view, Bundle savedInstanceState) { 322 super.onViewCreated(view, savedInstanceState); 323 324 if (mHavePrefs) { 325 bindPreferences(); 326 } 327 328 mInitDone = true; 329 } 330 331 @Override 332 public void onActivityCreated(Bundle savedInstanceState) { 333 super.onActivityCreated(savedInstanceState); 334 335 if (savedInstanceState != null) { 336 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); 337 if (container != null) { 338 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 339 if (preferenceScreen != null) { 340 preferenceScreen.restoreHierarchyState(container); 341 } 342 } 343 } 344 } 345 346 @Override 347 public void onStart() { 348 super.onStart(); 349 mPreferenceManager.setOnPreferenceTreeClickListener(this); 350 mPreferenceManager.setOnDisplayPreferenceDialogListener(this); 351 } 352 353 @Override 354 public void onStop() { 355 super.onStop(); 356 mPreferenceManager.setOnPreferenceTreeClickListener(null); 357 mPreferenceManager.setOnDisplayPreferenceDialogListener(null); 358 } 359 360 @Override 361 public void onDestroyView() { 362 mHandler.removeCallbacks(mRequestFocus); 363 mHandler.removeMessages(MSG_BIND_PREFERENCES); 364 if (mHavePrefs) { 365 unbindPreferences(); 366 } 367 mList = null; 368 super.onDestroyView(); 369 } 370 371 @Override 372 public void onSaveInstanceState(Bundle outState) { 373 super.onSaveInstanceState(outState); 374 375 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 376 if (preferenceScreen != null) { 377 Bundle container = new Bundle(); 378 preferenceScreen.saveHierarchyState(container); 379 outState.putBundle(PREFERENCES_TAG, container); 380 } 381 } 382 383 /** 384 * Returns the {@link PreferenceManager} used by this fragment. 385 * @return The {@link PreferenceManager}. 386 */ 387 public PreferenceManager getPreferenceManager() { 388 return mPreferenceManager; 389 } 390 391 /** 392 * Sets the root of the preference hierarchy that this fragment is showing. 393 * 394 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 395 */ 396 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 397 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 398 onUnbindPreferences(); 399 mHavePrefs = true; 400 if (mInitDone) { 401 postBindPreferences(); 402 } 403 } 404 } 405 406 /** 407 * Gets the root of the preference hierarchy that this fragment is showing. 408 * 409 * @return The {@link PreferenceScreen} that is the root of the preference 410 * hierarchy. 411 */ 412 public PreferenceScreen getPreferenceScreen() { 413 return mPreferenceManager.getPreferenceScreen(); 414 } 415 416 /** 417 * Inflates the given XML resource and adds the preference hierarchy to the current 418 * preference hierarchy. 419 * 420 * @param preferencesResId The XML resource ID to inflate. 421 */ 422 public void addPreferencesFromResource(@XmlRes int preferencesResId) { 423 requirePreferenceManager(); 424 425 setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext, 426 preferencesResId, getPreferenceScreen())); 427 } 428 429 /** 430 * Inflates the given XML resource and replaces the current preference hierarchy (if any) with 431 * the preference hierarchy rooted at {@code key}. 432 * 433 * @param preferencesResId The XML resource ID to inflate. 434 * @param key The preference key of the {@link android.support.v7.preference.PreferenceScreen} 435 * to use as the root of the preference hierarchy, or null to use the root 436 * {@link android.support.v7.preference.PreferenceScreen}. 437 */ 438 public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) { 439 requirePreferenceManager(); 440 441 final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(mStyledContext, 442 preferencesResId, null); 443 444 final Preference root; 445 if (key != null) { 446 root = xmlRoot.findPreference(key); 447 if (!(root instanceof PreferenceScreen)) { 448 throw new IllegalArgumentException("Preference object with key " + key 449 + " is not a PreferenceScreen"); 450 } 451 } else { 452 root = xmlRoot; 453 } 454 455 setPreferenceScreen((PreferenceScreen) root); 456 } 457 458 /** 459 * {@inheritDoc} 460 */ 461 public boolean onPreferenceTreeClick(Preference preference) { 462 if (preference.getFragment() != null) { 463 boolean handled = false; 464 if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) { 465 handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment()) 466 .onPreferenceStartFragment(this, preference); 467 } 468 if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback){ 469 handled = ((OnPreferenceStartFragmentCallback) getActivity()) 470 .onPreferenceStartFragment(this, preference); 471 } 472 return handled; 473 } 474 return false; 475 } 476 477 /** 478 * Called by 479 * {@link android.support.v7.preference.PreferenceScreen#onClick()} in order to navigate to a 480 * new screen of preferences. Calls 481 * {@link PreferenceFragment.OnPreferenceStartScreenCallback#onPreferenceStartScreen} 482 * if the target fragment or containing activity implements 483 * {@link PreferenceFragment.OnPreferenceStartScreenCallback}. 484 * @param preferenceScreen The {@link android.support.v7.preference.PreferenceScreen} to 485 * navigate to. 486 */ 487 @Override 488 public void onNavigateToScreen(PreferenceScreen preferenceScreen) { 489 boolean handled = false; 490 if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) { 491 handled = ((OnPreferenceStartScreenCallback) getCallbackFragment()) 492 .onPreferenceStartScreen(this, preferenceScreen); 493 } 494 if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) { 495 ((OnPreferenceStartScreenCallback) getActivity()) 496 .onPreferenceStartScreen(this, preferenceScreen); 497 } 498 } 499 500 /** 501 * Finds a {@link Preference} based on its key. 502 * 503 * @param key The key of the preference to retrieve. 504 * @return The {@link Preference} with the key, or null. 505 * @see android.support.v7.preference.PreferenceGroup#findPreference(CharSequence) 506 */ 507 public Preference findPreference(CharSequence key) { 508 if (mPreferenceManager == null) { 509 return null; 510 } 511 return mPreferenceManager.findPreference(key); 512 } 513 514 private void requirePreferenceManager() { 515 if (mPreferenceManager == null) { 516 throw new RuntimeException("This should be called after super.onCreate."); 517 } 518 } 519 520 private void postBindPreferences() { 521 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 522 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 523 } 524 525 private void bindPreferences() { 526 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 527 if (preferenceScreen != null) { 528 getListView().setAdapter(onCreateAdapter(preferenceScreen)); 529 preferenceScreen.onAttached(); 530 } 531 onBindPreferences(); 532 } 533 534 private void unbindPreferences() { 535 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 536 if (preferenceScreen != null) { 537 preferenceScreen.onDetached(); 538 } 539 onUnbindPreferences(); 540 } 541 542 /** @hide */ 543 protected void onBindPreferences() { 544 } 545 546 /** @hide */ 547 protected void onUnbindPreferences() { 548 } 549 550 public final RecyclerView getListView() { 551 return mList; 552 } 553 554 /** 555 * Creates the {@link android.support.v7.widget.RecyclerView} used to display the preferences. 556 * Subclasses may override this to return a customized 557 * {@link android.support.v7.widget.RecyclerView}. 558 * @param inflater The LayoutInflater object that can be used to inflate the 559 * {@link android.support.v7.widget.RecyclerView}. 560 * @param parent The parent {@link android.view.View} that the RecyclerView will be attached to. 561 * This method should not add the view itself, but this can be used to generate 562 * the LayoutParams of the view. 563 * @param savedInstanceState If non-null, this view is being re-constructed from a previous 564 * saved state as given here 565 * @return A new RecyclerView object to be placed into the view hierarchy 566 */ 567 public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, 568 Bundle savedInstanceState) { 569 RecyclerView recyclerView = (RecyclerView) inflater 570 .inflate(R.layout.preference_recyclerview, parent, false); 571 572 recyclerView.setLayoutManager(onCreateLayoutManager()); 573 574 return recyclerView; 575 } 576 577 /** 578 * Called from {@link #onCreateRecyclerView} to create the 579 * {@link android.support.v7.widget.RecyclerView.LayoutManager} for the created 580 * {@link android.support.v7.widget.RecyclerView}. 581 * @return A new {@link android.support.v7.widget.RecyclerView.LayoutManager} instance. 582 */ 583 public RecyclerView.LayoutManager onCreateLayoutManager() { 584 return new LinearLayoutManager(getActivity()); 585 } 586 587 /** 588 * Creates the root adapter. 589 * 590 * @param preferenceScreen Preference screen object to create the adapter for. 591 * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}. 592 */ 593 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 594 return new PreferenceGroupAdapter(preferenceScreen); 595 } 596 597 /** 598 * Called when a preference in the tree requests to display a dialog. Subclasses should 599 * override this method to display custom dialogs or to handle dialogs for custom preference 600 * classes. 601 * 602 * @param preference The Preference object requesting the dialog. 603 */ 604 @Override 605 public void onDisplayPreferenceDialog(Preference preference) { 606 607 boolean handled = false; 608 if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) { 609 handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment()) 610 .onPreferenceDisplayDialog(this, preference); 611 } 612 if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) { 613 handled = ((OnPreferenceDisplayDialogCallback) getActivity()) 614 .onPreferenceDisplayDialog(this, preference); 615 } 616 617 if (handled) { 618 return; 619 } 620 621 // check if dialog is already showing 622 if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { 623 return; 624 } 625 626 final DialogFragment f; 627 if (preference instanceof EditTextPreference) { 628 f = EditTextPreferenceDialogFragment.newInstance(preference.getKey()); 629 } else if (preference instanceof ListPreference) { 630 f = ListPreferenceDialogFragment.newInstance(preference.getKey()); 631 } else if (preference instanceof MultiSelectListPreference) { 632 f = MultiSelectListPreferenceDialogFragment.newInstance(preference.getKey()); 633 } else { 634 throw new IllegalArgumentException("Tried to display dialog for unknown " + 635 "preference type. Did you forget to override onDisplayPreferenceDialog()?"); 636 } 637 f.setTargetFragment(this, 0); 638 f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); 639 } 640 641 /** 642 * Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib. 643 * @return Fragment to possibly use as a callback 644 * @hide 645 */ 646 public Fragment getCallbackFragment() { 647 return null; 648 } 649 650 private class DividerDecoration extends RecyclerView.ItemDecoration { 651 652 private Drawable mDivider; 653 private int mDividerHeight; 654 655 @Override 656 public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 657 if (mDivider == null) { 658 return; 659 } 660 final int childCount = parent.getChildCount(); 661 final int width = parent.getWidth(); 662 for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { 663 final View view = parent.getChildAt(childViewIndex); 664 if (shouldDrawDividerBelow(view, parent)) { 665 int top = (int) ViewCompat.getY(view) + view.getHeight(); 666 mDivider.setBounds(0, top, width, top + mDividerHeight); 667 mDivider.draw(c); 668 } 669 } 670 } 671 672 @Override 673 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 674 RecyclerView.State state) { 675 if (shouldDrawDividerBelow(view, parent)) { 676 outRect.bottom = mDividerHeight; 677 } 678 } 679 680 private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { 681 final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); 682 final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder 683 && ((PreferenceViewHolder) holder).isDividerAllowedBelow(); 684 if (!dividerAllowedBelow) { 685 return false; 686 } 687 boolean nextAllowed = true; 688 int index = parent.indexOfChild(view); 689 if (index < parent.getChildCount() - 1) { 690 final View nextView = parent.getChildAt(index + 1); 691 final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView); 692 nextAllowed = nextHolder instanceof PreferenceViewHolder 693 && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove(); 694 } 695 return nextAllowed; 696 } 697 698 public void setDivider(Drawable divider) { 699 if (divider != null) { 700 mDividerHeight = divider.getIntrinsicHeight(); 701 } else { 702 mDividerHeight = 0; 703 } 704 mDivider = divider; 705 mList.invalidateItemDecorations(); 706 } 707 708 public void setDividerHeight(int dividerHeight) { 709 mDividerHeight = dividerHeight; 710 mList.invalidateItemDecorations(); 711 } 712 } 713} 714