6.7.0 - Alpha23 - 移除 "android.enableJetifier=true"; 模块化 Expandable Layout, Expandable RecyclerView, Recyclerview Flexible Divider

This commit is contained in:
SuperMonster003
2026-03-07 19:35:05 +08:00
parent 0cc28bdb6c
commit 528f3e59dc
47 changed files with 4673 additions and 75 deletions

View File

@@ -0,0 +1,22 @@
plugins {
id("org.autojs.build.versions")
id("org.autojs.build.jvm-convention")
id("com.android.library")
}
android {
namespace "com.github.aakira.expandablelayout"
compileSdkVersion versions.sdkVersionCompile
defaultConfig {
minSdkVersion versions.sdkVersionMin
targetSdkVersion versions.sdkVersionTarget
versionCode 12
versionName "1.6.0 Mod"
}
}
dependencies {
implementation libs.annotation
implementation libs.interpolator
}

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/eclipse/android/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1 @@
<manifest />

View File

@@ -0,0 +1,115 @@
package com.github.aakira.expandablelayout;
import android.animation.TimeInterpolator;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public interface ExpandableLayout {
/**
* Duration of expand animation
*/
int DEFAULT_DURATION = 300;
/**
* Visibility of the layout when the layout attaches
*/
boolean DEFAULT_EXPANDED = false;
/**
* Orientation of child views
*/
int HORIZONTAL = 0;
/**
* Orientation of child views
*/
int VERTICAL = 1;
/**
* Orientation of layout
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({HORIZONTAL, VERTICAL})
@interface Orientation {
}
/**
* Starts animation the state of the view to the inverse of its current state.
*/
void toggle();
/**
* Starts animation the state of the view to the inverse of its current state.
*
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
void toggle(final long duration, @Nullable final TimeInterpolator interpolator);
/**
* Starts expand animation.
*/
void expand();
/**
* Starts expand animation.
*
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
void expand(final long duration, @Nullable final TimeInterpolator interpolator);
/**
* Starts collapse animation.
*/
void collapse();
/**
* Starts collapse animation.
*
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
void collapse(final long duration, @Nullable final TimeInterpolator interpolator);
/**
* Sets the expandable layout listener.
*
* @param listener ExpandableLayoutListener
*/
void setListener(@NonNull final ExpandableLayoutListener listener);
/**
* Sets the length of the animation.
* The default duration is 300 milliseconds.
*
* @param duration
*/
void setDuration(final int duration);
/**
* Sets state of expanse.
*
* @param expanded The layout is visible if expanded is true
*/
void setExpanded(final boolean expanded);
/**
* Gets state of expanse.
*
* @return true if the layout is visible
*/
boolean isExpanded();
/**
* The time interpolator used in calculating the elapsed fraction of this animation. The
* interpolator determines whether the animation runs with linear or non-linear motion,
* such as acceleration and deceleration.
* The default value is {@link android.view.animation.AccelerateDecelerateInterpolator}
*
* @param interpolator
*/
void setInterpolator(@NonNull final TimeInterpolator interpolator);
}

View File

@@ -0,0 +1,35 @@
package com.github.aakira.expandablelayout;
public interface ExpandableLayoutListener {
/**
* Notifies the start of the animation.
* Sync from android.animation.Animator.AnimatorListener.onAnimationStart(Animator animation)
*/
void onAnimationStart();
/**
* Notifies the end of the animation.
* Sync from android.animation.Animator.AnimatorListener.onAnimationEnd(Animator animation)
*/
void onAnimationEnd();
/**
* Notifies the layout is going to open.
*/
void onPreOpen();
/**
* Notifies the layout is going to equal close size.
*/
void onPreClose();
/**
* Notifies the layout opened.
*/
void onOpened();
/**
* Notifies the layout size equal closed size.
*/
void onClosed();
}

View File

@@ -0,0 +1,45 @@
package com.github.aakira.expandablelayout;
public abstract class ExpandableLayoutListenerAdapter implements ExpandableLayoutListener {
/**
* {@inheritDoc}
*/
@Override
public void onAnimationStart() {
}
/**
* {@inheritDoc}
*/
@Override
public void onAnimationEnd() {
}
/**
* {@inheritDoc}
*/
@Override
public void onPreOpen() {
}
/**
* {@inheritDoc}
*/
@Override
public void onPreClose() {
}
/**
* {@inheritDoc}
*/
@Override
public void onOpened() {
}
/**
* {@inheritDoc}
*/
@Override
public void onClosed() {
}
}

View File

