/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.setupwizardlib; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import androidx.annotation.IntDef; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.RecyclerView; import android.view.View; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * An {@link androidx.recyclerview.widget.RecyclerView.ItemDecoration} for RecyclerView to draw * dividers between items. This ItemDecoration will draw the drawable specified by {@link * #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by default, * and the behavior of whether the divider is shown can be customized by subclassing {@link * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}. * *

Modified from v14 PreferenceFragment.DividerDecoration. */ public class DividerItemDecoration extends RecyclerView.ItemDecoration { /* static section */ /** * An interface to be implemented by a {@link RecyclerView.ViewHolder} which controls whether * dividers should be shown above and below that item. */ public interface DividedViewHolder { /** * Returns whether divider is allowed above this item. A divider will be shown only if both * items immediately above and below it allows this divider. */ boolean isDividerAllowedAbove(); /** * Returns whether divider is allowed below this item. A divider will be shown only if both * items immediately above and below it allows this divider. */ boolean isDividerAllowedBelow(); } @Retention(RetentionPolicy.SOURCE) @IntDef({DIVIDER_CONDITION_EITHER, DIVIDER_CONDITION_BOTH}) public @interface DividerCondition {} public static final int DIVIDER_CONDITION_EITHER = 0; public static final int DIVIDER_CONDITION_BOTH = 1; /** @deprecated Use {@link #DividerItemDecoration(android.content.Context)} */ @Deprecated public static DividerItemDecoration getDefault(Context context) { return new DividerItemDecoration(context); } /* non-static section */ private Drawable divider; private int dividerHeight; private int dividerIntrinsicHeight; @DividerCondition private int dividerCondition; public DividerItemDecoration() {} public DividerItemDecoration(Context context) { final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration); final Drawable divider = a.getDrawable(R.styleable.SuwDividerItemDecoration_android_listDivider); final int dividerHeight = a.getDimensionPixelSize(R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0); @DividerCondition final int dividerCondition = a.getInt( R.styleable.SuwDividerItemDecoration_suwDividerCondition, DIVIDER_CONDITION_EITHER); a.recycle(); setDivider(divider); setDividerHeight(dividerHeight); setDividerCondition(dividerCondition); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (divider == null) { return; } final int childCount = parent.getChildCount(); final int width = parent.getWidth(); final int dividerHeight = this.dividerHeight != 0 ? this.dividerHeight : dividerIntrinsicHeight; for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { final View view = parent.getChildAt(childViewIndex); if (shouldDrawDividerBelow(view, parent)) { final int top = (int) ViewCompat.getY(view) + view.getHeight(); divider.setBounds(0, top, width, top + dividerHeight); divider.draw(c); } } } @Override public void getItemOffsets( Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (shouldDrawDividerBelow(view, parent)) { outRect.bottom = dividerHeight != 0 ? dividerHeight : dividerIntrinsicHeight; } } private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); final int index = holder.getLayoutPosition(); final int lastItemIndex = parent.getAdapter().getItemCount() - 1; if (isDividerAllowedBelow(holder)) { if (dividerCondition == DIVIDER_CONDITION_EITHER) { // Draw the divider without consulting the next item if we only // need permission for either above or below. return true; } } else if (dividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) { // Don't draw if the current view holder doesn't allow drawing below // and the current theme requires permission for both the item below and above. // Also, if this is the last item, there is no item below to ask permission // for whether to draw a divider above, so don't draw it. return false; } // Require permission from index below to draw the divider. if (index < lastItemIndex) { final RecyclerView.ViewHolder nextHolder = parent.findViewHolderForLayoutPosition(index + 1); if (!isDividerAllowedAbove(nextHolder)) { // Don't draw if the next view holder doesn't allow drawing above return false; } } return true; } /** * Whether a divider is allowed above the view holder. The allowed values will be combined * according to {@link #getDividerCondition()}. The default implementation delegates to {@link * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows the * divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can override * this to give more information to decide whether a divider should be drawn. * * @return True if divider is allowed above this view holder. */ protected boolean isDividerAllowedAbove(RecyclerView.ViewHolder viewHolder) { return !(viewHolder instanceof DividedViewHolder) || ((DividedViewHolder) viewHolder).isDividerAllowedAbove(); } /** * Whether a divider is allowed below the view holder. The allowed values will be combined * according to {@link #getDividerCondition()}. The default implementation delegates to {@link * com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows the * divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can override * this to give more information to decide whether a divider should be drawn. * * @return True if divider is allowed below this view holder. */ protected boolean isDividerAllowedBelow(RecyclerView.ViewHolder viewHolder) { return !(viewHolder instanceof DividedViewHolder) || ((DividedViewHolder) viewHolder).isDividerAllowedBelow(); } /** Sets the drawable to be used as the divider. */ public void setDivider(Drawable divider) { if (divider != null) { dividerIntrinsicHeight = divider.getIntrinsicHeight(); } else { dividerIntrinsicHeight = 0; } this.divider = divider; } /** Gets the drawable currently used as the divider. */ public Drawable getDivider() { return divider; } /** Sets the divider height, in pixels. */ public void setDividerHeight(int dividerHeight) { this.dividerHeight = dividerHeight; } /** Gets the divider height, in pixels. */ public int getDividerHeight() { return dividerHeight; } /** * Sets whether the divider needs permission from both the item view holder below and above from * where the divider would draw itself or just needs permission from one or the other before * drawing itself. */ public void setDividerCondition(@DividerCondition int dividerCondition) { this.dividerCondition = dividerCondition; } /** * Gets whether the divider needs permission from both the item view holder below and above from * where the divider would draw itself or just needs permission from one or the other before * drawing itself. */ @DividerCondition public int getDividerCondition() { return dividerCondition; } }