/*
* Copyright 2017 Google Inc. All rights reserved.
*
* 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.google.android.flexbox;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
* {@link RecyclerView.ItemDecoration} implementation that can be used as item decorations between
* view holders within the {@link FlexboxLayoutManager}.
*
* Orientation for the decoration can be either of:
*
* - Horizontal (setOrientation(HORIZONTAL)
* - Vertical (setOrientation(VERTICAL)
* - Both orientation (setOrientation(BOTH)
*
.
* The default value is set to both.
*/
public class FlexboxItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL = 1;
public static final int VERTICAL = 1 << 1;
@SuppressWarnings("WeakerAccess")
public static final int BOTH = HORIZONTAL | VERTICAL;
private static final int[] LIST_DIVIDER_ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDrawable;
private int mOrientation;
public FlexboxItemDecoration(Context context) {
final TypedArray a = context.obtainStyledAttributes(LIST_DIVIDER_ATTRS);
mDrawable = a.getDrawable(0);
a.recycle();
setOrientation(BOTH);
}
/**
* Set the drawable used as the item decoration.
* If the drawable is not set, the default list divider is used as the
* item decoration.
*/
public void setDrawable(Drawable drawable) {
if (drawable == null) {
throw new IllegalArgumentException("Drawable cannot be null.");
}
mDrawable = drawable;
}
/**
* Set the orientation for the decoration.
* Orientation for the decoration can be either of:
*
* - Horizontal (setOrientation(HORIZONTAL)
* - Vertical (setOrientation(VERTICAL)
* - Both orientation (setOrientation(BOTH)
*
.
*/
public void setOrientation(int orientation) {
mOrientation = orientation;
}
@Override
public void onDraw(
@NonNull Canvas canvas,
@NonNull RecyclerView parent,
@NonNull RecyclerView.State state) {
drawHorizontalDecorations(canvas, parent);
drawVerticalDecorations(canvas, parent);
}
@Override
public void getItemOffsets(
@NonNull Rect outRect,
@NonNull View view,
RecyclerView parent,
@NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
if (position == 0) {
return;
}
if (!needsHorizontalDecoration() && !needsVerticalDecoration()) {
outRect.set(0, 0, 0, 0);
return;
}
FlexboxLayoutManager layoutManager = (FlexboxLayoutManager) parent.getLayoutManager();
List flexLines = layoutManager.getFlexLines();
int flexDirection = layoutManager.getFlexDirection();
setOffsetAlongMainAxis(outRect, position, layoutManager, flexLines, flexDirection);
setOffsetAlongCrossAxis(outRect, position, layoutManager, flexLines);
}
private void setOffsetAlongCrossAxis(Rect outRect, int position,
FlexboxLayoutManager layoutManager, List flexLines) {
if (flexLines.size() == 0) {
return;
}
int flexLineIndex = layoutManager.getPositionToFlexLineIndex(position);
if (flexLineIndex == 0) {
return;
}
if (layoutManager.isMainAxisDirectionHorizontal()) {
if (!needsHorizontalDecoration()) {
outRect.top = 0;
outRect.bottom = 0;
return;
}
outRect.top = mDrawable.getIntrinsicHeight();
outRect.bottom = 0;
} else {
if (!needsVerticalDecoration()) {
return;
}
if (layoutManager.isLayoutRtl()) {
outRect.right = mDrawable.getIntrinsicWidth();
outRect.left = 0;
} else {
outRect.left = mDrawable.getIntrinsicWidth();
outRect.right = 0;
}
}
}
private void setOffsetAlongMainAxis(Rect outRect, int position,
FlexboxLayoutManager layoutManager, List flexLines, int flexDirection) {
if (isFirstItemInLine(position, flexLines, layoutManager)) {
return;
}
if (layoutManager.isMainAxisDirectionHorizontal()) {
if (!needsVerticalDecoration()) {
outRect.left = 0;
outRect.right = 0;
return;
}
if (layoutManager.isLayoutRtl()) {
outRect.right = mDrawable.getIntrinsicWidth();
outRect.left = 0;
} else {
outRect.left = mDrawable.getIntrinsicWidth();
outRect.right = 0;
}
} else {
if (!needsHorizontalDecoration()) {
outRect.top = 0;
outRect.bottom = 0;
return;
}
if (flexDirection == FlexDirection.COLUMN_REVERSE) {
outRect.bottom = mDrawable.getIntrinsicHeight();
outRect.top = 0;
} else {
outRect.top = mDrawable.getIntrinsicHeight();
outRect.bottom = 0;
}
}
}
private void drawVerticalDecorations(Canvas canvas, RecyclerView parent) {
if (!needsVerticalDecoration()) {
return;
}
FlexboxLayoutManager layoutManager = (FlexboxLayoutManager) parent.getLayoutManager();
int parentTop = parent.getTop() - parent.getPaddingTop();
int parentBottom = parent.getBottom() + parent.getPaddingBottom();
int childCount = parent.getChildCount();
int flexDirection = layoutManager.getFlexDirection();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
int left, right;
if (layoutManager.isLayoutRtl()) {
left = child.getRight() + lp.rightMargin;
right = left + mDrawable.getIntrinsicWidth();
} else {
right = child.getLeft() - lp.leftMargin;
left = right - mDrawable.getIntrinsicWidth();
}
int top, bottom;
if (layoutManager.isMainAxisDirectionHorizontal()) {
top = child.getTop() - lp.topMargin;
bottom = child.getBottom() + lp.bottomMargin;
} else {
if (flexDirection == FlexDirection.COLUMN_REVERSE) {
bottom = child.getBottom() + lp.bottomMargin + mDrawable.getIntrinsicHeight();
bottom = Math.min(bottom, parentBottom);
top = child.getTop() - lp.topMargin;
} else {
top = child.getTop() - lp.topMargin - mDrawable.getIntrinsicHeight();
top = Math.max(top, parentTop);
bottom = child.getBottom() + lp.bottomMargin;
}
}
mDrawable.setBounds(left, top, right, bottom);
mDrawable.draw(canvas);
}
}
private void drawHorizontalDecorations(Canvas canvas, RecyclerView parent) {
if (!needsHorizontalDecoration()) {
return;
}
FlexboxLayoutManager layoutManager = (FlexboxLayoutManager) parent.getLayoutManager();
int flexDirection = layoutManager.getFlexDirection();
int parentLeft = parent.getLeft() - parent.getPaddingLeft();
int parentRight = parent.getRight() + parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
int top, bottom;
if (flexDirection == FlexDirection.COLUMN_REVERSE) {
top = child.getBottom() + lp.bottomMargin;
bottom = top + mDrawable.getIntrinsicHeight();
} else {
bottom = child.getTop() - lp.topMargin;
top = bottom - mDrawable.getIntrinsicHeight();
}
int left, right;
if (layoutManager.isMainAxisDirectionHorizontal()) {
if (layoutManager.isLayoutRtl()) {
right = child.getRight() + lp.rightMargin + mDrawable.getIntrinsicWidth();
right = Math.min(right, parentRight);
left = child.getLeft() - lp.leftMargin;
} else {
left = child.getLeft() - lp.leftMargin - mDrawable.getIntrinsicWidth();
left = Math.max(left, parentLeft);
right = child.getRight() + lp.rightMargin;
}
} else {
left = child.getLeft() - lp.leftMargin;
right = child.getRight() + lp.rightMargin;
}
mDrawable.setBounds(left, top, right, bottom);
mDrawable.draw(canvas);
}
}
private boolean needsHorizontalDecoration() {
return (mOrientation & HORIZONTAL) > 0;
}
private boolean needsVerticalDecoration() {
return (mOrientation & VERTICAL) > 0;
}
/**
* @return {@code true} if the given position is the first item in a flex line.
*/
private boolean isFirstItemInLine(int position, List flexLines,
FlexboxLayoutManager layoutManager) {
int flexLineIndex = layoutManager.getPositionToFlexLineIndex(position);
if (flexLineIndex != NO_POSITION &&
flexLineIndex < layoutManager.getFlexLinesInternal().size() &&
layoutManager.getFlexLinesInternal().get(flexLineIndex).mFirstIndex == position) {
return true;
}
if (position == 0) {
return true;
}
if (flexLines.size() == 0) {
return false;
}
// Check if the position is the "lastIndex + 1" of the last line in case the FlexLine which
// has the View, whose index is position is not included in the flexLines. (E.g. flexLines
// is being calculated
FlexLine lastLine = flexLines.get(flexLines.size() - 1);
return lastLine.mLastIndex == position - 1;
}
}