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