@@ -0,0 +1,565 @@
package com.github.aakira.expandablelayout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ExpandableLinearLayout extends LinearLayout implements ExpandableLayout {
private int duration;
private TimeInterpolator interpolator = new LinearInterpolator();
/**
* Default state of expanse
*
* @see #defaultChildIndex
* @see #defaultPosition
*/
private boolean defaultExpanded;
/**
* You cannot define {@link #defaultExpanded}, {@link #defaultChildIndex}
* and {@link #defaultPosition} at the same time.
* {@link #defaultPosition} has priority over {@link #defaultExpanded}
* and {@link #defaultChildIndex} if you set them at the same time.
* <p/>
* <p/>
* Priority
* {@link #defaultPosition} > {@link #defaultChildIndex} > {@link #defaultExpanded}
*/
private int defaultChildIndex;
private int defaultPosition;
/**
* The close position is width from left of layout if orientation is horizontal.
* The close position is height from top of layout if orientation is vertical.
*/
private int closePosition = 0;
private ExpandableLayoutListener listener;
private ExpandableSavedState savedState;
private boolean isExpanded;
private int layoutSize = 0;
private boolean inRecyclerView = false;
private boolean isArranged = false;
private boolean isCalculatedSize = false;
private boolean isAnimating = false;
/**
* State of expanse in recycler view.
*/
private boolean recyclerExpanded = false;
/**
* view size of children
**/
private List<Integer> childSizeList = new ArrayList<>();
private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
public ExpandableLinearLayout(final Context context) {
this(context, null);
}
public ExpandableLinearLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandableLinearLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ExpandableLinearLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr);
}
private void init(final Context context, final AttributeSet attrs, final int defStyleAttr) {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.expandableLayout, defStyleAttr, 0);
duration = a.getInteger(R.styleable.expandableLayout_ael_duration, DEFAULT_DURATION);
defaultExpanded = a.getBoolean(R.styleable.expandableLayout_ael_expanded, DEFAULT_EXPANDED);
defaultChildIndex = a.getInteger(R.styleable.expandableLayout_ael_defaultChildIndex,
Integer.MAX_VALUE);
defaultPosition = a.getDimensionPixelSize(R.styleable.expandableLayout_ael_defaultPosition,
Integer.MIN_VALUE);
final int interpolatorType = a.getInteger(R.styleable.expandableLayout_ael_interpolator,
Utils.LINEAR_INTERPOLATOR);
a.recycle();
interpolator = Utils.createInterpolator(interpolatorType);
isExpanded = defaultExpanded;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isCalculatedSize) {
// calculate a size of children
childSizeList.clear();
final int childCount = getChildCount();
if (childCount > 0) {
int sumSize = 0;
View view;
LayoutParams params;
for (int i = 0; i < childCount; i++) {
view = getChildAt(i);
params = (LayoutParams) view.getLayoutParams();
if (0 < i) {
sumSize = childSizeList.get(i - 1);
}
childSizeList.add(
(isVertical()
? view.getMeasuredHeight() + params.topMargin + params.bottomMargin
: view.getMeasuredWidth() + params.leftMargin + params.rightMargin
) + sumSize);
}
layoutSize = childSizeList.get(childCount - 1) +
(isVertical()
? getPaddingTop() + getPaddingBottom()
: getPaddingLeft() + getPaddingRight()
);
isCalculatedSize = true;
} else {
throw new IllegalStateException("The expandableLinearLayout must have at least one child");
}
}
if (isArranged) return;
// adjust default position if a user set a value.
if (!defaultExpanded) {
setLayoutSize(closePosition);
}
if (inRecyclerView) {
setLayoutSize(recyclerExpanded ? layoutSize : closePosition);
}
final int childNumbers = childSizeList.size();
if (childNumbers > defaultChildIndex && childNumbers > 0) {
moveChild(defaultChildIndex, 0, null);
}
if (defaultPosition > 0 && layoutSize >= defaultPosition && layoutSize > 0) {
move(defaultPosition, 0, null);
}
isArranged = true;
if (savedState == null) return;
setLayoutSize(savedState.getSize());
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable parcelable = super.onSaveInstanceState();
final ExpandableSavedState ss = new ExpandableSavedState(parcelable);
ss.setSize(getCurrentPosition());
return ss;
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (!(state instanceof ExpandableSavedState)) {
super.onRestoreInstanceState(state);
return;
}
final ExpandableSavedState ss = (ExpandableSavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
savedState = ss;
}
/**
* {@inheritDoc}
*/
@Override
public void setListener(@NonNull ExpandableLayoutListener listener) {
this.listener = listener;
}
/**
* {@inheritDoc}
*/
@Override
public void toggle() {
toggle(duration, interpolator);
}
/**
* {@inheritDoc}
*/
@Override
public void toggle(final long duration, final @Nullable TimeInterpolator interpolator) {
if (closePosition < getCurrentPosition()) {
collapse(duration, interpolator);
} else {
expand(duration, interpolator);
}
}
/**
* {@inheritDoc}
*/
@Override
public void expand() {
if (isAnimating) return;
createExpandAnimator(getCurrentPosition(), layoutSize, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void expand(final long duration, final @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
move(layoutSize, duration, interpolator);
return;
}
createExpandAnimator(getCurrentPosition(), layoutSize, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse() {
if (isAnimating) return;
createExpandAnimator(getCurrentPosition(), closePosition, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse(final long duration, final @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
move(closePosition, duration, interpolator);
return;
}
createExpandAnimator(getCurrentPosition(), closePosition, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void setDuration(final int duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
this.duration = duration;
}
/**
* {@inheritDoc}
*/
@Override
public void setExpanded(final boolean expanded) {
if (inRecyclerView) recyclerExpanded = expanded;
final int currentPosition = getCurrentPosition();
if ((expanded && (currentPosition == layoutSize))
|| (!expanded && currentPosition == closePosition)) return;
isExpanded = expanded;
setLayoutSize(expanded ? layoutSize : closePosition);
requestLayout();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isExpanded() {
return isExpanded;
}
/**
* {@inheritDoc}
*/
@Override
public void setInterpolator(@NonNull final TimeInterpolator interpolator) {
this.interpolator = interpolator;
}
/**
* Initializes this layout.
*/
public void initLayout() {
closePosition = 0;
layoutSize = 0;
isArranged = false;
isCalculatedSize = false;
savedState = null;
if (isVertical()) {
measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.UNSPECIFIED));
} else {
measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
}
}
/**
* @param position
*
* @see #move(int, long, TimeInterpolator)
*/
public void move(int position) {
move(position, duration, interpolator);
}
/**
* Moves to position.
* Sets 0 to duration if you want to move immediately.
*
* @param position
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void move(int position, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating || 0 > position || layoutSize < position) return;
if (duration <= 0) {
isExpanded = position > closePosition;
setLayoutSize(position);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentPosition(), position, duration,
interpolator == null ? this.interpolator : interpolator).start();
}
/**
* @param index child view index
*
* @see #moveChild(int, long, TimeInterpolator)
*/
public void moveChild(int index) {
moveChild(index, duration, interpolator);
}
/**
* Moves to bottom(VERTICAL) or right(HORIZONTAL) of child view
* Sets 0 to duration if you want to move immediately.
*
* @param index index child view index
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void moveChild(int index, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
final int destination = getChildPosition(index) +
(isVertical() ? getPaddingBottom() : getPaddingRight());
if (duration <= 0) {
isExpanded = destination > closePosition;
setLayoutSize(destination);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentPosition(), destination,
duration, interpolator == null ? this.interpolator : interpolator).start();
}
/**
* Gets the width from left of layout if orientation is horizontal.
* Gets the height from top of layout if orientation is vertical.
*
* @param index index of child view
*
* @return position from top or left
*/
public int getChildPosition(final int index) {
if (0 > index || childSizeList.size() <= index) {
throw new IllegalArgumentException("There aren't the view having this index.");
}
return childSizeList.get(index);
}
/**
* Gets the width from left of layout if orientation is horizontal.
* Gets the height from top of layout if orientation is vertical.
*
* @return
*
* @see #closePosition
*/
public int getClosePosition() {
return closePosition;
}
/**
* Sets the close position directly.
*
* @param position
*
* @see #closePosition
* @see #setClosePositionIndex(int)
*/
public void setClosePosition(final int position) {
this.closePosition = position;
}
/**
* Gets the current position.
*
* @return
*/
public int getCurrentPosition() {
return isVertical() ? getMeasuredHeight() : getMeasuredWidth();
}
/**
* Sets close position using index of child view.
*
* @param childIndex
*
* @see #closePosition
* @see #setClosePosition(int)
*/
public void setClosePositionIndex(final int childIndex) {
this.closePosition = getChildPosition(childIndex);
}
/**
* Set true if expandable layout is used in recycler view.
*
* @param inRecyclerView
*/
public void setInRecyclerView(final boolean inRecyclerView) {
this.inRecyclerView = inRecyclerView;
}
private boolean isVertical() {
return getOrientation() == LinearLayout.VERTICAL;
}
private void setLayoutSize(int size) {
if (isVertical()) {
getLayoutParams().height = size;
} else {
getLayoutParams().width = size;
}
}
/**
* Creates value animator.
* Expand the layout if {@param to} is bigger than {@param from}.
* Collapse the layout if {@param from} is bigger than {@param to}.
*
* @param from
* @param to
* @param duration
* @param interpolator
*
* @return
*/
private ValueAnimator createExpandAnimator(
final int from, final int to, final long duration, final TimeInterpolator interpolator) {
final ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(interpolator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator animator) {
if (isVertical()) {
getLayoutParams().height = (int) animator.getAnimatedValue();
} else {
getLayoutParams().width = (int) animator.getAnimatedValue();
}
requestLayout();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
isAnimating = true;
if (listener == null) return;
listener.onAnimationStart();
if (layoutSize == to) {
listener.onPreOpen();
return;
}
if (closePosition == to) {
listener.onPreClose();
}
}
@Override
public void onAnimationEnd(Animator animator) {
isAnimating = false;
isExpanded = to > closePosition;
if (listener == null) return;
listener.onAnimationEnd();
if (to == layoutSize) {
listener.onOpened();
return;
}
if (to == closePosition) {
listener.onClosed();
}
}
});
return valueAnimator;
}
/**
* Notify listeners
*/
private void notifyListeners() {
if (listener == null) return;
listener.onAnimationStart();
if (isExpanded) {
listener.onPreOpen();
} else {
listener.onPreClose();
}
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
}
listener.onAnimationEnd();
if (isExpanded) {
listener.onOpened();
} else {
listener.onClosed();
}
}
};
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
}

View File

