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