add window manager dialog
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
package com.mogo.module.common.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.mogo.module.common.R;
|
||||
import com.mogo.module.common.utils.CarSeries;
|
||||
import com.mogo.utils.WindowUtils;
|
||||
|
||||
/**
|
||||
* @author congtaowang
|
||||
* @since 2020-04-24
|
||||
* <p>
|
||||
* 显示在最上层的对话框
|
||||
*/
|
||||
public class WMDialog implements DialogInterface {
|
||||
|
||||
private WMDialogParams mParams;
|
||||
private WindowManager mWindowManager;
|
||||
private boolean mIsShowing = false;
|
||||
private View mContentView;
|
||||
private WindowManager.LayoutParams mLayoutParams;
|
||||
|
||||
private WMDialog( WMDialogParams params ) {
|
||||
this.mParams = params;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if ( mIsShowing ) {
|
||||
return;
|
||||
}
|
||||
mIsShowing = true;
|
||||
if ( mWindowManager == null ) {
|
||||
mWindowManager = ( WindowManager ) mParams.mContext.getApplicationContext().getSystemService( Context.WINDOW_SERVICE );
|
||||
}
|
||||
if ( mContentView == null ) {
|
||||
mLayoutParams = new WindowManager.LayoutParams();
|
||||
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
|
||||
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
} else {
|
||||
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
|
||||
}
|
||||
mLayoutParams.format = PixelFormat.TRANSLUCENT;
|
||||
mLayoutParams.gravity = Gravity.CENTER;
|
||||
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
|
||||
|
||||
if ( CarSeries.getSeries() == CarSeries.CAR_SERIES_F80X ) {
|
||||
mLayoutParams.width = 1920;
|
||||
mLayoutParams.height = 1080;
|
||||
} else {
|
||||
mLayoutParams.width = WindowUtils.getScreenWidth( mParams.mContext );
|
||||
mLayoutParams.height = WindowUtils.getScreenHeight( mParams.mContext );
|
||||
}
|
||||
mLayoutParams.dimAmount = 0.5f;
|
||||
mLayoutParams.x = 0;
|
||||
mLayoutParams.y = 0;
|
||||
mContentView = initViews();
|
||||
}
|
||||
mWindowManager.addView( mContentView, mLayoutParams );
|
||||
}
|
||||
|
||||
private View initViews() {
|
||||
View contentView = LayoutInflater.from( mParams.mContext ).inflate( R.layout.module_commons_layout_wm_dialog, null );
|
||||
TextView ok = contentView.findViewById( R.id.module_commons_wm_dialog_button_ok );
|
||||
TextView cancel = contentView.findViewById( R.id.module_commons_wm_dialog_button_cancel );
|
||||
TextView content = contentView.findViewById( R.id.module_commons_wm_dialog_content );
|
||||
|
||||
ok.setText( mParams.mOkButtonText );
|
||||
if ( mParams.mOnOkButtonClickListener != null ) {
|
||||
ok.setOnClickListener( view -> {
|
||||
if ( mParams.mOnOkButtonClickListener != null ) {
|
||||
mParams.mOnOkButtonClickListener.onClick( WMDialog.this, DialogInterface.BUTTON_POSITIVE );
|
||||
}
|
||||
} );
|
||||
}
|
||||
cancel.setText( mParams.mCancelButtonText );
|
||||
if ( mParams.mOnCancelButtonClickListener != null ) {
|
||||
cancel.setOnClickListener( view -> {
|
||||
if ( mParams.mOnCancelButtonClickListener != null ) {
|
||||
mParams.mOnCancelButtonClickListener.onClick( WMDialog.this, DialogInterface.BUTTON_NEGATIVE );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
content.setText( mParams.mContent );
|
||||
return contentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
if ( !mIsShowing ) {
|
||||
return;
|
||||
}
|
||||
if ( mContentView != null ) {
|
||||
mWindowManager.removeViewImmediate( mContentView );
|
||||
}
|
||||
if ( mParams.mOnDialogDismissListener != null ) {
|
||||
mParams.mOnDialogDismissListener.onDismiss( this );
|
||||
}
|
||||
mIsShowing = false;
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mIsShowing;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private Context mContext;
|
||||
private WMDialogParams mParams;
|
||||
|
||||
public Builder( Context context ) {
|
||||
this.mContext = context;
|
||||
mParams = new WMDialogParams();
|
||||
mParams.mContext = context;
|
||||
}
|
||||
|
||||
// public Builder setTitle( CharSequence title ) {
|
||||
// mParams.mTitle = title;
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// public Builder setTitle( @StringRes int title ) {
|
||||
// mParams.mTitle = mContext.getString( title );
|
||||
// return this;
|
||||
// }
|
||||
|
||||
public Builder setContent( CharSequence content ) {
|
||||
mParams.mContent = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setContent( @StringRes int content ) {
|
||||
mParams.mContent = mContext.getString( content );
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOkButton( CharSequence buttonText, OnClickListener listener ) {
|
||||
mParams.mOkButtonText = buttonText;
|
||||
mParams.mOnOkButtonClickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOkButton( @StringRes int buttonText, OnClickListener listener ) {
|
||||
mParams.mOkButtonText = mContext.getText( buttonText );
|
||||
mParams.mOnOkButtonClickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCancelButton( CharSequence buttonText, OnClickListener listener ) {
|
||||
mParams.mCancelButtonText = buttonText;
|
||||
mParams.mOnCancelButtonClickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCancelButton( @StringRes int buttonText, OnClickListener listener ) {
|
||||
mParams.mCancelButtonText = mContext.getText( buttonText );
|
||||
mParams.mOnCancelButtonClickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOnDialogDismissListener( OnDismissListener onDialogDismissListener ) {
|
||||
mParams.mOnDialogDismissListener = onDialogDismissListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WMDialog build() {
|
||||
WMDialog dialog = new WMDialog( mParams );
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WMDialogParams {
|
||||
|
||||
// public CharSequence mTitle;
|
||||
public CharSequence mOkButtonText;
|
||||
public CharSequence mCancelButtonText;
|
||||
public CharSequence mContent;
|
||||
public OnClickListener mOnOkButtonClickListener;
|
||||
public OnClickListener mOnCancelButtonClickListener;
|
||||
public OnDismissListener mOnDialogDismissListener;
|
||||
public Context mContext;
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 16.03.2017.
|
||||
*/
|
||||
public enum DSVOrientation {
|
||||
|
||||
HORIZONTAL {
|
||||
@Override
|
||||
Helper createHelper() {
|
||||
return new HorizontalHelper();
|
||||
}
|
||||
},
|
||||
VERTICAL {
|
||||
@Override
|
||||
Helper createHelper() {
|
||||
return new VerticalHelper();
|
||||
}
|
||||
};
|
||||
|
||||
//Package private
|
||||
abstract Helper createHelper();
|
||||
|
||||
interface Helper {
|
||||
|
||||
int getViewEnd(int recyclerWidth, int recyclerHeight);
|
||||
|
||||
int getDistanceToChangeCurrent(int childWidth, int childHeight);
|
||||
|
||||
void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint);
|
||||
|
||||
void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter);
|
||||
|
||||
int getFlingVelocity(int velocityX, int velocityY);
|
||||
|
||||
int getPendingDx(int pendingScroll);
|
||||
|
||||
int getPendingDy(int pendingScroll);
|
||||
|
||||
void offsetChildren(int amount, RecyclerViewProxy lm);
|
||||
|
||||
float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY);
|
||||
|
||||
boolean isViewVisible(Point center, int halfWidth, int halfHeight, int endBound,
|
||||
int extraSpace);
|
||||
|
||||
boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm);
|
||||
|
||||
boolean canScrollVertically();
|
||||
|
||||
boolean canScrollHorizontally();
|
||||
}
|
||||
|
||||
protected static class HorizontalHelper implements Helper {
|
||||
|
||||
@Override
|
||||
public int getViewEnd(int recyclerWidth, int recyclerHeight) {
|
||||
return recyclerWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDistanceToChangeCurrent(int childWidth, int childHeight) {
|
||||
return childWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint) {
|
||||
int newX = recyclerCenter.x - scrolled;
|
||||
outPoint.set(newX, recyclerCenter.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter) {
|
||||
int newX = outCenter.x + direction.applyTo(shiftAmount);
|
||||
outCenter.set(newX, outCenter.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewVisible(
|
||||
Point viewCenter, int halfWidth, int halfHeight, int endBound,
|
||||
int extraSpace) {
|
||||
int viewLeft = viewCenter.x - halfWidth;
|
||||
int viewRight = viewCenter.x + halfWidth;
|
||||
return viewLeft < (endBound + extraSpace) && viewRight > -extraSpace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm) {
|
||||
View firstChild = lm.getFirstChild(), lastChild = lm.getLastChild();
|
||||
int leftBound = -lm.getExtraLayoutSpace();
|
||||
int rightBound = lm.getWidth() + lm.getExtraLayoutSpace();
|
||||
boolean isNewVisibleFromLeft = lm.getDecoratedLeft(firstChild) > leftBound
|
||||
&& lm.getPosition(firstChild) > 0;
|
||||
boolean isNewVisibleFromRight = lm.getDecoratedRight(lastChild) < rightBound
|
||||
&& lm.getPosition(lastChild) < lm.getItemCount() - 1;
|
||||
return isNewVisibleFromLeft || isNewVisibleFromRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChildren(int amount, RecyclerViewProxy helper) {
|
||||
helper.offsetChildrenHorizontal(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY) {
|
||||
return viewCenterX - center.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlingVelocity(int velocityX, int velocityY) {
|
||||
return velocityX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollHorizontally() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingDx(int pendingScroll) {
|
||||
return pendingScroll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingDy(int pendingScroll) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static class VerticalHelper implements Helper {
|
||||
|
||||
@Override
|
||||
public int getViewEnd(int recyclerWidth, int recyclerHeight) {
|
||||
return recyclerHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDistanceToChangeCurrent(int childWidth, int childHeight) {
|
||||
return childHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint) {
|
||||
int newY = recyclerCenter.y - scrolled;
|
||||
outPoint.set(recyclerCenter.x, newY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter) {
|
||||
int newY = outCenter.y + direction.applyTo(shiftAmount);
|
||||
outCenter.set(outCenter.x, newY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChildren(int amount, RecyclerViewProxy helper) {
|
||||
helper.offsetChildrenVertical(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY) {
|
||||
return viewCenterY - center.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewVisible(
|
||||
Point viewCenter, int halfWidth, int halfHeight, int endBound,
|
||||
int extraSpace) {
|
||||
int viewTop = viewCenter.y - halfHeight;
|
||||
int viewBottom = viewCenter.y + halfHeight;
|
||||
return viewTop < (endBound + extraSpace) && viewBottom > -extraSpace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm) {
|
||||
View firstChild = lm.getFirstChild(), lastChild = lm.getLastChild();
|
||||
int topBound = -lm.getExtraLayoutSpace();
|
||||
int bottomBound = lm.getHeight() + lm.getExtraLayoutSpace();
|
||||
boolean isNewVisibleFromTop = lm.getDecoratedTop(firstChild) > topBound
|
||||
&& lm.getPosition(firstChild) > 0;
|
||||
boolean isNewVisibleFromBottom = lm.getDecoratedBottom(lastChild) < bottomBound
|
||||
&& lm.getPosition(lastChild) < lm.getItemCount() - 1;
|
||||
return isNewVisibleFromTop || isNewVisibleFromBottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlingVelocity(int velocityX, int velocityY) {
|
||||
return velocityY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollHorizontally() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingDx(int pendingScroll) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingDy(int pendingScroll) {
|
||||
return pendingScroll;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 16.03.2017.
|
||||
*/
|
||||
enum Direction {
|
||||
|
||||
START {
|
||||
@Override
|
||||
public int applyTo(int delta) {
|
||||
return delta * -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(int direction) {
|
||||
return direction < 0;
|
||||
}
|
||||
},
|
||||
END {
|
||||
@Override
|
||||
public int applyTo(int delta) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(int direction) {
|
||||
return direction > 0;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract int applyTo(int delta);
|
||||
|
||||
public abstract boolean sameAs(int direction);
|
||||
|
||||
public static Direction fromDelta(int delta) {
|
||||
return delta > 0 ? END : START;
|
||||
}
|
||||
}
|
||||
@@ -1,836 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.accessibility.AccessibilityEventCompat;
|
||||
import androidx.core.view.accessibility.AccessibilityRecordCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.alibaba.idst.nls.internal.utils.L;
|
||||
import com.mogo.utils.logger.Logger;
|
||||
import com.yarolegovich.discretescrollview.transform.DiscreteScrollItemTransformer;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 17.02.2017.
|
||||
*/
|
||||
public class DiscreteScrollLayoutManager extends LinearLayoutManager {
|
||||
|
||||
static final int NO_POSITION = -1;
|
||||
|
||||
private static final String EXTRA_POSITION = "extra_position";
|
||||
private static final int DEFAULT_TIME_FOR_ITEM_SETTLE = 300;
|
||||
private static final int DEFAULT_FLING_THRESHOLD = 2100; //Decrease to increase sensitivity.
|
||||
private static final int DEFAULT_TRANSFORM_CLAMP_ITEM_COUNT = 1;
|
||||
|
||||
protected static final float SCROLL_TO_SNAP_TO_ANOTHER_ITEM = 0.6f;
|
||||
|
||||
//This field will take value of all visible view's center points during the fill phase
|
||||
protected Point viewCenterIterator;
|
||||
protected Point recyclerCenter;
|
||||
protected Point currentViewCenter;
|
||||
protected int childHalfWidth, childHalfHeight;
|
||||
protected int extraLayoutSpace;
|
||||
|
||||
//Max possible distance a view can travel during one scroll phase
|
||||
protected int scrollToChangeCurrent;
|
||||
protected int currentScrollState;
|
||||
|
||||
protected int scrolled;
|
||||
protected int pendingScroll;
|
||||
protected int currentPosition;
|
||||
protected int pendingPosition;
|
||||
|
||||
protected SparseArray<View> detachedCache;
|
||||
|
||||
private DSVOrientation.Helper orientationHelper;
|
||||
|
||||
protected boolean isFirstOrEmptyLayout;
|
||||
|
||||
private Context context;
|
||||
|
||||
private int timeForItemSettle;
|
||||
private int offscreenItems;
|
||||
private int transformClampItemCount;
|
||||
|
||||
private boolean dataSetChangeShiftedPosition;
|
||||
|
||||
private int flingThreshold;
|
||||
private boolean shouldSlideOnFling;
|
||||
|
||||
private int viewWidth, viewHeight;
|
||||
|
||||
private float ratio=0.5F;
|
||||
|
||||
private static final String TAG = "DiscreteScrollLayoutMan";
|
||||
@NonNull
|
||||
private final ScrollStateListener scrollStateListener;
|
||||
private DiscreteScrollItemTransformer itemTransformer;
|
||||
|
||||
private RecyclerViewProxy recyclerViewProxy;
|
||||
|
||||
public DiscreteScrollLayoutManager(
|
||||
@NonNull Context c,
|
||||
@NonNull ScrollStateListener scrollStateListener,
|
||||
@NonNull DSVOrientation orientation) {
|
||||
super(c);
|
||||
this.context = c;
|
||||
this.timeForItemSettle = DEFAULT_TIME_FOR_ITEM_SETTLE;
|
||||
this.pendingPosition = NO_POSITION;
|
||||
this.currentPosition = NO_POSITION;
|
||||
this.flingThreshold = DEFAULT_FLING_THRESHOLD;
|
||||
this.shouldSlideOnFling = false;
|
||||
this.recyclerCenter = new Point();
|
||||
this.currentViewCenter = new Point();
|
||||
this.viewCenterIterator = new Point();
|
||||
this.detachedCache = new SparseArray<>();
|
||||
this.scrollStateListener = scrollStateListener;
|
||||
this.orientationHelper = orientation.createHelper();
|
||||
this.recyclerViewProxy = new RecyclerViewProxy(this);
|
||||
this.transformClampItemCount = DEFAULT_TRANSFORM_CLAMP_ITEM_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
if (state.getItemCount() == 0) {
|
||||
recyclerViewProxy.removeAndRecycleAllViews(recycler);
|
||||
currentPosition = pendingPosition = NO_POSITION;
|
||||
scrolled = pendingScroll = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ensureValidPosition(state);
|
||||
|
||||
updateRecyclerDimensions(state);
|
||||
|
||||
//onLayoutChildren may be called multiple times and this check is required so that the flag
|
||||
//won't be cleared until onLayoutCompleted
|
||||
if (!isFirstOrEmptyLayout) {
|
||||
isFirstOrEmptyLayout = recyclerViewProxy.getChildCount() == 0;
|
||||
if (isFirstOrEmptyLayout) {
|
||||
initChildDimensions(recycler);
|
||||
}
|
||||
}
|
||||
|
||||
recyclerViewProxy.detachAndScrapAttachedViews(recycler);
|
||||
|
||||
fill(recycler);
|
||||
|
||||
applyItemTransformToChildren();
|
||||
}
|
||||
|
||||
private void ensureValidPosition(RecyclerView.State state) {
|
||||
if (currentPosition == NO_POSITION || currentPosition >= state.getItemCount()) {
|
||||
//currentPosition might have been assigned in onRestoreInstanceState()
|
||||
//which can lead to a crash (position out of bounds) when data set
|
||||
//is not persisted across rotations
|
||||
currentPosition = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void setRatio(float ratio) {
|
||||
this.ratio = ratio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutCompleted(RecyclerView.State state) {
|
||||
if (isFirstOrEmptyLayout) {
|
||||
scrollStateListener.onCurrentViewFirstLayout();
|
||||
isFirstOrEmptyLayout = false;
|
||||
} else if (dataSetChangeShiftedPosition) {
|
||||
scrollStateListener.onDataSetChangeChangedPosition();
|
||||
dataSetChangeShiftedPosition = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void initChildDimensions(RecyclerView.Recycler recycler) {
|
||||
View viewToMeasure = recyclerViewProxy.getMeasuredChildForAdapterPosition(0, recycler);
|
||||
|
||||
int childViewWidth = recyclerViewProxy.getMeasuredWidthWithMargin(viewToMeasure);
|
||||
int childViewHeight = recyclerViewProxy.getMeasuredHeightWithMargin(viewToMeasure);
|
||||
|
||||
childHalfWidth = childViewWidth / 2;
|
||||
childHalfHeight = childViewHeight / 2;
|
||||
|
||||
scrollToChangeCurrent = orientationHelper.getDistanceToChangeCurrent(
|
||||
childViewWidth,
|
||||
childViewHeight);
|
||||
|
||||
extraLayoutSpace = scrollToChangeCurrent * offscreenItems;
|
||||
|
||||
recyclerViewProxy.detachAndScrapView(viewToMeasure, recycler);
|
||||
}
|
||||
|
||||
protected void updateRecyclerDimensions(RecyclerView.State state) {
|
||||
boolean dimensionsChanged = !state.isMeasuring()
|
||||
&& (recyclerViewProxy.getWidth() != viewWidth
|
||||
|| recyclerViewProxy.getHeight() != viewHeight);
|
||||
if (dimensionsChanged) {
|
||||
viewWidth = recyclerViewProxy.getWidth();
|
||||
viewHeight = recyclerViewProxy.getHeight();
|
||||
recyclerViewProxy.removeAllViews();
|
||||
}
|
||||
recyclerCenter.set(
|
||||
(int) (recyclerViewProxy.getWidth() * ratio),
|
||||
recyclerViewProxy.getHeight() / 2);
|
||||
}
|
||||
|
||||
protected void fill(RecyclerView.Recycler recycler) {
|
||||
cacheAndDetachAttachedViews();
|
||||
|
||||
orientationHelper.setCurrentViewCenter(recyclerCenter, scrolled, currentViewCenter);
|
||||
|
||||
final int endBound = orientationHelper.getViewEnd(
|
||||
recyclerViewProxy.getWidth(),
|
||||
recyclerViewProxy.getHeight());
|
||||
|
||||
//Layout current
|
||||
if (isViewVisible(currentViewCenter, endBound)) {
|
||||
layoutView(recycler, currentPosition, currentViewCenter);
|
||||
}
|
||||
|
||||
//Layout items before the current item
|
||||
layoutViews(recycler, Direction.START, endBound);
|
||||
|
||||
//Layout items after the current item
|
||||
layoutViews(recycler, Direction.END, endBound);
|
||||
|
||||
recycleDetachedViewsAndClearCache(recycler);
|
||||
}
|
||||
|
||||
private void layoutViews(RecyclerView.Recycler recycler, Direction direction, int endBound) {
|
||||
final int positionStep = direction.applyTo(1);
|
||||
|
||||
//Predictive layout is required when we are doing smooth fast scroll towards pendingPosition
|
||||
boolean noPredictiveLayoutRequired = pendingPosition == NO_POSITION
|
||||
|| !direction.sameAs(pendingPosition - currentPosition);
|
||||
|
||||
viewCenterIterator.set(currentViewCenter.x, currentViewCenter.y);
|
||||
for (int pos = currentPosition + positionStep; isInBounds(pos); pos += positionStep) {
|
||||
if (pos == pendingPosition) {
|
||||
noPredictiveLayoutRequired = true;
|
||||
}
|
||||
orientationHelper.shiftViewCenter(direction, scrollToChangeCurrent, viewCenterIterator);
|
||||
if (isViewVisible(viewCenterIterator, endBound)) {
|
||||
layoutView(recycler, pos, viewCenterIterator);
|
||||
} else if (noPredictiveLayoutRequired) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void layoutView(RecyclerView.Recycler recycler, int position, Point viewCenter) {
|
||||
if (position < 0) return;
|
||||
View v = detachedCache.get(position);
|
||||
if (v == null) {
|
||||
v = recyclerViewProxy.getMeasuredChildForAdapterPosition(position, recycler);
|
||||
recyclerViewProxy.layoutDecoratedWithMargins(v,
|
||||
viewCenter.x - childHalfWidth, viewCenter.y - childHalfHeight,
|
||||
viewCenter.x + childHalfWidth, viewCenter.y + childHalfHeight);
|
||||
} else {
|
||||
recyclerViewProxy.attachView(v);
|
||||
detachedCache.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
protected void cacheAndDetachAttachedViews() {
|
||||
detachedCache.clear();
|
||||
for (int i = 0; i < recyclerViewProxy.getChildCount(); i++) {
|
||||
View child = recyclerViewProxy.getChildAt(i);
|
||||
detachedCache.put(recyclerViewProxy.getPosition(child), child);
|
||||
}
|
||||
|
||||
for (int i = 0; i < detachedCache.size(); i++) {
|
||||
recyclerViewProxy.detachView(detachedCache.valueAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
protected void recycleDetachedViewsAndClearCache(RecyclerView.Recycler recycler) {
|
||||
for (int i = 0; i < detachedCache.size(); i++) {
|
||||
View viewToRemove = detachedCache.valueAt(i);
|
||||
recyclerViewProxy.recycleView(viewToRemove, recycler);
|
||||
}
|
||||
detachedCache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
int newPosition = currentPosition;
|
||||
if (currentPosition == NO_POSITION) {
|
||||
newPosition = 0;
|
||||
} else if (currentPosition >= positionStart) {
|
||||
newPosition = Math.min(currentPosition + itemCount, recyclerViewProxy.getItemCount() - 1);
|
||||
}
|
||||
onNewPosition(newPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
int newPosition = currentPosition;
|
||||
if (recyclerViewProxy.getItemCount() == 0) {
|
||||
newPosition = NO_POSITION;
|
||||
} else if (currentPosition >= positionStart) {
|
||||
if (currentPosition < positionStart + itemCount) {
|
||||
//If currentPosition is in the removed items, then the new item became current
|
||||
currentPosition = NO_POSITION;
|
||||
}
|
||||
newPosition = Math.max(0, currentPosition - itemCount);
|
||||
}
|
||||
onNewPosition(newPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsChanged(RecyclerView recyclerView) {
|
||||
//notifyDataSetChanged() was called. We need to ensure that currentPosition is not out of bounds
|
||||
currentPosition = Math.min(Math.max(0, currentPosition), recyclerViewProxy.getItemCount() - 1);
|
||||
dataSetChangeShiftedPosition = true;
|
||||
}
|
||||
|
||||
private void onNewPosition(int position) {
|
||||
if (currentPosition != position) {
|
||||
currentPosition = position;
|
||||
dataSetChangeShiftedPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
return scrollBy(dx, recycler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
return scrollBy(dy, recycler);
|
||||
}
|
||||
|
||||
protected int scrollBy(int amount, RecyclerView.Recycler recycler) {
|
||||
if (recyclerViewProxy.getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Direction direction = Direction.fromDelta(amount);
|
||||
int leftToScroll = calculateAllowedScrollIn(direction);
|
||||
if (leftToScroll <= 0) {
|
||||
return 0;
|
||||
}
|
||||
int delta = direction.applyTo(Math.min(leftToScroll, Math.abs(amount)));
|
||||
Logger.d(TAG,"leftToScroll--》"+leftToScroll+"---amount--》"+amount);
|
||||
|
||||
scrolled += delta;
|
||||
if (pendingScroll != 0) {
|
||||
pendingScroll -= delta;
|
||||
}
|
||||
|
||||
orientationHelper.offsetChildren(-delta, recyclerViewProxy);
|
||||
|
||||
if (orientationHelper.hasNewBecomeVisible(this)) {
|
||||
fill(recycler);
|
||||
}
|
||||
|
||||
notifyScroll();
|
||||
|
||||
applyItemTransformToChildren();
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
protected void applyItemTransformToChildren() {
|
||||
if (itemTransformer != null) {
|
||||
int clampAfterDistance = scrollToChangeCurrent * transformClampItemCount;
|
||||
for (int i = 0; i < recyclerViewProxy.getChildCount(); i++) {
|
||||
View child = recyclerViewProxy.getChildAt(i);
|
||||
|
||||
//RecyclerView.ViewHolder childViewHolder = recyclerView.getChildViewHolder(child);
|
||||
float position = getCenterRelativePositionOf(child, clampAfterDistance);
|
||||
itemTransformer.transformItem(child,null, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToPosition(int position) {
|
||||
if (currentPosition == position) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentPosition = position;
|
||||
recyclerViewProxy.requestLayout();
|
||||
}
|
||||
|
||||
//@Override
|
||||
//public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||
// if (currentPosition == position || pendingPosition != NO_POSITION) {
|
||||
// return;
|
||||
// }
|
||||
// checkTargetPosition(state, position);
|
||||
// if (currentPosition == NO_POSITION) {
|
||||
// //Layout not happened yet
|
||||
// currentPosition = position;
|
||||
// } else {
|
||||
// startSmoothPendingScroll(position);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
@Override
|
||||
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||
LinearSmoothScroller smoothScroller =
|
||||
new LinearSmoothScroller(recyclerView.getContext()) {
|
||||
@Override
|
||||
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
|
||||
// 返回:滑过1px时经历的时间(ms)。
|
||||
return 10f / displayMetrics.densityDpi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
|
||||
return boxStart - viewStart;
|
||||
}
|
||||
};
|
||||
|
||||
smoothScroller.setTargetPosition(position);
|
||||
startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollHorizontally() {
|
||||
return orientationHelper.canScrollHorizontally();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
return orientationHelper.canScrollVertically();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(int state) {
|
||||
if (currentScrollState == RecyclerView.SCROLL_STATE_IDLE && currentScrollState != state) {
|
||||
scrollStateListener.onScrollStart();
|
||||
}
|
||||
|
||||
if (state == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
//Scroll is not finished until current view is centered
|
||||
boolean isScrollEnded = onScrollEnd();
|
||||
if (isScrollEnded) {
|
||||
scrollStateListener.onScrollEnd();
|
||||
} else {
|
||||
//Scroll continues and we don't want to set currentScrollState to STATE_IDLE,
|
||||
//because this will then trigger .scrollStateListener.onScrollStart()
|
||||
return;
|
||||
}
|
||||
} else if (state == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||
onDragStart();
|
||||
}
|
||||
currentScrollState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if scroll is ended and we don't need to settle items
|
||||
*/
|
||||
private boolean onScrollEnd() {
|
||||
if (pendingPosition != NO_POSITION) {
|
||||
currentPosition = pendingPosition;
|
||||
pendingPosition = NO_POSITION;
|
||||
scrolled = 0;
|
||||
}
|
||||
|
||||
Direction scrollDirection = Direction.fromDelta(scrolled);
|
||||
if (Math.abs(scrolled) == scrollToChangeCurrent) {
|
||||
currentPosition += scrollDirection.applyTo(1);
|
||||
scrolled = 0;
|
||||
}
|
||||
|
||||
if (isAnotherItemCloserThanCurrent()) {
|
||||
pendingScroll = getHowMuchIsLeftToScroll(scrolled);
|
||||
} else {
|
||||
pendingScroll = -scrolled;
|
||||
}
|
||||
|
||||
if (pendingScroll == 0) {
|
||||
return true;
|
||||
} else {
|
||||
startSmoothPendingScroll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDragStart() {
|
||||
//Here we need to:
|
||||
//1. Stop any pending scroll
|
||||
//2. Set currentPosition to position of the item that is closest to the center
|
||||
boolean isScrollingThroughMultiplePositions = Math.abs(scrolled) > scrollToChangeCurrent;
|
||||
if (isScrollingThroughMultiplePositions) {
|
||||
int scrolledPositions = scrolled / scrollToChangeCurrent;
|
||||
currentPosition += scrolledPositions;
|
||||
scrolled -= scrolledPositions * scrollToChangeCurrent;
|
||||
}
|
||||
if (isAnotherItemCloserThanCurrent()) {
|
||||
Direction direction = Direction.fromDelta(scrolled);
|
||||
currentPosition += direction.applyTo(1);
|
||||
scrolled = -getHowMuchIsLeftToScroll(scrolled);
|
||||
}
|
||||
pendingPosition = NO_POSITION;
|
||||
pendingScroll = 0;
|
||||
}
|
||||
|
||||
public void onFling(int velocityX, int velocityY) {
|
||||
int velocity = orientationHelper.getFlingVelocity(velocityX, velocityY);
|
||||
int throttleValue = shouldSlideOnFling ? Math.abs(velocity / flingThreshold) : 1;
|
||||
int newPosition = currentPosition + Direction.fromDelta(velocity).applyTo(throttleValue);
|
||||
newPosition = checkNewOnFlingPositionIsInBounds(newPosition);
|
||||
boolean isInScrollDirection = velocity * scrolled >= 0;
|
||||
boolean canFling = isInScrollDirection && isInBounds(newPosition);
|
||||
if (canFling) {
|
||||
startSmoothPendingScroll(newPosition);
|
||||
} else {
|
||||
returnToCurrentPosition();
|
||||
}
|
||||
|
||||
Logger.d(TAG,"onFling"+newPosition);
|
||||
}
|
||||
|
||||
public void returnToCurrentPosition() {
|
||||
pendingScroll = -scrolled;
|
||||
if (pendingScroll != 0) {
|
||||
startSmoothPendingScroll();
|
||||
}
|
||||
}
|
||||
|
||||
protected int calculateAllowedScrollIn(Direction direction) {
|
||||
if (pendingScroll != 0) {
|
||||
return Math.abs(pendingScroll);
|
||||
}
|
||||
int allowedScroll;
|
||||
boolean isBoundReached;
|
||||
boolean isScrollDirectionAsBefore = direction.applyTo(scrolled) > 0;
|
||||
if (direction == Direction.START && currentPosition == 0) {
|
||||
//We can scroll to the left when currentPosition == 0 only if we scrolled to the right before
|
||||
isBoundReached = scrolled == 0;
|
||||
allowedScroll = isBoundReached ? 0 : Math.abs(scrolled);
|
||||
} else if (direction == Direction.END && currentPosition == recyclerViewProxy.getItemCount() - 1) {
|
||||
//We can scroll to the right when currentPosition == last only if we scrolled to the left before
|
||||
isBoundReached = scrolled == 0;
|
||||
allowedScroll = isBoundReached ? 0 : Math.abs(scrolled);
|
||||
} else {
|
||||
isBoundReached = false;
|
||||
allowedScroll = isScrollDirectionAsBefore ?
|
||||
scrollToChangeCurrent - Math.abs(scrolled) :
|
||||
scrollToChangeCurrent + Math.abs(scrolled);
|
||||
}
|
||||
scrollStateListener.onIsBoundReachedFlagChange(isBoundReached);
|
||||
return allowedScroll;
|
||||
}
|
||||
|
||||
private void startSmoothPendingScroll() {
|
||||
LinearSmoothScroller scroller = new DiscreteLinearSmoothScroller(context);
|
||||
scroller.setTargetPosition(currentPosition);
|
||||
recyclerViewProxy.startSmoothScroll(scroller);
|
||||
}
|
||||
|
||||
public void startSmoothPendingScroll(int position) {
|
||||
if (currentPosition == position) return;
|
||||
pendingScroll = -scrolled;
|
||||
Direction direction = Direction.fromDelta(position - currentPosition);
|
||||
int distanceToScroll = Math.abs(position - currentPosition) * scrollToChangeCurrent;
|
||||
pendingScroll += direction.applyTo(distanceToScroll);
|
||||
pendingPosition = position;
|
||||
startSmoothPendingScroll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isAutoMeasureEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeVerticalScrollRange(RecyclerView.State state) {
|
||||
return computeScrollRange(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeVerticalScrollOffset(RecyclerView.State state) {
|
||||
return computeScrollOffset(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeVerticalScrollExtent(RecyclerView.State state) {
|
||||
return computeScrollExtent(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeHorizontalScrollRange(RecyclerView.State state) {
|
||||
return computeScrollRange(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeHorizontalScrollOffset(RecyclerView.State state) {
|
||||
return computeScrollOffset(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeHorizontalScrollExtent(RecyclerView.State state) {
|
||||
return computeScrollExtent(state);
|
||||
}
|
||||
|
||||
private int computeScrollOffset(RecyclerView.State state) {
|
||||
int scrollbarSize = computeScrollExtent(state);
|
||||
int offset = (int) ((scrolled / (float) scrollToChangeCurrent) * scrollbarSize);
|
||||
return (currentPosition * scrollbarSize) + offset;
|
||||
}
|
||||
|
||||
private int computeScrollExtent(RecyclerView.State state) {
|
||||
if (getItemCount() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return (int) (computeScrollRange(state) / (float) getItemCount());
|
||||
}
|
||||
}
|
||||
|
||||
private int computeScrollRange(RecyclerView.State state) {
|
||||
if (getItemCount() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return scrollToChangeCurrent * (getItemCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
|
||||
pendingPosition = NO_POSITION;
|
||||
scrolled = pendingScroll = 0;
|
||||
if (newAdapter instanceof InitialPositionProvider) {
|
||||
currentPosition = ((InitialPositionProvider) newAdapter).getInitialPosition();
|
||||
} else {
|
||||
currentPosition = 0;
|
||||
}
|
||||
recyclerViewProxy.removeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
Bundle bundle = new Bundle();
|
||||
if (pendingPosition != NO_POSITION) {
|
||||
currentPosition = pendingPosition;
|
||||
}
|
||||
bundle.putInt(EXTRA_POSITION, currentPosition);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
currentPosition = bundle.getInt(EXTRA_POSITION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new RecyclerView.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
public int getNextPosition() {
|
||||
if (scrolled == 0) {
|
||||
return currentPosition;
|
||||
} else if (pendingPosition != NO_POSITION) {
|
||||
return pendingPosition;
|
||||
} else {
|
||||
return currentPosition + Direction.fromDelta(scrolled).applyTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void setItemTransformer(DiscreteScrollItemTransformer itemTransformer) {
|
||||
this.itemTransformer = itemTransformer;
|
||||
}
|
||||
|
||||
public void setTimeForItemSettle(int timeForItemSettle) {
|
||||
this.timeForItemSettle = timeForItemSettle;
|
||||
}
|
||||
|
||||
public void setOffscreenItems(int offscreenItems) {
|
||||
this.offscreenItems = offscreenItems;
|
||||
extraLayoutSpace = scrollToChangeCurrent * offscreenItems;
|
||||
recyclerViewProxy.requestLayout();
|
||||
}
|
||||
|
||||
public void setTransformClampItemCount(int transformClampItemCount) {
|
||||
this.transformClampItemCount = transformClampItemCount;
|
||||
applyItemTransformToChildren();
|
||||
}
|
||||
|
||||
public void setOrientation(DSVOrientation orientation) {
|
||||
orientationHelper = orientation.createHelper();
|
||||
recyclerViewProxy.removeAllViews();
|
||||
recyclerViewProxy.requestLayout();
|
||||
}
|
||||
|
||||
public void setShouldSlideOnFling(boolean result) {
|
||||
shouldSlideOnFling = result;
|
||||
}
|
||||
|
||||
public void setSlideOnFlingThreshold(int threshold) {
|
||||
flingThreshold = threshold;
|
||||
}
|
||||
|
||||
public int getCurrentPosition() {
|
||||
return currentPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
|
||||
super.onInitializeAccessibilityEvent(event);
|
||||
if (recyclerViewProxy.getChildCount() > 0) {
|
||||
final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
|
||||
record.setFromIndex(getPosition(getFirstChild()));
|
||||
record.setToIndex(getPosition(getLastChild()));
|
||||
}
|
||||
}
|
||||
|
||||
private float getCenterRelativePositionOf(View v, int maxDistance) {
|
||||
float distanceFromCenter = orientationHelper.getDistanceFromCenter(recyclerCenter,
|
||||
getDecoratedLeft(v) + childHalfWidth,
|
||||
getDecoratedTop(v) + childHalfHeight);
|
||||
return Math.min(Math.max(-1f, distanceFromCenter / maxDistance), 1f);
|
||||
}
|
||||
|
||||
private int checkNewOnFlingPositionIsInBounds(int position) {
|
||||
final int itemCount = recyclerViewProxy.getItemCount();
|
||||
//The check is required in case slide through multiple items is turned on
|
||||
if (currentPosition != 0 && position < 0) {
|
||||
//If currentPosition == 0 && position < 0 we forbid scroll to the left,
|
||||
//but if currentPosition != 0 we can slide to the first item
|
||||
return 0;
|
||||
} else if (currentPosition != itemCount - 1 && position >= itemCount) {
|
||||
return itemCount - 1;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
private int getHowMuchIsLeftToScroll(int dx) {
|
||||
return Direction.fromDelta(dx).applyTo(scrollToChangeCurrent - Math.abs(scrolled));
|
||||
}
|
||||
|
||||
private boolean isAnotherItemCloserThanCurrent() {
|
||||
return Math.abs(scrolled) >= scrollToChangeCurrent * SCROLL_TO_SNAP_TO_ANOTHER_ITEM;
|
||||
}
|
||||
|
||||
public View getFirstChild() {
|
||||
return recyclerViewProxy.getChildAt(0);
|
||||
}
|
||||
|
||||
public View getLastChild() {
|
||||
return recyclerViewProxy.getChildAt(recyclerViewProxy.getChildCount() - 1);
|
||||
}
|
||||
|
||||
public int getExtraLayoutSpace() {
|
||||
return extraLayoutSpace;
|
||||
}
|
||||
|
||||
private void notifyScroll() {
|
||||
float amountToScroll = pendingPosition != NO_POSITION ?
|
||||
Math.abs(scrolled + pendingScroll) :
|
||||
scrollToChangeCurrent;
|
||||
float position = -Math.min(Math.max(-1f, scrolled / amountToScroll), 1f);
|
||||
scrollStateListener.onScroll(position);
|
||||
}
|
||||
|
||||
private boolean isInBounds(int itemPosition) {
|
||||
return itemPosition >= 0 && itemPosition < recyclerViewProxy.getItemCount();
|
||||
}
|
||||
|
||||
private boolean isViewVisible(Point viewCenter, int endBound) {
|
||||
return orientationHelper.isViewVisible(
|
||||
viewCenter, childHalfWidth, childHalfHeight,
|
||||
endBound, extraLayoutSpace);
|
||||
}
|
||||
|
||||
|
||||
public void setPendingScroll(int pendingScroll){
|
||||
this.pendingScroll=pendingScroll;
|
||||
}
|
||||
|
||||
private void checkTargetPosition(RecyclerView.State state, int targetPosition) {
|
||||
if (targetPosition < 0 || targetPosition >= state.getItemCount()) {
|
||||
throw new IllegalArgumentException(String.format(Locale.US,
|
||||
"target position out of bounds: position=%d, itemCount=%d",
|
||||
targetPosition, state.getItemCount()));
|
||||
}
|
||||
}
|
||||
|
||||
protected void setRecyclerViewProxy(RecyclerViewProxy recyclerViewProxy) {
|
||||
this.recyclerViewProxy = recyclerViewProxy;
|
||||
}
|
||||
|
||||
protected void setOrientationHelper(DSVOrientation.Helper orientationHelper) {
|
||||
this.orientationHelper = orientationHelper;
|
||||
}
|
||||
|
||||
private class DiscreteLinearSmoothScroller extends LinearSmoothScroller {
|
||||
|
||||
public DiscreteLinearSmoothScroller(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateDxToMakeVisible(View view, int snapPreference) {
|
||||
return orientationHelper.getPendingDx(-pendingScroll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateDyToMakeVisible(View view, int snapPreference) {
|
||||
return orientationHelper.getPendingDy(-pendingScroll);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int calculateTimeForScrolling(int dx) {
|
||||
float dist = Math.min(Math.abs(dx), scrollToChangeCurrent);
|
||||
return (int) (Math.max(0.01f, dist / scrollToChangeCurrent) * timeForItemSettle);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
return new PointF(
|
||||
orientationHelper.getPendingDx(pendingScroll),
|
||||
orientationHelper.getPendingDy(pendingScroll));
|
||||
}
|
||||
|
||||
@Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
|
||||
return 10f / displayMetrics.densityDpi;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ScrollStateListener {
|
||||
void onIsBoundReachedFlagChange(boolean isBoundReached);
|
||||
|
||||
void onScrollStart();
|
||||
|
||||
void onScrollEnd();
|
||||
|
||||
void onScroll(float currentViewPosition);
|
||||
|
||||
void onCurrentViewFirstLayout();
|
||||
|
||||
void onDataSetChangeChangedPosition();
|
||||
}
|
||||
|
||||
public interface InitialPositionProvider {
|
||||
int getInitialPosition();
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.mogo.module.common.R;
|
||||
import com.yarolegovich.discretescrollview.transform.DiscreteScrollItemTransformer;
|
||||
import com.yarolegovich.discretescrollview.util.ScrollListenerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 18.02.2017.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DiscreteScrollView extends RecyclerView {
|
||||
|
||||
public static final int NO_POSITION = DiscreteScrollLayoutManager.NO_POSITION;
|
||||
|
||||
private static final int DEFAULT_ORIENTATION = DSVOrientation.HORIZONTAL.ordinal();
|
||||
|
||||
private DiscreteScrollLayoutManager layoutManager;
|
||||
|
||||
private List<ScrollStateChangeListener> scrollStateChangeListeners;
|
||||
private List<OnItemChangedListener> onItemChangedListeners;
|
||||
|
||||
private boolean isOverScrollEnabled;
|
||||
|
||||
public DiscreteScrollView(Context context) {
|
||||
super(context);
|
||||
init(null);
|
||||
}
|
||||
|
||||
public DiscreteScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
public DiscreteScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
private void init(AttributeSet attrs) {
|
||||
scrollStateChangeListeners = new ArrayList<>();
|
||||
onItemChangedListeners = new ArrayList<>();
|
||||
|
||||
int orientation = DEFAULT_ORIENTATION;
|
||||
if (attrs != null) {
|
||||
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.DiscreteScrollView);
|
||||
orientation = ta.getInt(R.styleable.DiscreteScrollView_dsv_orientation, DEFAULT_ORIENTATION);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
isOverScrollEnabled = getOverScrollMode() != OVER_SCROLL_NEVER;
|
||||
|
||||
layoutManager = new DiscreteScrollLayoutManager(
|
||||
getContext(), new ScrollStateListener(),
|
||||
DSVOrientation.values()[orientation]);
|
||||
setLayoutManager(layoutManager);
|
||||
}
|
||||
|
||||
public void setRatio(float ratio){
|
||||
layoutManager.setRatio(ratio);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayoutManager(LayoutManager layout) {
|
||||
if (layout instanceof DiscreteScrollLayoutManager) {
|
||||
super.setLayoutManager(layout);
|
||||
} else {
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.dsv_ex_msg_dont_set_lm));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean fling(int velocityX, int velocityY) {
|
||||
boolean isFling = super.fling(velocityX, velocityY);
|
||||
if (isFling) {
|
||||
layoutManager.onFling(velocityX, velocityY);
|
||||
} else {
|
||||
layoutManager.returnToCurrentPosition();
|
||||
}
|
||||
return isFling;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ViewHolder getViewHolder(int position) {
|
||||
View view = layoutManager.findViewByPosition(position);
|
||||
return view != null ? getChildViewHolder(view) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return adapter position of the current item or -1 if nothing is selected
|
||||
*/
|
||||
public int getCurrentItem() {
|
||||
return layoutManager.getCurrentPosition();
|
||||
}
|
||||
|
||||
public void setItemTransformer(DiscreteScrollItemTransformer transformer) {
|
||||
layoutManager.setItemTransformer(transformer);
|
||||
}
|
||||
|
||||
public void setItemTransitionTimeMillis(@IntRange(from = 10) int millis) {
|
||||
layoutManager.setTimeForItemSettle(millis);
|
||||
}
|
||||
|
||||
public void setSlideOnFling(boolean result){
|
||||
layoutManager.setShouldSlideOnFling(result);
|
||||
}
|
||||
|
||||
public void setSlideOnFlingThreshold(int threshold){
|
||||
layoutManager.setSlideOnFlingThreshold(threshold);
|
||||
}
|
||||
|
||||
public void setOrientation(DSVOrientation orientation) {
|
||||
layoutManager.setOrientation(orientation);
|
||||
}
|
||||
|
||||
public void setOffscreenItems(int items) {
|
||||
layoutManager.setOffscreenItems(items);
|
||||
}
|
||||
|
||||
public void setClampTransformProgressAfter(@IntRange(from = 1) int itemCount) {
|
||||
if (itemCount <= 1) {
|
||||
throw new IllegalArgumentException("must be >= 1");
|
||||
}
|
||||
layoutManager.setTransformClampItemCount(itemCount);
|
||||
}
|
||||
|
||||
public void setOverScrollEnabled(boolean overScrollEnabled) {
|
||||
isOverScrollEnabled = overScrollEnabled;
|
||||
setOverScrollMode(OVER_SCROLL_NEVER);
|
||||
}
|
||||
|
||||
public void addScrollStateChangeListener(@NonNull ScrollStateChangeListener<?> scrollStateChangeListener) {
|
||||
scrollStateChangeListeners.add(scrollStateChangeListener);
|
||||
}
|
||||
|
||||
public void addScrollListener(@NonNull ScrollListener<?> scrollListener) {
|
||||
addScrollStateChangeListener(new ScrollListenerAdapter(scrollListener));
|
||||
}
|
||||
|
||||
public void addOnItemChangedListener(@NonNull OnItemChangedListener<?> onItemChangedListener) {
|
||||
onItemChangedListeners.add(onItemChangedListener);
|
||||
}
|
||||
|
||||
public void removeScrollStateChangeListener(@NonNull ScrollStateChangeListener<?> scrollStateChangeListener) {
|
||||
scrollStateChangeListeners.remove(scrollStateChangeListener);
|
||||
}
|
||||
|
||||
public void removeScrollListener(@NonNull ScrollListener<?> scrollListener) {
|
||||
removeScrollStateChangeListener(new ScrollListenerAdapter<>(scrollListener));
|
||||
}
|
||||
|
||||
public void removeItemChangedListener(@NonNull OnItemChangedListener<?> onItemChangedListener) {
|
||||
onItemChangedListeners.remove(onItemChangedListener);
|
||||
}
|
||||
|
||||
private void notifyScrollStart(ViewHolder holder, int current) {
|
||||
for (ScrollStateChangeListener listener : scrollStateChangeListeners) {
|
||||
listener.onScrollStart(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyScrollEnd(ViewHolder holder, int current) {
|
||||
for (ScrollStateChangeListener listener : scrollStateChangeListeners) {
|
||||
listener.onScrollEnd(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyScroll(float position,
|
||||
int currentIndex, int newIndex,
|
||||
ViewHolder currentHolder, ViewHolder newHolder) {
|
||||
for (ScrollStateChangeListener listener : scrollStateChangeListeners) {
|
||||
listener.onScroll(position, currentIndex, newIndex,
|
||||
currentHolder,
|
||||
newHolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCurrentItemChanged(ViewHolder holder, int current) {
|
||||
for (OnItemChangedListener listener : onItemChangedListeners) {
|
||||
listener.onCurrentItemChanged(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCurrentItemChanged() {
|
||||
if (onItemChangedListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int current = layoutManager.getCurrentPosition();
|
||||
ViewHolder currentHolder = getViewHolder(current);
|
||||
notifyCurrentItemChanged(currentHolder, current);
|
||||
}
|
||||
|
||||
public void setPendingScroll(int PendingScroll ) {
|
||||
layoutManager.setPendingScroll(PendingScroll);
|
||||
}
|
||||
|
||||
private class ScrollStateListener implements DiscreteScrollLayoutManager.ScrollStateListener {
|
||||
|
||||
@Override
|
||||
public void onIsBoundReachedFlagChange(boolean isBoundReached) {
|
||||
if (isOverScrollEnabled) {
|
||||
setOverScrollMode(isBoundReached ? OVER_SCROLL_ALWAYS : OVER_SCROLL_NEVER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStart() {
|
||||
if (scrollStateChangeListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int current = layoutManager.getCurrentPosition();
|
||||
ViewHolder holder = getViewHolder(current);
|
||||
if (holder != null) {
|
||||
notifyScrollStart(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollEnd() {
|
||||
if (onItemChangedListeners.isEmpty() && scrollStateChangeListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int current = layoutManager.getCurrentPosition();
|
||||
ViewHolder holder = getViewHolder(current);
|
||||
if (holder != null) {
|
||||
notifyScrollEnd(holder, current);
|
||||
notifyCurrentItemChanged(holder, current);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(float currentViewPosition) {
|
||||
if (scrollStateChangeListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int currentIndex = getCurrentItem();
|
||||
int newIndex = layoutManager.getNextPosition();
|
||||
if (currentIndex != newIndex) {
|
||||
notifyScroll(currentViewPosition,
|
||||
currentIndex, newIndex,
|
||||
getViewHolder(currentIndex),
|
||||
getViewHolder(newIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentViewFirstLayout() {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
notifyCurrentItemChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataSetChangeChangedPosition() {
|
||||
notifyCurrentItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ScrollStateChangeListener<T extends ViewHolder> {
|
||||
|
||||
void onScrollStart(@NonNull T currentItemHolder, int adapterPosition);
|
||||
|
||||
void onScrollEnd(@NonNull T currentItemHolder, int adapterPosition);
|
||||
|
||||
void onScroll(float scrollPosition,
|
||||
int currentPosition,
|
||||
int newPosition,
|
||||
@Nullable T currentHolder,
|
||||
@Nullable T newCurrent);
|
||||
}
|
||||
|
||||
public interface ScrollListener<T extends ViewHolder> {
|
||||
|
||||
void onScroll(float scrollPosition,
|
||||
int currentPosition, int newPosition,
|
||||
@Nullable T currentHolder,
|
||||
@Nullable T newCurrent);
|
||||
}
|
||||
|
||||
public interface OnItemChangedListener<T extends ViewHolder> {
|
||||
/*
|
||||
* This method will be also triggered when view appears on the screen for the first time.
|
||||
* If data set is empty, viewHolder will be null and adapterPosition will be NO_POSITION
|
||||
*/
|
||||
void onCurrentItemChanged(@Nullable T viewHolder, int adapterPosition);
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.mogo.module.common.R;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 28-Apr-17.
|
||||
*/
|
||||
|
||||
public class InfiniteScrollAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T>
|
||||
implements DiscreteScrollLayoutManager.InitialPositionProvider {
|
||||
|
||||
private static final int CENTER = Integer.MAX_VALUE / 2;
|
||||
private static final int RESET_BOUND = 100;
|
||||
|
||||
public static <T extends RecyclerView.ViewHolder> InfiniteScrollAdapter<T> wrap(
|
||||
@NonNull RecyclerView.Adapter<T> adapter) {
|
||||
return new InfiniteScrollAdapter<>(adapter);
|
||||
}
|
||||
|
||||
private RecyclerView.Adapter<T> wrapped;
|
||||
private DiscreteScrollLayoutManager layoutManager;
|
||||
|
||||
public InfiniteScrollAdapter(@NonNull RecyclerView.Adapter<T> wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
this.wrapped.registerAdapterDataObserver(new DataSetChangeDelegate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
wrapped.onAttachedToRecyclerView(recyclerView);
|
||||
if (recyclerView instanceof DiscreteScrollView) {
|
||||
layoutManager = (DiscreteScrollLayoutManager) recyclerView.getLayoutManager();
|
||||
} else {
|
||||
String msg = recyclerView.getContext().getString(R.string.dsv_ex_msg_adapter_wrong_recycler);
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
wrapped.onDetachedFromRecyclerView(recyclerView);
|
||||
layoutManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull T onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return wrapped.onCreateViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull T holder, int position) {
|
||||
if (isResetRequired(position)) {
|
||||
int resetPosition = CENTER + mapPositionToReal(layoutManager.getCurrentPosition());
|
||||
setPosition(resetPosition);
|
||||
return;
|
||||
}
|
||||
wrapped.onBindViewHolder(holder, mapPositionToReal(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return wrapped.getItemViewType(mapPositionToReal(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return isInfinite() ? Integer.MAX_VALUE : wrapped.getItemCount();
|
||||
}
|
||||
|
||||
public int getRealItemCount() {
|
||||
return wrapped.getItemCount();
|
||||
}
|
||||
|
||||
public int getRealCurrentPosition() {
|
||||
return getRealPosition(layoutManager.getCurrentPosition());
|
||||
}
|
||||
|
||||
public int getRealPosition(int position) {
|
||||
return mapPositionToReal(position);
|
||||
}
|
||||
|
||||
public int getClosestPosition(int position) {
|
||||
ensureValidPosition(position);
|
||||
int adapterCurrent = layoutManager.getCurrentPosition();
|
||||
int current = mapPositionToReal(adapterCurrent);
|
||||
if (position == current) {
|
||||
return adapterCurrent;
|
||||
}
|
||||
int delta = position - current;
|
||||
int target = adapterCurrent + delta;
|
||||
int wraparoundTarget = adapterCurrent + (position > current ?
|
||||
delta - wrapped.getItemCount() :
|
||||
wrapped.getItemCount() + delta);
|
||||
int distance = Math.abs(adapterCurrent - target);
|
||||
int wraparoundDistance = Math.abs(adapterCurrent - wraparoundTarget);
|
||||
if (distance == wraparoundDistance) {
|
||||
//Scroll to the right feels more natural, so prefer it
|
||||
return target > adapterCurrent ? target : wraparoundTarget;
|
||||
} else {
|
||||
return distance < wraparoundDistance ? target : wraparoundTarget;
|
||||
}
|
||||
}
|
||||
|
||||
private int mapPositionToReal(int position) {
|
||||
if (position < CENTER) {
|
||||
int rem = (CENTER - position) % wrapped.getItemCount();
|
||||
return rem == 0 ? 0 : wrapped.getItemCount() - rem;
|
||||
} else {
|
||||
return (position - CENTER) % wrapped.getItemCount();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isResetRequired(int requestedPosition) {
|
||||
return isInfinite()
|
||||
&& (requestedPosition <= RESET_BOUND
|
||||
|| requestedPosition >= (Integer.MAX_VALUE - RESET_BOUND));
|
||||
}
|
||||
|
||||
private void ensureValidPosition(int position) {
|
||||
if (position >= wrapped.getItemCount()) {
|
||||
throw new IndexOutOfBoundsException(String.format(Locale.US,
|
||||
"requested position is outside adapter's bounds: position=%d, size=%d",
|
||||
position, wrapped.getItemCount()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInfinite() {
|
||||
return wrapped.getItemCount() > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInitialPosition() {
|
||||
return isInfinite() ? CENTER : 0;
|
||||
}
|
||||
|
||||
private void setPosition(int position) {
|
||||
layoutManager.scrollToPosition(position);
|
||||
}
|
||||
|
||||
//TODO: handle proper data set change notifications
|
||||
private class DataSetChangeDelegate extends RecyclerView.AdapterDataObserver {
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
setPosition(getInitialPosition());
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
notifyItemRangeChanged(0, getItemCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
|
||||
notifyItemRangeChanged(0, getItemCount(), payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 10/25/17.
|
||||
*/
|
||||
public class RecyclerViewProxy {
|
||||
|
||||
private RecyclerView.LayoutManager layoutManager;
|
||||
|
||||
public RecyclerViewProxy(@NonNull RecyclerView.LayoutManager layoutManager) {
|
||||
this.layoutManager = layoutManager;
|
||||
}
|
||||
|
||||
public void attachView(View view) {
|
||||
layoutManager.attachView(view);
|
||||
}
|
||||
|
||||
public void detachView(View view) {
|
||||
layoutManager.detachView(view);
|
||||
}
|
||||
|
||||
public void detachAndScrapView(View view, RecyclerView.Recycler recycler) {
|
||||
layoutManager.detachAndScrapView(view, recycler);
|
||||
}
|
||||
|
||||
public void detachAndScrapAttachedViews(RecyclerView.Recycler recycler) {
|
||||
layoutManager.detachAndScrapAttachedViews(recycler);
|
||||
}
|
||||
|
||||
public void recycleView(View view, RecyclerView.Recycler recycler) {
|
||||
recycler.recycleView(view);
|
||||
}
|
||||
|
||||
public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
|
||||
layoutManager.removeAndRecycleAllViews(recycler);
|
||||
}
|
||||
|
||||
public int getChildCount() {
|
||||
return layoutManager.getChildCount();
|
||||
}
|
||||
|
||||
public int getItemCount() {
|
||||
return layoutManager.getItemCount();
|
||||
}
|
||||
|
||||
public View getMeasuredChildForAdapterPosition(int position, RecyclerView.Recycler recycler) {
|
||||
View view = recycler.getViewForPosition(position);
|
||||
layoutManager.addView(view);
|
||||
layoutManager.measureChildWithMargins(view, 0, 0);
|
||||
return view;
|
||||
}
|
||||
|
||||
public void layoutDecoratedWithMargins(View v, int left, int top, int right, int bottom) {
|
||||
layoutManager.layoutDecoratedWithMargins(v, left, top, right, bottom);
|
||||
}
|
||||
|
||||
public View getChildAt(int index) {
|
||||
return layoutManager.getChildAt(index);
|
||||
}
|
||||
|
||||
public int getPosition(View view) {
|
||||
return layoutManager.getPosition(view);
|
||||
}
|
||||
|
||||
public int getMeasuredWidthWithMargin(View child) {
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
|
||||
return layoutManager.getDecoratedMeasuredWidth(child) + lp.leftMargin + lp.rightMargin;
|
||||
}
|
||||
|
||||
public int getMeasuredHeightWithMargin(View child) {
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
|
||||
return layoutManager.getDecoratedMeasuredHeight(child) + lp.topMargin + lp.bottomMargin;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return layoutManager.getWidth();
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return layoutManager.getHeight();
|
||||
}
|
||||
|
||||
public void offsetChildrenHorizontal(int amount) {
|
||||
layoutManager.offsetChildrenHorizontal(amount);
|
||||
}
|
||||
|
||||
public void offsetChildrenVertical(int amount) {
|
||||
layoutManager.offsetChildrenVertical(amount);
|
||||
}
|
||||
|
||||
public void requestLayout() {
|
||||
layoutManager.requestLayout();
|
||||
}
|
||||
|
||||
public void startSmoothScroll(RecyclerView.SmoothScroller smoothScroller) {
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
public void removeAllViews() {
|
||||
layoutManager.removeAllViews();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview.transform;
|
||||
|
||||
import android.view.View;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 02.03.2017.
|
||||
*/
|
||||
|
||||
public interface DiscreteScrollItemTransformer {
|
||||
void transformItem(View item, RecyclerView.ViewHolder childViewHolder,float position);
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview.transform;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 03.03.2017.
|
||||
*/
|
||||
|
||||
public class Pivot {
|
||||
|
||||
public static final int AXIS_X = 0;
|
||||
public static final int AXIS_Y = 1;
|
||||
|
||||
private static final int PIVOT_CENTER = -1;
|
||||
private static final int PIVOT_MAX = -2;
|
||||
|
||||
private int axis;
|
||||
private int pivotPoint;
|
||||
|
||||
public Pivot(@Axis int axis, int pivotPoint) {
|
||||
this.axis = axis;
|
||||
this.pivotPoint = pivotPoint;
|
||||
}
|
||||
|
||||
public void setOn(View view) {
|
||||
if (axis == AXIS_X) {
|
||||
switch (pivotPoint) {
|
||||
case PIVOT_CENTER:
|
||||
view.setPivotX(view.getWidth() * 0.5f);
|
||||
break;
|
||||
case PIVOT_MAX:
|
||||
view.setPivotX(view.getWidth());
|
||||
break;
|
||||
default:
|
||||
view.setPivotX(pivotPoint);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (axis == AXIS_Y) {
|
||||
switch (pivotPoint) {
|
||||
case PIVOT_CENTER:
|
||||
view.setPivotY(view.getHeight() * 0.5f);
|
||||
break;
|
||||
case PIVOT_MAX:
|
||||
view.setPivotY(view.getHeight());
|
||||
break;
|
||||
default:
|
||||
view.setPivotY(pivotPoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Axis
|
||||
public int getAxis() {
|
||||
return axis;
|
||||
}
|
||||
|
||||
public enum X {
|
||||
LEFT {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_X, 0);
|
||||
}
|
||||
},
|
||||
CENTER {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_X, PIVOT_CENTER);
|
||||
}
|
||||
},
|
||||
RIGHT {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_X, PIVOT_MAX);
|
||||
}
|
||||
};
|
||||
|
||||
public abstract Pivot create();
|
||||
}
|
||||
|
||||
public enum Y {
|
||||
TOP {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_Y, 0);
|
||||
}
|
||||
},
|
||||
CENTER {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_Y, PIVOT_CENTER);
|
||||
}
|
||||
},
|
||||
BOTTOM {
|
||||
@Override
|
||||
public Pivot create() {
|
||||
return new Pivot(AXIS_Y, PIVOT_MAX);
|
||||
}
|
||||
};
|
||||
|
||||
public abstract Pivot create();
|
||||
}
|
||||
|
||||
@IntDef({AXIS_X, AXIS_Y})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Axis{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview.transform;
|
||||
|
||||
import android.view.View;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 03.03.2017.
|
||||
*/
|
||||
public class ScaleTransformer implements DiscreteScrollItemTransformer {
|
||||
|
||||
private Pivot pivotX;
|
||||
private Pivot pivotY;
|
||||
private float minScale;
|
||||
private float maxMinDiff;
|
||||
|
||||
public ScaleTransformer() {
|
||||
pivotX = Pivot.X.CENTER.create();
|
||||
pivotY = Pivot.Y.CENTER.create();
|
||||
minScale = 0.8f;
|
||||
maxMinDiff = 0.2f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformItem(View item, RecyclerView.ViewHolder childViewHolder, float position) {
|
||||
pivotX.setOn(item);
|
||||
pivotY.setOn(item);
|
||||
float closenessToCenter = 1f - Math.abs(position);
|
||||
float scale = minScale + maxMinDiff * closenessToCenter;
|
||||
item.setScaleX(scale);
|
||||
item.setScaleY(scale);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private ScaleTransformer transformer;
|
||||
private float maxScale;
|
||||
|
||||
public Builder() {
|
||||
transformer = new ScaleTransformer();
|
||||
maxScale = 1f;
|
||||
}
|
||||
|
||||
public Builder setMinScale(@FloatRange(from = 0.01) float scale) {
|
||||
transformer.minScale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxScale(@FloatRange(from = 0.01) float scale) {
|
||||
maxScale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPivotX(Pivot.X pivotX) {
|
||||
return setPivotX(pivotX.create());
|
||||
}
|
||||
|
||||
public Builder setPivotX(Pivot pivot) {
|
||||
assertAxis(pivot, Pivot.AXIS_X);
|
||||
transformer.pivotX = pivot;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPivotY(Pivot.Y pivotY) {
|
||||
return setPivotY(pivotY.create());
|
||||
}
|
||||
|
||||
public Builder setPivotY(Pivot pivot) {
|
||||
assertAxis(pivot, Pivot.AXIS_Y);
|
||||
transformer.pivotY = pivot;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScaleTransformer build() {
|
||||
transformer.maxMinDiff = maxScale - transformer.minScale;
|
||||
return transformer;
|
||||
}
|
||||
|
||||
private void assertAxis(Pivot pivot, @Pivot.Axis int axis) {
|
||||
if (pivot.getAxis() != axis) {
|
||||
throw new IllegalArgumentException("You passed a Pivot for wrong axis.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.yarolegovich.discretescrollview.util;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.yarolegovich.discretescrollview.DiscreteScrollView;
|
||||
|
||||
/**
|
||||
* Created by yarolegovich on 16.03.2017.
|
||||
*/
|
||||
public class ScrollListenerAdapter<T extends RecyclerView.ViewHolder> implements DiscreteScrollView.ScrollStateChangeListener<T> {
|
||||
|
||||
private DiscreteScrollView.ScrollListener<T> adaptee;
|
||||
|
||||
public ScrollListenerAdapter(@NonNull DiscreteScrollView.ScrollListener<T> adaptee) {
|
||||
this.adaptee = adaptee;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStart(@NonNull T currentItemHolder, int adapterPosition) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollEnd(@NonNull T currentItemHolder, int adapterPosition) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(float scrollPosition,
|
||||
int currentIndex, int newIndex,
|
||||
@Nullable T currentHolder, @Nullable T newCurrentHolder) {
|
||||
adaptee.onScroll(scrollPosition, currentIndex, newIndex, currentHolder, newCurrentHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ScrollListenerAdapter) {
|
||||
return adaptee.equals(((ScrollListenerAdapter) obj).adaptee);
|
||||
} else {
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user