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 com.android.setupwizardlib;
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.support.annotation.IntDef;
25import android.support.v4.view.ViewCompat;
26import android.support.v7.widget.RecyclerView;
27import android.view.View;
28
29import java.lang.annotation.Retention;
30import java.lang.annotation.RetentionPolicy;
31
32/**
33 * An {@link android.support.v7.widget.RecyclerView.ItemDecoration} for RecyclerView to draw
34 * dividers between items. This ItemDecoration will draw the drawable specified by
35 * {@link #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by
36 * default, and the behavior of whether the divider is shown can be customized by subclassing
37 * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}.
38 *
39 * <p>Modified from v14 PreferenceFragment.DividerDecoration, added with inset capabilities.
40 */
41public class DividerItemDecoration extends RecyclerView.ItemDecoration {
42
43    /* static section */
44
45    public interface DividedViewHolder {
46
47        /**
48         * Returns whether divider is allowed above this item. A divider will be shown only if both
49         * items immediately above and below it allows this divider.
50         */
51        boolean isDividerAllowedAbove();
52
53        /**
54         * Returns whether divider is allowed below this item. A divider will be shown only if both
55         * items immediately above and below it allows this divider.
56         */
57        boolean isDividerAllowedBelow();
58    }
59
60    @Retention(RetentionPolicy.SOURCE)
61    @IntDef({
62            DIVIDER_CONDITION_EITHER,
63            DIVIDER_CONDITION_BOTH})
64    public @interface DividerCondition {}
65
66    public static final int DIVIDER_CONDITION_EITHER = 0;
67    public static final int DIVIDER_CONDITION_BOTH = 1;
68
69    /**
70     * Creates a default instance of {@link DividerItemDecoration}, using
71     * {@code android:attr/listDivider} as the divider and {@code android:attr/dividerHeight} as the
72     * divider height.
73     */
74    public static DividerItemDecoration getDefault(Context context) {
75        final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration);
76        final Drawable divider = a.getDrawable(
77                R.styleable.SuwDividerItemDecoration_android_listDivider);
78        final int dividerHeight = a.getDimensionPixelSize(
79                R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0);
80        @DividerCondition final int dividerCondition = a.getInt(
81                R.styleable.SuwDividerItemDecoration_suwDividerCondition,
82                DIVIDER_CONDITION_EITHER);
83        a.recycle();
84
85        final DividerItemDecoration decoration = new DividerItemDecoration();
86        decoration.setDivider(divider);
87        decoration.setDividerHeight(dividerHeight);
88        decoration.setDividerCondition(dividerCondition);
89        return decoration;
90    }
91
92    /* non-static section */
93
94    private Drawable mDivider;
95    private int mDividerHeight;
96    private int mDividerIntrinsicHeight;
97    @DividerCondition
98    private int mDividerCondition;
99
100    @Override
101    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
102        if (mDivider == null) {
103            return;
104        }
105        final int childCount = parent.getChildCount();
106        final int width = parent.getWidth();
107        final int dividerHeight = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight;
108        for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
109            final View view = parent.getChildAt(childViewIndex);
110            if (shouldDrawDividerBelow(view, parent)) {
111                final int top = (int) ViewCompat.getY(view) + view.getHeight();
112                mDivider.setBounds(0, top, width, top + dividerHeight);
113                mDivider.draw(c);
114            }
115        }
116    }
117
118    @Override
119    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
120            RecyclerView.State state) {
121        if (shouldDrawDividerBelow(view, parent)) {
122            outRect.bottom = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight;
123        }
124    }
125
126    private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
127        final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
128        final int index = holder.getLayoutPosition();
129        final int lastItemIndex = parent.getAdapter().getItemCount() - 1;
130        if ((holder instanceof DividedViewHolder)) {
131            if (((DividedViewHolder) holder).isDividerAllowedBelow()) {
132                if (mDividerCondition == DIVIDER_CONDITION_EITHER) {
133                    // Draw the divider without consulting the next item if we only
134                    // need permission for either above or below.
135                    return true;
136                }
137            } else if (mDividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) {
138                // Don't draw if the current view holder doesn't allow drawing below
139                // and the current theme requires permission for both the item below and above.
140                // Also, if this is the last item, there is no item below to ask permission
141                // for whether to draw a divider above, so don't draw it.
142                return false;
143            }
144        }
145        // Require permission from index below to draw the divider.
146        if (index < lastItemIndex) {
147            final RecyclerView.ViewHolder nextHolder =
148                    parent.findViewHolderForLayoutPosition(index + 1);
149            if ((nextHolder instanceof DividedViewHolder)
150                    && !((DividedViewHolder) nextHolder).isDividerAllowedAbove()) {
151                // Don't draw if the next view holder doesn't allow drawing above
152                return false;
153            }
154        }
155        return true;
156    }
157
158    /**
159     * Sets the drawable to be used as the divider.
160     */
161    public void setDivider(Drawable divider) {
162        if (divider != null) {
163            mDividerIntrinsicHeight = divider.getIntrinsicHeight();
164        } else {
165            mDividerIntrinsicHeight = 0;
166        }
167        mDivider = divider;
168    }
169
170    /**
171     * Gets the drawable currently used as the divider.
172     */
173    public Drawable getDivider() {
174        return mDivider;
175    }
176
177    /**
178     * Sets the divider height, in pixels.
179     */
180    public void setDividerHeight(int dividerHeight) {
181        mDividerHeight = dividerHeight;
182    }
183
184    /**
185     * Gets the divider height, in pixels.
186     */
187    public int getDividerHeight() {
188        return mDividerHeight;
189    }
190
191    /**
192     * Sets whether the divider needs permission from both the item view holder below
193     * and above from where the divider would draw itself or just needs permission from
194     * one or the other before drawing itself.
195     */
196    public void setDividerCondition(@DividerCondition int dividerCondition) {
197        mDividerCondition = dividerCondition;
198    }
199
200    /**
201     * Gets whether the divider needs permission from both the item view holder below
202     * and above from where the divider would draw itself or just needs permission from
203     * one or the other before drawing itself.
204     */
205    @DividerCondition
206    public int getDividerCondition() {
207        return mDividerCondition;
208    }
209}
210