PreferenceFragmentCompat.java revision 301061d5655864f03f9bab0a28f7c3f2e28a0a04
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(R.id.list_container); 261 if (!(rawListContainer instanceof ViewGroup)) { 262 throw new RuntimeException("Content has view with id attribute 'R.id.list_container' " 263 + "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 onActivityCreated(Bundle savedInstanceState) { 313 super.onActivityCreated(savedInstanceState); 314 315 if (mHavePrefs) { 316 bindPreferences(); 317 } 318 319 mInitDone = true; 320 321 if (savedInstanceState != null) { 322 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); 323 if (container != null) { 324 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 325 if (preferenceScreen != null) { 326 preferenceScreen.restoreHierarchyState(container); 327 } 328 } 329 } 330 } 331 332 @Override 333 public void onStart() { 334 super.onStart(); 335 mPreferenceManager.setOnPreferenceTreeClickListener(this); 336 mPreferenceManager.setOnDisplayPreferenceDialogListener(this); 337 } 338 339 @Override 340 public void onStop() { 341 super.onStop(); 342 mPreferenceManager.setOnPreferenceTreeClickListener(null); 343 mPreferenceManager.setOnDisplayPreferenceDialogListener(null); 344 } 345 346 @Override 347 public void onDestroyView() { 348 mList = null; 349 mHandler.removeCallbacks(mRequestFocus); 350 mHandler.removeMessages(MSG_BIND_PREFERENCES); 351 super.onDestroyView(); 352 } 353 354 @Override 355 public void onSaveInstanceState(Bundle outState) { 356 super.onSaveInstanceState(outState); 357 358 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 359 if (preferenceScreen != null) { 360 Bundle container = new Bundle(); 361 preferenceScreen.saveHierarchyState(container); 362 outState.putBundle(PREFERENCES_TAG, container); 363 } 364 } 365 366 /** 367 * Returns the {@link PreferenceManager} used by this fragment. 368 * @return The {@link PreferenceManager}. 369 */ 370 public PreferenceManager getPreferenceManager() { 371 return mPreferenceManager; 372 } 373 374 /** 375 * Sets the root of the preference hierarchy that this fragment is showing. 376 * 377 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 378 */ 379 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 380 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 381 onUnbindPreferences(); 382 mHavePrefs = true; 383 if (mInitDone) { 384 postBindPreferences(); 385 } 386 } 387 } 388 389 /** 390 * Gets the root of the preference hierarchy that this fragment is showing. 391 * 392 * @return The {@link PreferenceScreen} that is the root of the preference 393 * hierarchy. 394 */ 395 public PreferenceScreen getPreferenceScreen() { 396 return mPreferenceManager.getPreferenceScreen(); 397 } 398 399 /** 400 * Inflates the given XML resource and adds the preference hierarchy to the current 401 * preference hierarchy. 402 * 403 * @param preferencesResId The XML resource ID to inflate. 404 */ 405 public void addPreferencesFromResource(@XmlRes int preferencesResId) { 406 requirePreferenceManager(); 407 408 setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext, 409 preferencesResId, getPreferenceScreen())); 410 } 411 412 /** 413 * Inflates the given XML resource and replaces the current preference hierarchy (if any) with 414 * the preference hierarchy rooted at {@code key}. 415 * 416 * @param preferencesResId The XML resource ID to inflate. 417 * @param key The preference key of the {@link android.support.v7.preference.PreferenceScreen} 418 * to use as the root of the preference hierarchy, or null to use the root 419 * {@link android.support.v7.preference.PreferenceScreen}. 420 */ 421 public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) { 422 requirePreferenceManager(); 423 424 final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(mStyledContext, 425 preferencesResId, null); 426 427 final Preference root; 428 if (key != null) { 429 root = xmlRoot.findPreference(key); 430 if (!(root instanceof PreferenceScreen)) { 431 throw new IllegalArgumentException("Preference object with key " + key 432 + " is not a PreferenceScreen"); 433 } 434 } else { 435 root = xmlRoot; 436 } 437 438 setPreferenceScreen((PreferenceScreen) root); 439 } 440 441 /** 442 * {@inheritDoc} 443 */ 444 public boolean onPreferenceTreeClick(Preference preference) { 445 if (preference.getFragment() != null) { 446 boolean handled = false; 447 if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) { 448 handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment()) 449 .onPreferenceStartFragment(this, preference); 450 } 451 if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback){ 452 handled = ((OnPreferenceStartFragmentCallback) getActivity()) 453 .onPreferenceStartFragment(this, preference); 454 } 455 return handled; 456 } 457 return false; 458 } 459 460 /** 461 * Called by 462 * {@link android.support.v7.preference.PreferenceScreen#onClick()} in order to navigate to a 463 * new screen of preferences. Calls 464 * {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback#onPreferenceStartScreen} 465 * if the target fragment or containing activity implements 466 * {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback}. 467 * @param preferenceScreen The {@link android.support.v7.preference.PreferenceScreen} to 468 * navigate to. 469 */ 470 @Override 471 public void onNavigateToScreen(PreferenceScreen preferenceScreen) { 472 boolean handled = false; 473 if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) { 474 handled = ((OnPreferenceStartScreenCallback) getCallbackFragment()) 475 .onPreferenceStartScreen(this, preferenceScreen); 476 } 477 if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) { 478 ((OnPreferenceStartScreenCallback) getActivity()) 479 .onPreferenceStartScreen(this, preferenceScreen); 480 } 481 } 482 483 /** 484 * Finds a {@link Preference} based on its key. 485 * 486 * @param key The key of the preference to retrieve. 487 * @return The {@link Preference} with the key, or null. 488 * @see android.support.v7.preference.PreferenceGroup#findPreference(CharSequence) 489 */ 490 public Preference findPreference(CharSequence key) { 491 if (mPreferenceManager == null) { 492 return null; 493 } 494 return mPreferenceManager.findPreference(key); 495 } 496 497 private void requirePreferenceManager() { 498 if (mPreferenceManager == null) { 499 throw new RuntimeException("This should be called after super.onCreate."); 500 } 501 } 502 503 private void postBindPreferences() { 504 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 505 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 506 } 507 508 private void bindPreferences() { 509 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 510 if (preferenceScreen != null) { 511 getListView().setAdapter(onCreateAdapter(preferenceScreen)); 512 preferenceScreen.onAttached(); 513 } 514 onBindPreferences(); 515 } 516 517 /** @hide */ 518 protected void onBindPreferences() { 519 } 520 521 /** @hide */ 522 protected void onUnbindPreferences() { 523 } 524 525 public final RecyclerView getListView() { 526 return mList; 527 } 528 529 /** 530 * Creates the {@link android.support.v7.widget.RecyclerView} used to display the preferences. 531 * Subclasses may override this to return a customized 532 * {@link android.support.v7.widget.RecyclerView}. 533 * @param inflater The LayoutInflater object that can be used to inflate the 534 * {@link android.support.v7.widget.RecyclerView}. 535 * @param parent The parent {@link android.view.View} that the RecyclerView will be attached to. 536 * This method should not add the view itself, but this can be used to generate 537 * the LayoutParams of the view. 538 * @param savedInstanceState If non-null, this view is being re-constructed from a previous 539 * saved state as given here 540 * @return A new RecyclerView object to be placed into the view hierarchy 541 */ 542 public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, 543 Bundle savedInstanceState) { 544 RecyclerView recyclerView = (RecyclerView) inflater 545 .inflate(R.layout.preference_recyclerview, parent, false); 546 547 recyclerView.setLayoutManager(onCreateLayoutManager()); 548 549 return recyclerView; 550 } 551 552 /** 553 * Called from {@link #onCreateRecyclerView} to create the 554 * {@link android.support.v7.widget.RecyclerView.LayoutManager} for the created 555 * {@link android.support.v7.widget.RecyclerView}. 556 * @return A new {@link android.support.v7.widget.RecyclerView.LayoutManager} instance. 557 */ 558 public RecyclerView.LayoutManager onCreateLayoutManager() { 559 return new LinearLayoutManager(getActivity()); 560 } 561 562 /** 563 * Creates the root adapter. 564 * 565 * @param preferenceScreen Preference screen object to create the adapter for. 566 * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}. 567 */ 568 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 569 return new PreferenceGroupAdapter(preferenceScreen); 570 } 571 572 /** 573 * Called when a preference in the tree requests to display a dialog. Subclasses should 574 * override this method to display custom dialogs or to handle dialogs for custom preference 575 * classes. 576 * 577 * @param preference The Preference object requesting the dialog. 578 */ 579 @Override 580 public void onDisplayPreferenceDialog(Preference preference) { 581 582 boolean handled = false; 583 if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) { 584 handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment()) 585 .onPreferenceDisplayDialog(this, preference); 586 } 587 if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) { 588 handled = ((OnPreferenceDisplayDialogCallback) getActivity()) 589 .onPreferenceDisplayDialog(this, preference); 590 } 591 592 if (handled) { 593 return; 594 } 595 596 // check if dialog is already showing 597 if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { 598 return; 599 } 600 601 final DialogFragment f; 602 if (preference instanceof EditTextPreference) { 603 f = EditTextPreferenceDialogFragmentCompat.newInstance(preference.getKey()); 604 } else if (preference instanceof ListPreference) { 605 f = ListPreferenceDialogFragmentCompat.newInstance(preference.getKey()); 606 } else { 607 throw new IllegalArgumentException("Tried to display dialog for unknown " + 608 "preference type. Did you forget to override onDisplayPreferenceDialog()?"); 609 } 610 f.setTargetFragment(this, 0); 611 f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); 612 } 613 614 /** 615 * Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib. 616 * @return Fragment to possibly use as a callback 617 * @hide 618 */ 619 public Fragment getCallbackFragment() { 620 return null; 621 } 622 623 private class DividerDecoration extends RecyclerView.ItemDecoration { 624 625 private Drawable mDivider; 626 private int mDividerHeight; 627 628 @Override 629 public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 630 if (mDivider == null) { 631 return; 632 } 633 final int childCount = parent.getChildCount(); 634 final int width = parent.getWidth(); 635 for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { 636 final View view = parent.getChildAt(childViewIndex); 637 if (shouldDrawDividerAbove(view, parent)) { 638 int top = (int) ViewCompat.getY(view); 639 mDivider.setBounds(0, top, width, top + mDividerHeight); 640 mDivider.draw(c); 641 } 642 if (shouldDrawDividerBelow(view, parent)) { 643 int top = (int) ViewCompat.getY(view) + view.getHeight(); 644 mDivider.setBounds(0, top, width, top + mDividerHeight); 645 mDivider.draw(c); 646 } 647 } 648 } 649 650 @Override 651 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 652 RecyclerView.State state) { 653 if (shouldDrawDividerAbove(view, parent)) { 654 outRect.top = mDividerHeight; 655 } 656 if (shouldDrawDividerBelow(view, parent)) { 657 outRect.bottom = mDividerHeight; 658 } 659 } 660 661 private boolean shouldDrawDividerAbove(View view, RecyclerView parent) { 662 final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); 663 return holder.getAdapterPosition() == 0 && 664 ((PreferenceViewHolder) holder).isDividerAllowedAbove(); 665 } 666 667 private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { 668 final PreferenceViewHolder holder = 669 (PreferenceViewHolder) parent.getChildViewHolder(view); 670 boolean nextAllowed = true; 671 int index = parent.indexOfChild(view); 672 if (index < parent.getChildCount() - 1) { 673 final View nextView = parent.getChildAt(index + 1); 674 final PreferenceViewHolder nextHolder = 675 (PreferenceViewHolder) parent.getChildViewHolder(nextView); 676 nextAllowed = nextHolder.isDividerAllowedAbove(); 677 } 678 return nextAllowed && holder.isDividerAllowedBelow(); 679 } 680 681 public void setDivider(Drawable divider) { 682 if (divider != null) { 683 mDividerHeight = divider.getIntrinsicHeight(); 684 } else { 685 mDividerHeight = 0; 686 } 687 mDivider = divider; 688 mList.invalidateItemDecorations(); 689 } 690 691 public void setDividerHeight(int dividerHeight) { 692 mDividerHeight = dividerHeight; 693 mList.invalidateItemDecorations(); 694 } 695 } 696} 697