Home | History | Annotate | Download | only in setupdesign
      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      */
     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      */
     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
     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 
     83   public DividerItemDecoration() {}
     84 
     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
    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
    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 
    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    */
    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    */
    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. */
    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. */
    195   public Drawable getDivider() {
    196     return divider;
    197   }
    198 
    199   /** Sets the divider height, in pixels. */
    200   public void setDividerHeight(int dividerHeight) {
    201     this.dividerHeight = dividerHeight;
    202   }
    203 
    204   /** Gets the divider height, in pixels. */
    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    */
    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
    224   public int getDividerCondition() {
    225     return dividerCondition;
    226   }
    227 }
    228