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