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.google.android.setupdesign; 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.google.android.setupdesign.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.SudDividerItemDecoration); 87 final Drawable divider = 88 a.getDrawable(R.styleable.SudDividerItemDecoration_android_listDivider); 89 final int dividerHeight = 90 a.getDimensionPixelSize(R.styleable.SudDividerItemDecoration_android_dividerHeight, 0); 91 @DividerCondition 92 final int dividerCondition = 93 a.getInt( 94 R.styleable.SudDividerItemDecoration_sudDividerCondition, 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.google.android.setupdesign.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.google.android.setupdesign.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