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