6.7.0 - Alpha23 - 移除 "android.enableJetifier=true"; 模块化 Expandable Layout, Expandable RecyclerView, Recyclerview Flexible Divider
This commit is contained in:
22
modules/expandable-layout/build.gradle
Normal file
22
modules/expandable-layout/build.gradle
Normal 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
|
||||
}
|
||||
17
modules/expandable-layout/proguard-rules.pro
vendored
Normal file
17
modules/expandable-layout/proguard-rules.pro
vendored
Normal 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 *;
|
||||
#}
|
||||
1
modules/expandable-layout/src/main/AndroidManifest.xml
Normal file
1
modules/expandable-layout/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest />
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
modules/expandable-layout/src/main/res/values/attrs.xml
Normal file
26
modules/expandable-layout/src/main/res/values/attrs.xml
Normal 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>
|
||||
21
modules/expandable-recyclerview/build.gradle
Normal file
21
modules/expandable-recyclerview/build.gradle
Normal 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
|
||||
}
|
||||
17
modules/expandable-recyclerview/proguard-rules.pro
vendored
Normal file
17
modules/expandable-recyclerview/proguard-rules.pro
vendored
Normal 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 *;
|
||||
#}
|
||||
@@ -0,0 +1 @@
|
||||
<manifest />
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
20
modules/recyclerview-flexibledivider/build.gradle
Normal file
20
modules/recyclerview-flexibledivider/build.gradle
Normal 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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<manifest package="com.yqritc.recyclerviewflexibledivider"></manifest>
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user