@@ -0,0 +1,553 @@
package com.github.aakira.expandablelayout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ExpandableRelativeLayout extends RelativeLayout implements ExpandableLayout {
private int duration;
private TimeInterpolator interpolator = new LinearInterpolator();
private int orientation;
/**
* Default state of expanse
*
* @see #defaultChildIndex
* @see #defaultPosition
*/
private boolean defaultExpanded;
/**
* You cannot define {@link #defaultExpanded}, {@link #defaultChildIndex}
* and {@link #defaultPosition} at the same time.
* {@link #defaultPosition} has priority over {@link #defaultExpanded}
* and {@link #defaultChildIndex} if you set them at the same time.
* <p>
* <p>
* Priority
* {@link #defaultPosition} > {@link #defaultChildIndex} > {@link #defaultExpanded}
*/
private int defaultChildIndex;
private int defaultPosition;
/**
* The close position is width from left of layout if orientation is horizontal.
* The close position is height from top of layout if orientation is vertical.
*/
private int closePosition = 0;
private ExpandableLayoutListener listener;
private ExpandableSavedState savedState;
private boolean isExpanded;
private int layoutSize = 0;
private boolean isArranged = false;
private boolean isCalculatedSize = false;
private boolean isAnimating = false;
/**
* view size of children
**/
private List<Integer> childSizeList = new ArrayList<>();
/**
* view position top or left of children
**/
private List<Integer> childPositionList = new ArrayList<>();
private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
public ExpandableRelativeLayout(final Context context) {
this(context, null);
}
public ExpandableRelativeLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandableRelativeLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ExpandableRelativeLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr);
}
private void init(final Context context, final AttributeSet attrs, final int defStyleAttr) {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.expandableLayout, defStyleAttr, 0);
duration = a.getInteger(R.styleable.expandableLayout_ael_duration, DEFAULT_DURATION);
defaultExpanded = a.getBoolean(R.styleable.expandableLayout_ael_expanded, DEFAULT_EXPANDED);
orientation = a.getInteger(R.styleable.expandableLayout_ael_orientation, VERTICAL);
defaultChildIndex = a.getInteger(R.styleable.expandableLayout_ael_defaultChildIndex,
Integer.MAX_VALUE);
defaultPosition = a.getDimensionPixelSize(R.styleable.expandableLayout_ael_defaultPosition,
Integer.MIN_VALUE);
final int interpolatorType = a.getInteger(R.styleable.expandableLayout_ael_interpolator,
Utils.LINEAR_INTERPOLATOR);
a.recycle();
interpolator = Utils.createInterpolator(interpolatorType);
isExpanded = defaultExpanded;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (isCalculatedSize) return;
final int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
if (isVertical()) {
int measuredHeight = getMeasuredHeight();
super.onMeasure(widthMeasureSpec, measureSpec);
layoutSize = getMeasuredHeight();
setMeasuredDimension(getMeasuredWidth(), measuredHeight);
} else {
int measuredWidth = getMeasuredWidth();
super.onMeasure(measureSpec, heightMeasureSpec);
layoutSize = getMeasuredWidth();
setMeasuredDimension(measuredWidth, getMeasuredHeight());
}
// calculate a size of children
childSizeList.clear();
View view;
LayoutParams params;
for (int i = 0; i < getChildCount(); i++) {
view = getChildAt(i);
params = (LayoutParams) view.getLayoutParams();
childSizeList.add(isVertical()
? view.getMeasuredHeight() + params.topMargin + params.bottomMargin
: view.getMeasuredWidth() + params.leftMargin + params.rightMargin);
}
isCalculatedSize = true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (isArranged) return;
childPositionList.clear();
// calculate a top position of children
for (int i = 0; i < getChildCount(); i++) {
childPositionList.add((int) (isVertical() ? getChildAt(i).getY() : getChildAt(i).getX()));
}
// adjust default position if a user set a value.
if (!defaultExpanded) {
setLayoutSize(closePosition);
}
final int childNumbers = childSizeList.size();
if (childNumbers > defaultChildIndex && childNumbers > 0) {
moveChild(defaultChildIndex, 0, null);
}
if (defaultPosition > 0 && layoutSize >= defaultPosition && layoutSize > 0) {
move(defaultPosition, 0, null);
}
isArranged = true;
if (savedState == null) return;
setLayoutSize(savedState.getSize());
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable parcelable = super.onSaveInstanceState();
final ExpandableSavedState ss = new ExpandableSavedState(parcelable);
ss.setSize(getCurrentPosition());
return ss;
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (!(state instanceof ExpandableSavedState)) {
super.onRestoreInstanceState(state);
return;
}
final ExpandableSavedState ss = (ExpandableSavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
savedState = ss;
}
/**
* {@inheritDoc}
*/
@Override
public void setListener(@NonNull ExpandableLayoutListener listener) {
this.listener = listener;
}
/**
* {@inheritDoc}
*/
@Override
public void toggle() {
toggle(duration, interpolator);
}
/**
* {@inheritDoc}
*/
@Override
public void toggle(final long duration, final @Nullable TimeInterpolator interpolator) {
if (closePosition < getCurrentPosition()) {
collapse(duration, interpolator);
} else {
expand(duration, interpolator);
}
}
/**
* {@inheritDoc}
*/
@Override
public void expand() {
if (isAnimating) return;
createExpandAnimator(getCurrentPosition(), layoutSize, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void expand(final long duration, final @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
move(layoutSize, duration, interpolator);
return;
}
createExpandAnimator(getCurrentPosition(), layoutSize, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse() {
if (isAnimating) return;
createExpandAnimator(getCurrentPosition(), closePosition, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse(final long duration, final @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
move(closePosition, duration, interpolator);
return;
}
createExpandAnimator(getCurrentPosition(), closePosition, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void setDuration(final int duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
this.duration = duration;
}
/**
* {@inheritDoc}
*/
@Override
public void setExpanded(boolean expanded) {
final int currentPosition = getCurrentPosition();
if ((expanded && (currentPosition == layoutSize))
|| (!expanded && currentPosition == closePosition)) return;
isExpanded = expanded;
setLayoutSize(expanded ? layoutSize : closePosition);
requestLayout();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isExpanded() {
return isExpanded;
}
/**
* {@inheritDoc}
*/
@Override
public void setInterpolator(@NonNull final TimeInterpolator interpolator) {
this.interpolator = interpolator;
}
/**
* @param position
*
* @see #move(int, long, TimeInterpolator)
*/
public void move(int position) {
move(position, duration, interpolator);
}
/**
* Moves to position.
* Sets 0 to duration if you want to move immediately.
*
* @param position
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void move(int position, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating || 0 > position || layoutSize < position) return;
if (duration <= 0) {
isExpanded = position > closePosition;
setLayoutSize(position);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentPosition(), position, duration,
interpolator == null ? this.interpolator : interpolator).start();
}
/**
* @param index child view index
*
* @see #moveChild(int, long, TimeInterpolator)
*/
public void moveChild(int index) {
moveChild(index, duration, interpolator);
}
/**
* Moves to bottom(VERTICAL) or right(HORIZONTAL) of child view
* Sets 0 to duration if you want to move immediately.
*
* @param index index child view index
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void moveChild(int index, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
final int destination = getChildPosition(index) +
(isVertical() ? getPaddingBottom() : getPaddingRight());
if (duration <= 0) {
isExpanded = destination > closePosition;
setLayoutSize(destination);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentPosition(), destination,
duration, interpolator == null ? this.interpolator : interpolator).start();
}
/**
* Sets orientation of expanse animation.
*
* @param orientation Set 0 if orientation is horizontal, 1 if orientation is vertical
*/
public void setOrientation(@Orientation final int orientation) {
this.orientation = orientation;
}
/**
* Gets the width from left of layout if orientation is horizontal.
* Gets the height from top of layout if orientation is vertical.
*
* @param index index of child view
*
* @return position from top or left
*/
public int getChildPosition(final int index) {
if (0 > index || childSizeList.size() <= index) {
throw new IllegalArgumentException("There aren't the view having this index.");
}
return childPositionList.get(index) + childSizeList.get(index);
}
/**
* Gets the width from left of layout if orientation is horizontal.
* Gets the height from top of layout if orientation is vertical.
*
* @return
*
* @see #closePosition
*/
public int getClosePosition() {
return closePosition;
}
/**
* Sets the close position directly.
*
* @param position
*
* @see #closePosition
* @see #setClosePositionIndex(int)
*/
public void setClosePosition(final int position) {
this.closePosition = position;
}
/**
* Gets the current position.
*
* @return
*/
public int getCurrentPosition() {
return isVertical() ? getMeasuredHeight() : getMeasuredWidth();
}
/**
* Sets close position using index of child view.
*
* @param childIndex
*
* @see #closePosition
* @see #setClosePosition(int)
*/
public void setClosePositionIndex(final int childIndex) {
this.closePosition = getChildPosition(childIndex);
}
private boolean isVertical() {
return orientation == VERTICAL;
}
private void setLayoutSize(int size) {
if (isVertical()) {
getLayoutParams().height = size;
} else {
getLayoutParams().width = size;
}
}
/**
* Creates value animator.
* Expand the layout if {@param to} is bigger than {@param from}.
* Collapse the layout if {@param from} is bigger than {@param to}.
*
* @param from
* @param to
* @param duration
* @param interpolator
*
* @return
*/
private ValueAnimator createExpandAnimator(
final int from, final int to, final long duration, final TimeInterpolator interpolator) {
final ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(interpolator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator animator) {
if (isVertical()) {
getLayoutParams().height = (int) animator.getAnimatedValue();
} else {
getLayoutParams().width = (int) animator.getAnimatedValue();
}
requestLayout();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
isAnimating = true;
if (listener == null) return;
listener.onAnimationStart();
if (layoutSize == to) {
listener.onPreOpen();
return;
}
if (closePosition == to) {
listener.onPreClose();
}
}
@Override
public void onAnimationEnd(Animator animator) {
isAnimating = false;
isExpanded = to > closePosition;
if (listener == null) return;
listener.onAnimationEnd();
if (to == layoutSize) {
listener.onOpened();
return;
}
if (to == closePosition) {
listener.onClosed();
}
}
});
return valueAnimator;
}
/**
* Notify listeners
*/
private void notifyListeners() {
if (listener == null) return;
listener.onAnimationStart();
if (isExpanded) {
listener.onPreOpen();
} else {
listener.onPreClose();
}
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
}
listener.onAnimationEnd();
if (isExpanded) {
listener.onOpened();
} else {
listener.onClosed();
}
}
};
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
}

View File

@@ -0,0 +1,54 @@
package com.github.aakira.expandablelayout;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
public class ExpandableSavedState extends View.BaseSavedState {
private int size;
private float weight;
ExpandableSavedState(Parcelable superState) {
super(superState);
}
private ExpandableSavedState(Parcel in) {
super(in);
this.size = in.readInt();
this.weight = in.readFloat();
}
public int getSize() {
return this.size;
}
public void setSize(int size) {
this.size = size;
}
public float getWeight() {
return this.weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(this.size);
out.writeFloat(this.weight);
}
public static final Creator<ExpandableSavedState> CREATOR =
new Creator<ExpandableSavedState>() {
public ExpandableSavedState createFromParcel(Parcel in) {
return new ExpandableSavedState(in);
}
public ExpandableSavedState[] newArray(int size) {
return new ExpandableSavedState[size];
}
};
}

View File

@@ -0,0 +1,385 @@
package com.github.aakira.expandablelayout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class ExpandableWeightLayout extends RelativeLayout implements ExpandableLayout {
private int duration;
private TimeInterpolator interpolator = new LinearInterpolator();
private boolean defaultExpanded;
private ExpandableLayoutListener listener;
private ExpandableSavedState savedState;
private boolean isExpanded;
private float layoutWeight = 0.0f;
private boolean isArranged = false;
private boolean isCalculatedSize = false;
private boolean isAnimating = false;
private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
public ExpandableWeightLayout(final Context context) {
this(context, null);
}
public ExpandableWeightLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandableWeightLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ExpandableWeightLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr);
}
private void init(final Context context, final AttributeSet attrs, final int defStyleAttr) {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.expandableLayout, defStyleAttr, 0);
duration = a.getInteger(R.styleable.expandableLayout_ael_duration, DEFAULT_DURATION);
defaultExpanded = a.getBoolean(R.styleable.expandableLayout_ael_expanded, DEFAULT_EXPANDED);
final int interpolatorType = a.getInteger(R.styleable.expandableLayout_ael_interpolator,
Utils.LINEAR_INTERPOLATOR);
a.recycle();
interpolator = Utils.createInterpolator(interpolatorType);
isExpanded = defaultExpanded;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Check this layout using the attribute of weight
if (!(getLayoutParams() instanceof LinearLayout.LayoutParams)) {
throw new AssertionError("You must arrange in LinearLayout.");
}
if (0 >= getCurrentWeight()) throw new AssertionError("You must set a weight than 0.");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isCalculatedSize) {
layoutWeight = getCurrentWeight();
isCalculatedSize = true;
}
if (isArranged) return;
setWeight(defaultExpanded ? layoutWeight : 0);
isArranged = true;
if (savedState == null) return;
setWeight(savedState.getWeight());
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable parcelable = super.onSaveInstanceState();
final ExpandableSavedState ss = new ExpandableSavedState(parcelable);
ss.setWeight(getCurrentWeight());
return ss;
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (!(state instanceof ExpandableSavedState)) {
super.onRestoreInstanceState(state);
return;
}
final ExpandableSavedState ss = (ExpandableSavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
savedState = ss;
}
/**
* {@inheritDoc}
*/
@Override
public void setListener(@NonNull ExpandableLayoutListener listener) {
this.listener = listener;
}
/**
* {@inheritDoc}
*/
@Override
public void toggle() {
toggle(duration, interpolator);
}
/**
* {@inheritDoc}
*/
@Override
public void toggle(final long duration, @Nullable final TimeInterpolator interpolator) {
if (0 < getCurrentWeight()) {
collapse(duration, interpolator);
} else {
expand(duration, interpolator);
}
}
/**
* {@inheritDoc}
*/
@Override
public void expand() {
if (isAnimating) return;
createExpandAnimator(0, layoutWeight, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void expand(final long duration, @Nullable final TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
isExpanded = true;
setWeight(layoutWeight);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentWeight(), layoutWeight, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse() {
if (isAnimating) return;
createExpandAnimator(getCurrentWeight(), 0, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse(final long duration, @Nullable final TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
isExpanded = false;
setWeight(0);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentWeight(), 0, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void setDuration(@NonNull final int duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
this.duration = duration;
}
/**
* {@inheritDoc}
*/
@Override
public void setExpanded(boolean expanded) {
final float currentWeight = getCurrentWeight();
if ((expanded && (currentWeight == layoutWeight))
|| (!expanded && currentWeight == 0)) return;
isExpanded = expanded;
setWeight(expanded ? layoutWeight : 0);
requestLayout();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isExpanded() {
return isExpanded;
}
/**
* {@inheritDoc}
*/
@Override
public void setInterpolator(@NonNull final TimeInterpolator interpolator) {
this.interpolator = interpolator;
}
/**
* Sets weight of expandable layout.
*
* @param expandWeight expand to this weight by {@link #expand()}
*/
public void setExpandWeight(final float expandWeight) {
layoutWeight = expandWeight;
}
/**
* Gets current weight of expandable layout.
*
* @return weight
*/
public float getCurrentWeight() {
return ((LinearLayout.LayoutParams) getLayoutParams()).weight;
}
/**
* @param weight
*
* @see #move(float, long, TimeInterpolator)
*/
public void move(float weight) {
move(weight, duration, interpolator);
}
/**
* Change to weight.
* Sets 0 to duration if you want to move immediately.
*
* @param weight
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void move(float weight, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0L) {
isExpanded = weight > 0;
setWeight(weight);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentWeight(), weight, duration, interpolator).start();
}
/**
* Creates value animator.
* Expand the layout if @param.to is bigger than @param.from.
* Collapse the layout if @param.from is bigger than @param.to.
*
* @param from
* @param to
* @param duration
* @param interpolator TimeInterpolator
*
* @return
*/
private ValueAnimator createExpandAnimator(final float from, final float to, final long duration,
@Nullable final TimeInterpolator interpolator) {
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(from, to);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(interpolator == null ? this.interpolator : interpolator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator animation) {
setWeight((float) animation.getAnimatedValue());
requestLayout();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
isAnimating = true;
if (listener == null) return;
listener.onAnimationStart();
if (layoutWeight == to) {
listener.onPreOpen();
return;
}
if (0 == to) {
listener.onPreClose();
}
}
@Override
public void onAnimationEnd(Animator animation) {
isAnimating = false;
isExpanded = to > 0;
if (listener == null) return;
listener.onAnimationEnd();
if (to == layoutWeight) {
listener.onOpened();
return;
}
if (to == 0) {
listener.onClosed();
}
}
});
return valueAnimator;
}
private void setWeight(final float weight) {
((LinearLayout.LayoutParams) getLayoutParams()).weight = weight;
}
/**
* Notify listeners
*/
private void notifyListeners() {
if (listener == null) return;
listener.onAnimationStart();
if (isExpanded) {
listener.onPreOpen();
} else {
listener.onPreClose();
}
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
}
listener.onAnimationEnd();
if (isExpanded) {
listener.onOpened();
} else {
listener.onClosed();
}
}
};
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
}

View File

@@ -0,0 +1,65 @@
package com.github.aakira.expandablelayout;
import android.animation.TimeInterpolator;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import androidx.annotation.IntRange;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
public class Utils {
public static final int ACCELERATE_DECELERATE_INTERPOLATOR = 0;
public static final int ACCELERATE_INTERPOLATOR = 1;
public static final int ANTICIPATE_INTERPOLATOR = 2;
public static final int ANTICIPATE_OVERSHOOT_INTERPOLATOR = 3;
public static final int BOUNCE_INTERPOLATOR = 4;
public static final int DECELERATE_INTERPOLATOR = 5;
public static final int FAST_OUT_LINEAR_IN_INTERPOLATOR = 6;
public static final int FAST_OUT_SLOW_IN_INTERPOLATOR = 7;
public static final int LINEAR_INTERPOLATOR = 8;
public static final int LINEAR_OUT_SLOW_IN_INTERPOLATOR = 9;
public static final int OVERSHOOT_INTERPOLATOR = 10;
/**
* Creates interpolator.
*
* @param interpolatorType
* @return
*/
public static TimeInterpolator createInterpolator(@IntRange(from = 0, to = 10) final int interpolatorType) {
switch (interpolatorType) {
case ACCELERATE_DECELERATE_INTERPOLATOR:
return new AccelerateDecelerateInterpolator();
case ACCELERATE_INTERPOLATOR:
return new AccelerateInterpolator();
case ANTICIPATE_INTERPOLATOR:
return new AnticipateInterpolator();
case ANTICIPATE_OVERSHOOT_INTERPOLATOR:
return new AnticipateOvershootInterpolator();
case BOUNCE_INTERPOLATOR:
return new BounceInterpolator();
case DECELERATE_INTERPOLATOR:
return new DecelerateInterpolator();
case FAST_OUT_LINEAR_IN_INTERPOLATOR:
return new FastOutLinearInInterpolator();
case FAST_OUT_SLOW_IN_INTERPOLATOR:
return new FastOutSlowInInterpolator();
case LINEAR_INTERPOLATOR:
return new LinearInterpolator();
case LINEAR_OUT_SLOW_IN_INTERPOLATOR:
return new LinearOutSlowInInterpolator();
case OVERSHOOT_INTERPOLATOR:
return new OvershootInterpolator();
default:
return new LinearInterpolator();
}
}
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="expandableLayout">
<attr name="ael_duration" format="integer" />
<attr name="ael_expanded" format="boolean" />
<attr name="ael_defaultChildIndex" format="integer" />
<attr name="ael_defaultPosition" format="dimension" />
<attr name="ael_orientation" format="enum">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
<attr name="ael_interpolator" format="enum">
<enum name="accelerateDecelerate" value="0" />
<enum name="accelerate" value="1" />
<enum name="anticipate" value="2" />
<enum name="anticipateOvershoot" value="3" />
<enum name="bounce" value="4" />
<enum name="decelerate" value="5" />
<enum name="fastOutLinearIn" value="6" />
<enum name="fastOutSlowIn" value="7" />
<enum name="linear" value="8" />
<enum name="linearOutSlowIn" value="9" />
<enum name="overshoot" value="10" />
</attr>
</declare-styleable>
</resources>

View File

@@ -0,0 +1,21 @@
plugins {
id("org.autojs.build.versions")
id("org.autojs.build.jvm-convention")
id("com.android.library")
}
android {
namespace "com.bignerdranch.expandablerecyclerview"
compileSdkVersion versions.sdkVersionCompile
defaultConfig {
minSdkVersion versions.sdkVersionMin
targetSdkVersion versions.sdkVersionTarget
versionName "3.0.0-RC1 Mod"
}
}
dependencies {
implementation libs.annotation
implementation libs.recyclerview
}

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/eclipse/android/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1 @@
<manifest />

View File

@@ -0,0 +1,70 @@
package com.bignerdranch.expandablerecyclerview;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
/**
* ViewHolder for a child list item.
* <p>
* The user should extend this class and implement as they wish for their
* child list item.
*/
public class ChildViewHolder<C> extends RecyclerView.ViewHolder {
C mChild;
ExpandableRecyclerAdapter mExpandableAdapter;
/**
* Default constructor.
*
* @param itemView The {@link View} being hosted in this ViewHolder
*/
public ChildViewHolder(@NonNull View itemView) {
super(itemView);
}
/**
* @return the childListItem associated with this view holder
*/
@UiThread
public C getChild() {
return mChild;
}
/**
* Returns the adapter position of the Parent associated with this ChildViewHolder
*
* @return The adapter position of the Parent if it still exists in the adapter.
* RecyclerView.NO_POSITION if item has been removed from the adapter,
* RecyclerView.Adapter.notifyDataSetChanged() has been called after the last
* layout pass or the ViewHolder has already been recycled.
*/
@UiThread
public int getParentAdapterPosition() {
int flatPosition = getAdapterPosition();
if (mExpandableAdapter == null || flatPosition == RecyclerView.NO_POSITION) {
return RecyclerView.NO_POSITION;
}
return mExpandableAdapter.getNearestParentPosition(flatPosition);
}
/**
* Returns the adapter position of the Child associated with this ChildViewHolder
*
* @return The adapter position of the Child if it still exists in the adapter.
* RecyclerView.NO_POSITION if item has been removed from the adapter,
* RecyclerView.Adapter.notifyDataSetChanged() has been called after the last
* layout pass or the ViewHolder has already been recycled.
*/
@UiThread
public int getChildAdapterPosition() {
int flatPosition = getAdapterPosition();
if (mExpandableAdapter == null || flatPosition == RecyclerView.NO_POSITION) {
return RecyclerView.NO_POSITION;
}
return mExpandableAdapter.getChildPosition(flatPosition);
}
}

View File

@@ -0,0 +1,201 @@
package com.bignerdranch.expandablerecyclerview;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import com.bignerdranch.expandablerecyclerview.model.Parent;
/**
* ViewHolder for a {@link Parent}
* Keeps track of expanded state and holds callbacks which can be used to
* trigger expansion-based events.
*
* @author Ryan Brooks
* @version 1.0
* @since 5/27/2015
*/
public class ParentViewHolder<P extends Parent<C>, C> extends RecyclerView.ViewHolder implements View.OnClickListener {
@Nullable
private ParentViewHolderExpandCollapseListener mParentViewHolderExpandCollapseListener;
private boolean mExpanded;
P mParent;
ExpandableRecyclerAdapter mExpandableAdapter;
/**
* Empowers {@link com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter}
* implementations to be notified of expand/collapse state change events.
*/
interface ParentViewHolderExpandCollapseListener {
/**
* Called when a parent is expanded.
*
* @param flatParentPosition The index of the parent in the list being expanded
*/
@UiThread
void onParentExpanded(int flatParentPosition);
/**
* Called when a parent is collapsed.
*
* @param flatParentPosition The index of the parent in the list being collapsed
*/
@UiThread
void onParentCollapsed(int flatParentPosition);
}
/**
* Default constructor.
*
* @param itemView The {@link View} being hosted in this ViewHolder
*/
@UiThread
public ParentViewHolder(@NonNull View itemView) {
super(itemView);
mExpanded = false;
}
/**
* @return the Parent associated with this ViewHolder
*/
@UiThread
public P getParent() {
return mParent;
}
/**
* Returns the adapter position of the Parent associated with this ParentViewHolder
*
* @return The adapter position of the Parent if it still exists in the adapter.
* RecyclerView.NO_POSITION if item has been removed from the adapter,
* RecyclerView.Adapter.notifyDataSetChanged() has been called after the last
* layout pass or the ViewHolder has already been recycled.
*/
@UiThread
public int getParentAdapterPosition() {
int flatPosition = getAdapterPosition();
if (flatPosition == RecyclerView.NO_POSITION) {
return flatPosition;
}
return mExpandableAdapter.getNearestParentPosition(flatPosition);
}
/**
* Sets a {@link android.view.View.OnClickListener} on the entire parent
* view to trigger expansion.
*/
@UiThread
public void setMainItemClickToExpand() {
itemView.setOnClickListener(this);
}
/**
* Returns expanded state for the {@link Parent}
* corresponding to this {@link ParentViewHolder}.
*
* @return true if expanded, false if not
*/
@UiThread
public boolean isExpanded() {
return mExpanded;
}
/**
* Setter method for expanded state, used for initialization of expanded state.
* changes to the state are given in {@link #onExpansionToggled(boolean)}
*
* @param expanded true if expanded, false if not
*/
@UiThread
public void setExpanded(boolean expanded) {
mExpanded = expanded;
}
/**
* Callback triggered when expansion state is changed, but not during
* initialization.
* <p>
* Useful for implementing animations on expansion.
*
* @param expanded true if view is expanded before expansion is toggled,
* false if not
*/
@UiThread
public void onExpansionToggled(boolean expanded) {
}
/**
* Setter for the {@link ParentViewHolderExpandCollapseListener} implemented in
* {@link com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter}.
*
* @param parentViewHolderExpandCollapseListener The {@link ParentViewHolderExpandCollapseListener} to set on the {@link ParentViewHolder}
*/
@UiThread
void setParentViewHolderExpandCollapseListener(ParentViewHolderExpandCollapseListener parentViewHolderExpandCollapseListener) {
mParentViewHolderExpandCollapseListener = parentViewHolderExpandCollapseListener;
}
/**
* {@link android.view.View.OnClickListener} to listen for click events on
* the entire parent {@link View}.
* <p>
* Only registered if {@link #shouldItemViewClickToggleExpansion()} is true.
*
* @param v The {@link View} that is the trigger for expansion
*/
@Override
@UiThread
public void onClick(View v) {
if (mExpanded) {
collapseView();
} else {
expandView();
}
}
/**
* Used to determine whether a click in the entire parent {@link View}
* should trigger row expansion.
* <p>
* If you return false, you can call {@link #expandView()} to trigger an
* expansion in response to a another event or {@link #collapseView()} to
* trigger a collapse.
*
* @return true to set an {@link android.view.View.OnClickListener} on the item view
*/
@UiThread
public boolean shouldItemViewClickToggleExpansion() {
return true;
}
/**
* Triggers expansion of the parent.
*/
@UiThread
protected void expandView() {
setExpanded(true);
onExpansionToggled(false);
if (mParentViewHolderExpandCollapseListener != null) {
mParentViewHolderExpandCollapseListener.onParentExpanded(getAdapterPosition());
}
}
/**
* Triggers collapse of the parent.
*/
@UiThread
protected void collapseView() {
setExpanded(false);
onExpansionToggled(true);
if (mParentViewHolderExpandCollapseListener != null) {
mParentViewHolderExpandCollapseListener.onParentCollapsed(getAdapterPosition());
}
}
}

View File

@@ -0,0 +1,125 @@
package com.bignerdranch.expandablerecyclerview.model;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
/**
* Wrapper used to link metadata with a list item.
*
* @param <P> Parent list item
* @param <C> Child list item
*/
public class ExpandableWrapper<P extends Parent<C>, C> {
private P mParent;
private C mChild;
private boolean mWrappedParent;
private boolean mExpanded;
private List<ExpandableWrapper<P, C>> mWrappedChildList;
/**
* Constructor to wrap a parent object of type {@link P}.
*
* @param parent The parent object to wrap
*/
public ExpandableWrapper(@NonNull P parent) {
mParent = parent;
mWrappedParent = true;
mExpanded = false;
mWrappedChildList = generateChildItemList(parent);
}
/**
* Constructor to wrap a child object of type {@link C}
*
* @param child The child object to wrap
*/
public ExpandableWrapper(@NonNull C child) {
mChild = child;
mWrappedParent = false;
mExpanded = false;
}
public P getParent() {
return mParent;
}
public void setParent(@NonNull P parent) {
mParent = parent;
mWrappedChildList = generateChildItemList(parent);
}
public C getChild() {
return mChild;
}
public boolean isExpanded() {
return mExpanded;
}
public void setExpanded(boolean expanded) {
mExpanded = expanded;
}
public boolean isParent() {
return mWrappedParent;
}
/**
* @return The initial expanded state of a parent
* @throws IllegalStateException If a parent isn't being wrapped
*/
public boolean isParentInitiallyExpanded() {
if (!mWrappedParent) {
throw new IllegalStateException("Parent not wrapped");
}
return mParent.isInitiallyExpanded();
}
/**
* @return The list of children of a parent
* @throws IllegalStateException If a parent isn't being wrapped
*/
public List<ExpandableWrapper<P, C>> getWrappedChildList() {
if (!mWrappedParent) {
throw new IllegalStateException("Parent not wrapped");
}
return mWrappedChildList;
}
private List<ExpandableWrapper<P, C>> generateChildItemList(P parentListItem) {
List<ExpandableWrapper<P, C>> childItemList = new ArrayList<>();
for (C child : parentListItem.getChildList()) {
childItemList.add(new ExpandableWrapper<P, C>(child));
}
return childItemList;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ExpandableWrapper<?, ?> that = (ExpandableWrapper<?, ?>) o;
if (mParent != null ? !mParent.equals(that.mParent) : that.mParent != null)
return false;
return mChild != null ? mChild.equals(that.mChild) : that.mChild == null;
}
@Override
public int hashCode() {
int result = mParent != null ? mParent.hashCode() : 0;
result = 31 * result + (mChild != null ? mChild.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,26 @@
package com.bignerdranch.expandablerecyclerview.model;
import java.util.List;
/**
* Interface for implementing required methods in a parent.
*/
public interface Parent<C> {
/**
* Getter for the list of this parent's child items.
* <p>
* If list is empty, the parent has no children.
*
* @return A {@link List} of the children of this {@link Parent}
*/
List<C> getChildList();
/**
* Getter used to determine if this {@link Parent}'s
* {@link android.view.View} should show up initially as expanded.
*
* @return true if expanded, false if not
*/
boolean isInitiallyExpanded();
}

View File

@@ -0,0 +1,32 @@
package com.bignerdranch.expandablerecyclerview.model;
import java.util.List;
/**
* Simple implementation of the ParentListItem interface,
* by default all items are not initially expanded.
*
* @param <C> Type of the Child Items held by the Parent.
*/
public class SimpleParent<C> implements Parent<C> {
private List<C> mChildList;
protected SimpleParent(List<C> childItemList) {
mChildList = childItemList;
}
@Override
public boolean isInitiallyExpanded() {
return false;
}
@Override
public List<C> getChildList() {
return mChildList;
}
public void setChildList(List<C> childList) {
mChildList = childList;
}
}

View File

@@ -6,7 +6,7 @@ plugins {
android {
namespace = "com.wdullaer.materialdatetimepicker"
version = "4.2.3 (54)"
version = "4.2.3 (54) Mod"
compileSdk = versions.sdkVersionCompile
defaultConfig {

View File

@@ -6,7 +6,7 @@ plugins {
android {
namespace = "com.afollestad.materialdialogs"
version = "0.9.6.0 (179)"
version = "0.9.6.0 (179) Mod"
compileSdk = versions.sdkVersionCompile
@@ -27,8 +27,8 @@ repositories {
}
dependencies {
implementation(libs.appcompat.v7)
implementation(libs.support.annotations)
implementation(libs.recyclerview.v7)
implementation(libs.appcompat)
implementation(libs.annotation)
implementation(libs.recyclerview)
implementation(libs.materialprogressbar)
}

View File

@@ -0,0 +1,20 @@
plugins {
id("org.autojs.build.versions")
id("org.autojs.build.jvm-convention")
id("com.android.library")
}
android {
namespace "com.yqritc.recyclerviewflexibledivider"
compileSdkVersion versions.sdkVersionCompile
defaultConfig {
minSdkVersion versions.sdkVersionMin
targetSdkVersion versions.sdkVersionTarget
versionCode 15
versionName "1.4.0 Mod"
}
}
dependencies {
implementation libs.recyclerview
}

View File

@@ -0,0 +1 @@
<manifest package="com.yqritc.recyclerviewflexibledivider"></manifest>

View File

@@ -0,0 +1,439 @@
package com.yqritc.recyclerviewflexibledivider;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.DrawableRes;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by yqritc on 2015/01/08.
*/
public abstract class FlexibleDividerDecoration extends RecyclerView.ItemDecoration {
private static final int DEFAULT_SIZE = 2;
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
protected enum DividerType {
DRAWABLE, PAINT, COLOR
}
protected DividerType mDividerType = DividerType.DRAWABLE;
protected VisibilityProvider mVisibilityProvider;
protected PaintProvider mPaintProvider;
protected ColorProvider mColorProvider;
protected DrawableProvider mDrawableProvider;
protected SizeProvider mSizeProvider;
protected boolean mShowLastDivider;
protected boolean mPositionInsideItem;
private Paint mPaint;
protected FlexibleDividerDecoration(Builder builder) {
if (builder.mPaintProvider != null) {
mDividerType = DividerType.PAINT;
mPaintProvider = builder.mPaintProvider;
} else if (builder.mColorProvider != null) {
mDividerType = DividerType.COLOR;
mColorProvider = builder.mColorProvider;
mPaint = new Paint();
setSizeProvider(builder);
} else {
mDividerType = DividerType.DRAWABLE;
if (builder.mDrawableProvider == null) {
TypedArray a = builder.mContext.obtainStyledAttributes(ATTRS);
final Drawable divider = a.getDrawable(0);
a.recycle();
mDrawableProvider = new DrawableProvider() {
@Override
public Drawable drawableProvider(int position, RecyclerView parent) {
return divider;
}
};
} else {
mDrawableProvider = builder.mDrawableProvider;
}
mSizeProvider = builder.mSizeProvider;
}
mVisibilityProvider = builder.mVisibilityProvider;
mShowLastDivider = builder.mShowLastDivider;
mPositionInsideItem = builder.mPositionInsideItem;
}
private void setSizeProvider(Builder builder) {
mSizeProvider = builder.mSizeProvider;
if (mSizeProvider == null) {
mSizeProvider = new SizeProvider() {
@Override
public int dividerSize(int position, RecyclerView parent) {
return DEFAULT_SIZE;
}
};
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
RecyclerView.Adapter adapter = parent.getAdapter();
if (adapter == null) {
return;
}
int itemCount = adapter.getItemCount();
int lastDividerOffset = getLastDividerOffset(parent);
int validChildCount = parent.getChildCount();
int lastChildPosition = -1;
for (int i = 0; i < validChildCount; i++) {
View child = parent.getChildAt(i);
int childPosition = parent.getChildAdapterPosition(child);
if (childPosition < lastChildPosition) {
// Avoid remaining divider when animation starts
continue;
}
lastChildPosition = childPosition;
if (!mShowLastDivider && childPosition >= itemCount - lastDividerOffset) {
// Don't draw divider for last line if mShowLastDivider = false
continue;
}
if (wasDividerAlreadyDrawn(childPosition, parent)) {
// No need to draw divider again as it was drawn already by previous column
continue;
}
int groupIndex = getGroupIndex(childPosition, parent);
if (mVisibilityProvider.shouldHideDivider(groupIndex, parent)) {
continue;
}
Rect bounds = getDividerBound(groupIndex, parent, child);
switch (mDividerType) {
case DRAWABLE:
Drawable drawable = mDrawableProvider.drawableProvider(groupIndex, parent);
drawable.setBounds(bounds);
drawable.draw(c);
break;
case PAINT:
mPaint = mPaintProvider.dividerPaint(groupIndex, parent);
c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint);
break;
case COLOR:
mPaint.setColor(mColorProvider.dividerColor(groupIndex, parent));
mPaint.setStrokeWidth(mSizeProvider.dividerSize(groupIndex, parent));
c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint);
break;
}
}
}
@Override
public void getItemOffsets(Rect rect, View v, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(v);
int itemCount = parent.getAdapter().getItemCount();
int lastDividerOffset = getLastDividerOffset(parent);
if (!mShowLastDivider && position >= itemCount - lastDividerOffset) {
// Don't set item offset for last line if mShowLastDivider = false
return;
}
int groupIndex = getGroupIndex(position, parent);
if (mVisibilityProvider.shouldHideDivider(groupIndex, parent)) {
return;
}
setItemOffsets(rect, groupIndex, parent);
}
/**
* Check if recyclerview is reverse layout
*
* @param parent RecyclerView
* @return true if recyclerview is reverse layout
*/
protected boolean isReverseLayout(RecyclerView parent) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) layoutManager).getReverseLayout();
} else {
return false;
}
}
/**
* In the case mShowLastDivider = false,
* Returns offset for how many views we don't have to draw a divider for,
* for LinearLayoutManager it is as simple as not drawing the last child divider,
* but for a GridLayoutManager it needs to take the span count for the last items into account
* until we use the span count configured for the grid.
*
* @param parent RecyclerView
* @return offset for how many views we don't have to draw a divider or 1 if its a
* LinearLayoutManager
*/
private int getLastDividerOffset(RecyclerView parent) {
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
int itemCount = parent.getAdapter().getItemCount();
for (int i = itemCount - 1; i >= 0; i--) {
if (spanSizeLookup.getSpanIndex(i, spanCount) == 0) {
return itemCount - i;
}
}
}
return 1;
}
/**
* Determines whether divider was already drawn for the row the item is in,
* effectively only makes sense for a grid
*
* @param position current view position to draw divider
* @param parent RecyclerView
* @return true if the divider can be skipped as it is in the same row as the previous one.
*/
private boolean wasDividerAlreadyDrawn(int position, RecyclerView parent) {
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
return spanSizeLookup.getSpanIndex(position, spanCount) > 0;
}
return false;
}
/**
* Returns a group index for GridLayoutManager.
* for LinearLayoutManager, always returns position.
*
* @param position current view position to draw divider
* @param parent RecyclerView
* @return group index of items
*/
private int getGroupIndex(int position, RecyclerView parent) {
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
return spanSizeLookup.getSpanGroupIndex(position, spanCount);
}
return position;
}
protected abstract Rect getDividerBound(int position, RecyclerView parent, View child);
protected abstract void setItemOffsets(Rect outRect, int position, RecyclerView parent);
/**
* Interface for controlling divider visibility
*/
public interface VisibilityProvider {
/**
* Returns true if divider should be hidden.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return True if the divider at position should be hidden
*/
boolean shouldHideDivider(int position, RecyclerView parent);
}
/**
* Interface for controlling paint instance for divider drawing
*/
public interface PaintProvider {
/**
* Returns {@link android.graphics.Paint} for divider
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return Paint instance
*/
Paint dividerPaint(int position, RecyclerView parent);
}
/**
* Interface for controlling divider color
*/
public interface ColorProvider {
/**
* Returns {@link android.graphics.Color} value of divider
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return Color value
*/
int dividerColor(int position, RecyclerView parent);
}
/**
* Interface for controlling drawable object for divider drawing
*/
public interface DrawableProvider {
/**
* Returns drawable instance for divider
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return Drawable instance
*/
Drawable drawableProvider(int position, RecyclerView parent);
}
/**
* Interface for controlling divider size
*/
public interface SizeProvider {
/**
* Returns size value of divider.
* Height for horizontal divider, width for vertical divider
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return Size of divider
*/
int dividerSize(int position, RecyclerView parent);
}
public static class Builder<T extends Builder> {
private Context mContext;
protected Resources mResources;
private PaintProvider mPaintProvider;
private ColorProvider mColorProvider;
private DrawableProvider mDrawableProvider;
private SizeProvider mSizeProvider;
private VisibilityProvider mVisibilityProvider = new VisibilityProvider() {
@Override
public boolean shouldHideDivider(int position, RecyclerView parent) {
return false;
}
};
private boolean mShowLastDivider = false;
private boolean mPositionInsideItem = false;
public Builder(Context context) {
mContext = context;
mResources = context.getResources();
}
public T paint(final Paint paint) {
return paintProvider(new PaintProvider() {
@Override
public Paint dividerPaint(int position, RecyclerView parent) {
return paint;
}
});
}
public T paintProvider(PaintProvider provider) {
mPaintProvider = provider;
return (T) this;
}
public T color(final int color) {
return colorProvider(new ColorProvider() {
@Override
public int dividerColor(int position, RecyclerView parent) {
return color;
}
});
}
public T colorResId(@ColorRes int colorId) {
return color(ContextCompat.getColor(mContext, colorId));
}
public T colorProvider(ColorProvider provider) {
mColorProvider = provider;
return (T) this;
}
public T drawable(@DrawableRes int id) {
return drawable(ContextCompat.getDrawable(mContext, id));
}
public T drawable(final Drawable drawable) {
return drawableProvider(new DrawableProvider() {
@Override
public Drawable drawableProvider(int position, RecyclerView parent) {
return drawable;
}
});
}
public T drawableProvider(DrawableProvider provider) {
mDrawableProvider = provider;
return (T) this;
}
public T size(final int size) {
return sizeProvider(new SizeProvider() {
@Override
public int dividerSize(int position, RecyclerView parent) {
return size;
}
});
}
public T sizeResId(@DimenRes int sizeId) {
return size(mResources.getDimensionPixelSize(sizeId));
}
public T sizeProvider(SizeProvider provider) {
mSizeProvider = provider;
return (T) this;
}
public T visibilityProvider(VisibilityProvider provider) {
mVisibilityProvider = provider;
return (T) this;
}
public T showLastDivider() {
mShowLastDivider = true;
return (T) this;
}
public T positionInsideItem(boolean positionInsideItem) {
mPositionInsideItem = positionInsideItem;
return (T) this;
}
protected void checkBuilderParams() {
if (mPaintProvider != null) {
if (mColorProvider != null) {
throw new IllegalArgumentException(
"Use setColor method of Paint class to specify line color. Do not provider ColorProvider if you set PaintProvider.");
}
if (mSizeProvider != null) {
throw new IllegalArgumentException(
"Use setStrokeWidth method of Paint class to specify line size. Do not provider SizeProvider if you set PaintProvider.");
}
}
}
}
}

View File

@@ -0,0 +1,174 @@
package com.yqritc.recyclerviewflexibledivider;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.DimenRes;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by yqritc on 2015/01/15.
*/
public class HorizontalDividerItemDecoration extends FlexibleDividerDecoration {
private MarginProvider mMarginProvider;
protected HorizontalDividerItemDecoration(Builder builder) {
super(builder);
mMarginProvider = builder.mMarginProvider;
}
@Override
protected Rect getDividerBound(int position, RecyclerView parent, View child) {
Rect bounds = new Rect(0, 0, 0, 0);
int transitionX = (int) ViewCompat.getTranslationX(child);
int transitionY = (int) ViewCompat.getTranslationY(child);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
bounds.left = parent.getPaddingLeft() +
mMarginProvider.dividerLeftMargin(position, parent) + transitionX;
bounds.right = parent.getWidth() - parent.getPaddingRight() -
mMarginProvider.dividerRightMargin(position, parent) + transitionX;
int dividerSize = getDividerSize(position, parent);
boolean isReverseLayout = isReverseLayout(parent);
if (mDividerType == DividerType.DRAWABLE) {
// set top and bottom position of divider
if (isReverseLayout) {
bounds.bottom = child.getTop() - params.topMargin + transitionY;
bounds.top = bounds.bottom - dividerSize;
} else {
bounds.top = child.getBottom() + params.bottomMargin + transitionY;
bounds.bottom = bounds.top + dividerSize;
}
} else {
// set center point of divider
int halfSize = dividerSize / 2;
if (isReverseLayout) {
bounds.top = child.getTop() - params.topMargin - halfSize + transitionY;
} else {
bounds.top = child.getBottom() + params.bottomMargin + halfSize + transitionY;
}
bounds.bottom = bounds.top;
}
if (mPositionInsideItem) {
if (isReverseLayout) {
bounds.top += dividerSize;
bounds.bottom += dividerSize;
} else {
bounds.top -= dividerSize;
bounds.bottom -= dividerSize;
}
}
return bounds;
}
@Override
protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) {
if (mPositionInsideItem) {
outRect.set(0, 0, 0, 0);
return;
}
if (isReverseLayout(parent)) {
outRect.set(0, getDividerSize(position, parent), 0, 0);
} else {
outRect.set(0, 0, 0, getDividerSize(position, parent));
}
}
private int getDividerSize(int position, RecyclerView parent) {
if (mPaintProvider != null) {
return (int) mPaintProvider.dividerPaint(position, parent).getStrokeWidth();
} else if (mSizeProvider != null) {
return mSizeProvider.dividerSize(position, parent);
} else if (mDrawableProvider != null) {
Drawable drawable = mDrawableProvider.drawableProvider(position, parent);
return drawable.getIntrinsicHeight();
}
throw new RuntimeException("failed to get size");
}
/**
* Interface for controlling divider margin
*/
public interface MarginProvider {
/**
* Returns left margin of divider.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return left margin
*/
int dividerLeftMargin(int position, RecyclerView parent);
/**
* Returns right margin of divider.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return right margin
*/
int dividerRightMargin(int position, RecyclerView parent);
}
public static class Builder extends FlexibleDividerDecoration.Builder<Builder> {
private MarginProvider mMarginProvider = new MarginProvider() {
@Override
public int dividerLeftMargin(int position, RecyclerView parent) {
return 0;
}
@Override
public int dividerRightMargin(int position, RecyclerView parent) {
return 0;
}
};
public Builder(Context context) {
super(context);
}
public Builder margin(final int leftMargin, final int rightMargin) {
return marginProvider(new MarginProvider() {
@Override
public int dividerLeftMargin(int position, RecyclerView parent) {
return leftMargin;
}
@Override
public int dividerRightMargin(int position, RecyclerView parent) {
return rightMargin;
}
});
}
public Builder margin(int horizontalMargin) {
return margin(horizontalMargin, horizontalMargin);
}
public Builder marginResId(@DimenRes int leftMarginId, @DimenRes int rightMarginId) {
return margin(mResources.getDimensionPixelSize(leftMarginId),
mResources.getDimensionPixelSize(rightMarginId));
}
public Builder marginResId(@DimenRes int horizontalMarginId) {
return marginResId(horizontalMarginId, horizontalMarginId);
}
public Builder marginProvider(MarginProvider provider) {
mMarginProvider = provider;
return this;
}
public HorizontalDividerItemDecoration build() {
checkBuilderParams();
return new HorizontalDividerItemDecoration(this);
}
}
}

View File

@@ -0,0 +1,174 @@
package com.yqritc.recyclerviewflexibledivider;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.DimenRes;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by yqritc on 2015/01/15.
*/
public class VerticalDividerItemDecoration extends FlexibleDividerDecoration {
private MarginProvider mMarginProvider;
protected VerticalDividerItemDecoration(Builder builder) {
super(builder);
mMarginProvider = builder.mMarginProvider;
}
@Override
protected Rect getDividerBound(int position, RecyclerView parent, View child) {
Rect bounds = new Rect(0, 0, 0, 0);
int transitionX = (int) ViewCompat.getTranslationX(child);
int transitionY = (int) ViewCompat.getTranslationY(child);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
bounds.top = parent.getPaddingTop() +
mMarginProvider.dividerTopMargin(position, parent) + transitionY;
bounds.bottom = parent.getHeight() - parent.getPaddingBottom() -
mMarginProvider.dividerBottomMargin(position, parent) + transitionY;
int dividerSize = getDividerSize(position, parent);
boolean isReverseLayout = isReverseLayout(parent);
if (mDividerType == DividerType.DRAWABLE) {
// set left and right position of divider
if (isReverseLayout) {
bounds.right = child.getLeft() - params.leftMargin + transitionX;
bounds.left = bounds.right - dividerSize;
} else {
bounds.left = child.getRight() + params.rightMargin + transitionX;
bounds.right = bounds.left + dividerSize;
}
} else {
// set center point of divider
int halfSize = dividerSize / 2;
if (isReverseLayout) {
bounds.left = child.getLeft() - params.leftMargin - halfSize + transitionX;
} else {
bounds.left = child.getRight() + params.rightMargin + halfSize + transitionX;
}
bounds.right = bounds.left;
}
if (mPositionInsideItem) {
if (isReverseLayout) {
bounds.left += dividerSize;
bounds.right += dividerSize;
} else {
bounds.left -= dividerSize;
bounds.right -= dividerSize;
}
}
return bounds;
}
@Override
protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) {
if (mPositionInsideItem) {
outRect.set(0, 0, 0, 0);
return;
}
if (isReverseLayout(parent)) {
outRect.set(getDividerSize(position, parent), 0, 0, 0);
} else {
outRect.set(0, 0, getDividerSize(position, parent), 0);
}
}
private int getDividerSize(int position, RecyclerView parent) {
if (mPaintProvider != null) {
return (int) mPaintProvider.dividerPaint(position, parent).getStrokeWidth();
} else if (mSizeProvider != null) {
return mSizeProvider.dividerSize(position, parent);
} else if (mDrawableProvider != null) {
Drawable drawable = mDrawableProvider.drawableProvider(position, parent);
return drawable.getIntrinsicWidth();
}
throw new RuntimeException("failed to get size");
}
/**
* Interface for controlling divider margin
*/
public interface MarginProvider {
/**
* Returns top margin of divider.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return top margin
*/
int dividerTopMargin(int position, RecyclerView parent);
/**
* Returns bottom margin of divider.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return bottom margin
*/
int dividerBottomMargin(int position, RecyclerView parent);
}
public static class Builder extends FlexibleDividerDecoration.Builder<Builder> {
private MarginProvider mMarginProvider = new MarginProvider() {
@Override
public int dividerTopMargin(int position, RecyclerView parent) {
return 0;
}
@Override
public int dividerBottomMargin(int position, RecyclerView parent) {
return 0;
}
};
public Builder(Context context) {
super(context);
}
public Builder margin(final int topMargin, final int bottomMargin) {
return marginProvider(new MarginProvider() {
@Override
public int dividerTopMargin(int position, RecyclerView parent) {
return topMargin;
}
@Override
public int dividerBottomMargin(int position, RecyclerView parent) {
return bottomMargin;
}
});
}
public Builder margin(int verticalMargin) {
return margin(verticalMargin, verticalMargin);
}
public Builder marginResId(@DimenRes int topMarginId, @DimenRes int bottomMarginId) {
return margin(mResources.getDimensionPixelSize(topMarginId),
mResources.getDimensionPixelSize(bottomMarginId));
}
public Builder marginResId(@DimenRes int verticalMarginId) {
return marginResId(verticalMarginId, verticalMarginId);
}
public Builder marginProvider(MarginProvider provider) {
mMarginProvider = provider;
return this;
}
public VerticalDividerItemDecoration build() {
checkBuilderParams();
return new VerticalDividerItemDecoration(this);
}
}
}