需求就是图中的效果,最大的那个item是选中的item,其余的是普通的item,并且滑动过程中随收视伴随item缩放的效果,先声明下,这个不是动画效果,只是根据手势滑动的距离不断设置item缩放的比例。
刚开始想这个效果是不是可以用属性动画中的缩放动画实现,但是用动画实现的话需要设置动画的执行时间,ValueAnimator只能实现过度效果但是item的大小是与手指滑动的距离息息相关的,用动画去实现显得和这个需求不相符,既然与滑动的距离相关,那么就根据滑动的距离的值去判断item应该设置的大小来实现这个需求是更合理的。
控件的选择上准备用RecyclerView+SnapHelper实现,可能很多朋友不熟悉SnapHelper的作用,SnapHelper旨在支持RecyclerView的对其方式,也就是计算对齐RecyclerView中TargetView的指定点或者容器中的任何像素点,简单点理解就是计算item停留的位置。滑动的距离通过实现RecyclerView的RecyclerView.OnScrollListener接口,重写onScrolled()和onScrollStateChanged()判断当前的滑动状态与滑动距离,item的摆放是从中间向两边摆,计算当前item距离中心点的距离进而计算item应该缩放的比例。
该类的作用主要是处理手势,SnapHelper与RecyclerView的关联,计算缩放比例。
public class GalleryLayoutManager extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider {
private static final String TAG = "GalleryLayoutManager";
final static int LAYOUT_START = -1;
final static int LAYOUT_END = 1;
public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
public static final int VERTICAL = OrientationHelper.VERTICAL;
public static final long ANIM_INTERVAL = 150L;
public static final long ANIM_OFFSET = 50L;
private int mFirstVisiblePosition = 0;
private int mLastVisiblePos = 0;
private int mInitialSelectedPosition = 0;
public void setInitialSelectedPosition(int initialSelectedPosition) {
mInitialSelectedPosition = initialSelectedPosition;
}
private int mCurSelectedPosition = -1;
private int mPendingScrollPosition = NO_POSITION;
private View mCurSelectedView;
/**
* Scroll state
*/
private State mState;
private LinearSnapHelper mSnapHelper = new LinearSnapHelper();
private InnerScrollListener mInnerScrollListener = new InnerScrollListener();
private boolean mCallbackInFling = false;
/**
* Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
*/
private int mOrientation = HORIZONTAL;
private OrientationHelper mHorizontalHelper;
private OrientationHelper mVerticalHelper;
private AnimatorSet mAnimatorSet;
public GalleryLayoutManager(int orientation) {
mOrientation = orientation;
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
if (mOrientation == VERTICAL) {
return new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
} else {
return new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
}
@Override
public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
return new LayoutParams(c, attrs);
}
@Override
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (lp instanceof ViewGroup.MarginLayoutParams) {
return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
} else {
return new LayoutParams(lp);
}
}
@Override
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
return lp instanceof LayoutParams;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getItemCount() == 0) {
// reset();
detachAndScrapAttachedViews(recycler);
return;
}
if (state.isPreLayout()) {
return;
}
if (state.getItemCount() != 0 && !state.didStructureChange()) {
return;
}
if (getChildCount() == 0 || state.didStructureChange()) {
// reset();
}
if (mPendingScrollPosition != NO_POSITION) {
mInitialSelectedPosition = mPendingScrollPosition;
}
mInitialSelectedPosition = Math.min(Math.max(0, mInitialSelectedPosition), getItemCount() - 1);
detachAndScrapAttachedViews(recycler);
firstFillCover(recycler, state, 0);
}
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
mPendingScrollPosition = NO_POSITION;
}
private void reset() {
if (mState != null) {
mState.mItemsFrames.clear();
}
//when data set update keep the last selected position
if (mCurSelectedPosition != -1) {
mInitialSelectedPosition = mCurSelectedPosition;
}
mInitialSelectedPosition = Math.min(Math.max(0, mInitialSelectedPosition), getItemCount() - 1);
mFirstVisiblePosition = mInitialSelectedPosition;
mLastVisiblePos = mInitialSelectedPosition;
mCurSelectedPosition = -1;
if (mCurSelectedView != null) {
mCurSelectedView.setSelected(false);
mCurSelectedView = null;
}
}
private void firstFillCover(RecyclerView.Recycler recycler, RecyclerView.State state, int scrollDelta) {
if (mOrientation == HORIZONTAL) {
firstFillWithHorizontal(recycler, state);
} else {
firstFillWithVertical(recycler, state);
}
if (mItemTransformer != null) {
View child;
for (int i = 0; i < getChildCount(); i++) {
child = getChildAt(i);
mItemTransformer.transformItem(this, child, calculateToCenterFraction(child, scrollDelta));
}
}
mInnerScrollListener.onScrolled(mRecyclerView, 0, 0);
}
/**
* Layout the item view witch position specified by {@link GalleryLayoutManager#mInitialSelectedPosition} first and then layout the other
*
* @param recycler
* @param state
*/
private void firstFillWithHorizontal(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler);
int leftEdge = getOrientationHelper().getStartAfterPadding();
int rightEdge = getOrientationHelper().getEndAfterPadding();
int startPosition = mInitialSelectedPosition;
int scrapWidth, scrapHeight;
Rect scrapRect = new Rect();
int height = getVerticalSpace();
int topOffset;
//layout the init position view
View scrap = recycler.getViewForPosition(mInitialSelectedPosition);
addView(scrap, 0);
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
topOffset = (int) (getPaddingTop() + (height - scrapHeight) / 2.0f);
int left = (int) (getPaddingLeft() + (getHorizontalSpace() - scrapWidth) / 2.f);
scrapRect.set(left, topOffset, left + scrapWidth, topOffset + scrapHeight);
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
if (getState().mItemsFrames.get(startPosition) == null) {
getState().mItemsFrames.put(startPosition, scrapRect);
} else {
getState().mItemsFrames.get(startPosition).set(scrapRect);
}
mFirstVisiblePosition = mLastVisiblePos = startPosition;
int leftStartOffset = getDecoratedLeft(scrap);
int rightStartOffset = getDecoratedRight(scrap);
//fill left of center
fillLeft(recycler, mInitialSelectedPosition - 1, leftStartOffset, leftEdge);
//fill right of center
fillRight(recycler, mInitialSelectedPosition + 1, rightStartOffset, rightEdge);
}
@Override
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
super.onItemsRemoved(recyclerView, positionStart, itemCount);
}
/**
* Layout the item view witch position special by {@link GalleryLayoutManager#mInitialSelectedPosition} first and then layout the other
*
* @param recycler
* @param state
*/
private void firstFillWithVertical(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler);
int topEdge = getOrientationHelper().getStartAfterPadding();
int bottomEdge = getOrientationHelper().getEndAfterPadding();
int startPosition = mInitialSelectedPosition;
int scrapWidth, scrapHeight;
Rect scrapRect = new Rect();
int width = getHorizontalSpace();
int leftOffset;
//layout the init position view
View scrap = recycler.getViewForPosition(mInitialSelectedPosition);
addView(scrap, 0);
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
leftOffset = (int) (getPaddingLeft() + (width - scrapWidth) / 2.0f);
int top = (int) (getPaddingTop() + (getVerticalSpace() - scrapHeight) / 2.f);
scrapRect.set(leftOffset, top, leftOffset + scrapWidth, top + scrapHeight);
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
if (getState().mItemsFrames.get(startPosition) == null) {
getState().mItemsFrames.put(startPosition, scrapRect);
} else {
getState().mItemsFrames.get(startPosition).set(scrapRect);
}
mFirstVisiblePosition = mLastVisiblePos = startPosition;
int topStartOffset = getDecoratedTop(scrap);
int bottomStartOffset = getDecoratedBottom(scrap);
//fill left of center
fillTop(recycler, mInitialSelectedPosition - 1, topStartOffset, topEdge);
//fill right of center
fillBottom(recycler, mInitialSelectedPosition + 1, bottomStartOffset, bottomEdge);
}
/**
* Fill left of the center view
*
* @param recycler
* @param startPosition start position to fill left
* @param startOffset layout start offset
* @param leftEdge
*/
private void fillLeft(RecyclerView.Recycler recycler, int startPosition, int startOffset, int leftEdge) {
View scrap;
int topOffset;
int scrapWidth, scrapHeight;
Rect scrapRect = new Rect();
int height = getVerticalSpace();
for (int i = startPosition; i >= 0 && startOffset > leftEdge; i--) {
scrap = recycler.getViewForPosition(i);
addView(scrap, 0);
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
topOffset = (int) (getPaddingTop() + (height - scrapHeight) / 2.0f);
scrapRect.set(startOffset - scrapWidth, topOffset, startOffset, topOffset + scrapHeight);
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
startOffset = scrapRect.left;
mFirstVisiblePosition = i;
if (getState().mItemsFrames.get(i) == null) {
getState().mItemsFrames.put(i, scrapRect);
} else {
getState().mItemsFrames.get(i).set(scrapRect);
}
}
}
/**
* Fill right of the center view
*
* @param recycler
* @param startPosition start position to fill right
* @param startOffset layout start offset
* @param rightEdge
*/
private void fillRight(RecyclerView.Recycler recycler, int startPosition, int startOffset, int rightEdge) {
View scrap;
int topOffset;
int scrapWidth, scrapHeight;
Rect scrapRect = new Rect();
int height = getVerticalSpace();
for (int i = startPosition; i < getItemCount() && startOffset < rightEdge; i++) {
scrap = recycler.getViewForPosition(i);
addView(scrap);
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
topOffset = (int) (getPaddingTop() + (height - scrapHeight) / 2.0f);
scrapRect.set(startOffset, topOffset, startOffset + scrapWidth, topOffset + scrapHeight);
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
startOffset = scrapRect.right;
mLastVisiblePos = i;
if (getState().mItemsFrames.get(i) == null) {
getState().mItemsFrames.put(i, scrapRect);
} else {
getState().mItemsFrames.get(i).set(scrapRect);
}
}
}
/**
* Fill top of the center view
*
* @param recycler
* @param startPosition start position to fill top
* @param startOffset layout start offset
* @param topEdge top edge of the RecycleView
*/
private void fillTop(RecyclerView.Recycler recycler, int startPosition, int startOffset, int topEdge) {
View scrap;
int leftOffset;
int scrapWidth, scrapHeight;
Rect scrapRect = new Rect();
int width = getHorizontalSpace();
for (int i = startPosition; i >= 0 && startOffset > topEdge; i--) {
scrap = recycler.getViewForPosition(i);
addView(scrap, 0);
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
leftOffset = (int) (getPaddingLeft() + (width - scrapWidth) / 2.0f);
scrapRect.set(leftOffset, startOffset - scrapHeight, leftOffset + scrapWidth, startOffset);
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
startOffset = scrapRect.top;
mFirstVisiblePosition = i;
if (getState().mItemsFrames.get(i) == null) {
getState().mItemsFrames.put(i, scrapRect);
} else {
getState().mItemsFrames.get(i).set(scrapRect);
}
}
}
/**
* Fill bottom of the center view
*
* @param recycler
* @param startPosition start position to fill bottom
* @param startOffset layout start offset
* @param bottomEdge bottom edge of the RecycleView
*/
private void fillBottom(RecyclerView.Recycler recycler, int startPosition, int startOffset, int bottomEdge) {
View scrap;
int leftOffset;
int scrapWidth, scrapHeight;
Rect scrapRect = new Rect();
int width = getHorizontalSpace();
for (int i = startPosition; i < getItemCount() && startOffset < bottomEdge; i++) {
scrap = recycler.getViewForPosition(i);
addView(scrap);
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
leftOffset = (int) (getPaddingLeft() + (width - scrapWidth) / 2.0f);
scrapRect.set(leftOffset, startOffset, leftOffset + scrapWidth, startOffset + scrapHeight);
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
startOffset = scrapRect.bottom;
mLastVisiblePos = i;
if (getState().mItemsFrames.get(i) == null) {
getState().mItemsFrames.put(i, scrapRect);
} else {
getState().mItemsFrames.get(i).set(scrapRect);
}
}
}
private void fillCover(RecyclerView.Recycler recycler, RecyclerView.State state, int scrollDelta) {
if (getItemCount() == 0) {
return;
}
if (mOrientation == HORIZONTAL) {
fillWithHorizontal(recycler, state, scrollDelta);
} else {
fillWithVertical(recycler, state, scrollDelta);
}
if (mItemTransformer != null) {
View child;
for (int i = 0; i < getChildCount(); i++) {
child = getChildAt(i);
mItemTransformer.transformItem(this, child, calculateToCenterFraction(child, scrollDelta));
}
}
}
//计算view应该缩放的比例,返回值是返回的0到1直接的值,1表示item等于普通的大小,0是最大的那个item
private float calculateToCenterFraction(View child, float pendingOffset) {
int distance = calculateDistanceCenter(child, pendingOffset);
int childLength = mOrientation == GalleryLayoutManager.HORIZONTAL ? child.getWidth() : child.getHeight();
return Math.max(-1.f, Math.min(1.f, distance * 1.f / childLength));
}
/**
* @param child
* @param pendingOffset child view will scroll by
* @return
*/
private int calculateDistanceCenter(View child, float pendingOffset) {
OrientationHelper orientationHelper = getOrientationHelper();
int parentCenter = (orientationHelper.getEndAfterPadding() - orientationHelper.getStartAfterPadding()) / 2 + orientationHelper.getStartAfterPadding();
if (mOrientation == GalleryLayoutManager.HORIZONTAL) {
return (int) (child.getWidth() / 2 - pendingOffset + child.getLeft() - parentCenter);
} else {
return (int) (child.getHeight() / 2 - pendingOffset + child.getTop() - parentCenter);
}
}
/**
* @param recycler
* @param state
* @param dy
*/
private void fillWithVertical(RecyclerView.Recycler recycler, RecyclerView.State state, int dy) {
int topEdge = getOrientationHelper().getStartAfterPadding();
int bottomEdge = getOrientationHelper().getEndAfterPadding();
//1.remove and recycle the view that disappear in screen
View child;
if (getChildCount() > 0) {
if (dy >= 0) {
//remove and recycle the top off screen view
int fixIndex = 0;
for (int i = 0; i < getChildCount(); i++) {
child = getChildAt(i + fixIndex);
if (getDecoratedBottom(child) - dy < topEdge) {
removeAndRecycleView(child, recycler);
mFirstVisiblePosition++;
fixIndex--;
} else {
break;
}
}
} else { //dy<0
//remove and recycle the bottom off screen view
for (int i = getChildCount() - 1; i >= 0; i--) {
child = getChildAt(i);
if (getDecoratedTop(child) - dy > bottomEdge) {
removeAndRecycleView(child, recycler);
mLastVisiblePos--;
} else {
break;
}
}
}
}
int startPosition = mFirstVisiblePosition;
int startOffset = -1;
int scrapWidth, scrapHeight;
Rect scrapRect;
int width = getHorizontalSpace();
int leftOffset;
View scrap;
//2.Add or reattach item view to fill screen
if (dy >= 0) {
if (getChildCount() != 0) {
View lastView = getChildAt(getChildCount() - 1);
startPosition = getPosition(lastView) + 1;
startOffset = getDecoratedBottom(lastView);
}
for (int i = startPosition; i < getItemCount() && startOffset < bottomEdge + dy; i++) {
scrapRect = getState().mItemsFrames.get(i);
scrap = recycler.getViewForPosition(i);
addView(scrap);
if (scrapRect == null) {
scrapRect = new Rect();
getState().mItemsFrames.put(i, scrapRect);
}
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
leftOffset = (int) (getPaddingLeft() + (width - scrapWidth) / 2.0f);
if (startOffset == -1 && startPosition == 0) {
//layout the first position item in center
int top = (int) (getPaddingTop() + (getVerticalSpace() - scrapHeight) / 2.f);
scrapRect.set(leftOffset, top, leftOffset + scrapWidth, top + scrapHeight);
} else {
scrapRect.set(leftOffset, startOffset, leftOffset + scrapWidth, startOffset + scrapHeight);
}
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
startOffset = scrapRect.bottom;
mLastVisiblePos = i;
}
} else {
//dy<0
if (getChildCount() > 0) {
View firstView = getChildAt(0);
startPosition = getPosition(firstView) - 1; //前一个View的position
startOffset = getDecoratedTop(firstView);
}
for (int i = startPosition; i >= 0 && startOffset > topEdge + dy; i--) {
scrapRect = getState().mItemsFrames.get(i);
scrap = recycler.getViewForPosition(i);
addView(scrap, 0);
if (scrapRect == null) {
scrapRect = new Rect();
getState().mItemsFrames.put(i, scrapRect);
}
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
leftOffset = (int) (getPaddingLeft() + (width - scrapWidth) / 2.0f);
scrapRect.set(leftOffset, startOffset - scrapHeight, leftOffset + scrapWidth, startOffset);
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
startOffset = scrapRect.top;
mFirstVisiblePosition = i;
}
}
}
/**
* @param recycler
* @param state
*/
private void fillWithHorizontal(RecyclerView.Recycler recycler, RecyclerView.State state, int dx) {
int leftEdge = getOrientationHelper().getStartAfterPadding();
int rightEdge = getOrientationHelper().getEndAfterPadding();
//1.remove and recycle the view that disappear in screen
View child;
if (getChildCount() > 0) {
if (dx >= 0) {
//remove and recycle the left off screen view
int fixIndex = 0;
for (int i = 0; i < getChildCount(); i++) {
child = getChildAt(i + fixIndex);
if (getDecoratedRight(child) - dx < leftEdge) {
removeAndRecycleView(child, recycler);
mFirstVisiblePosition++;
fixIndex--;
} else {
break;
}
}
} else { //dx<0
//remove and recycle the right off screen view
for (int i = getChildCount() - 1; i >= 0; i--) {
child = getChildAt(i);
if (getDecoratedLeft(child) - dx > rightEdge) {
removeAndRecycleView(child, recycler);
mLastVisiblePos--;
}
}
}
}
//2.Add or reattach item view to fill screen
int startPosition = mFirstVisiblePosition;
int startOffset = -1;
int scrapWidth, scrapHeight;
Rect scrapRect;
int height = getVerticalSpace();
int topOffset;
View scrap;
if (dx >= 0) {
if (getChildCount() != 0) {
View lastView = getChildAt(getChildCount() - 1);
startPosition = getPosition(lastView) + 1; //start layout from next position item
startOffset = getDecoratedRight(lastView);
}
for (int i = startPosition; i < getItemCount() && startOffset < rightEdge + dx; i++) {
scrapRect = getState().mItemsFrames.get(i);
scrap = recycler.getViewForPosition(i);
addView(scrap);
if (scrapRect == null) {
scrapRect = new Rect();
getState().mItemsFrames.put(i, scrapRect);
}
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
topOffset = (int) (getPaddingTop() + (height - scrapHeight) / 2.0f);
if (startOffset == -1 && startPosition == 0) {
// layout the first position item in center
int left = (int) (getPaddingLeft() + (getHorizontalSpace() - scrapWidth) / 2.f);
scrapRect.set(left, topOffset, left + scrapWidth, topOffset + scrapHeight);
} else {
scrapRect.set(startOffset, topOffset, startOffset + scrapWidth, topOffset + scrapHeight);
}
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
startOffset = scrapRect.right;
mLastVisiblePos = i;
}
} else {
//dx<0
if (getChildCount() > 0) {
View firstView = getChildAt(0);
startPosition = getPosition(firstView) - 1; //start layout from previous position item
startOffset = getDecoratedLeft(firstView);
}
for (int i = startPosition; i >= 0 && startOffset > leftEdge + dx; i--) {
scrapRect = getState().mItemsFrames.get(i);
scrap = recycler.getViewForPosition(i);
addView(scrap, 0);
if (scrapRect == null) {
scrapRect = new Rect();
getState().mItemsFrames.put(i, scrapRect);
}
measureChildWithMargins(scrap, 0, 0);
scrapWidth = getDecoratedMeasuredWidth(scrap);
scrapHeight = getDecoratedMeasuredHeight(scrap);
topOffset = (int) (getPaddingTop() + (height - scrapHeight) / 2.0f);
scrapRect.set(startOffset - scrapWidth, topOffset, startOffset, topOffset + scrapHeight);
layoutDecorated(scrap, scrapRect.left, scrapRect.top, scrapRect.right, scrapRect.bottom);
startOffset = scrapRect.left;
mFirstVisiblePosition = i;
}
}
}
private int getHorizontalSpace() {
return getWidth() - getPaddingRight() - getPaddingLeft();
}
private int getVerticalSpace() {
return getHeight() - getPaddingBottom() - getPaddingTop();
}
public State getState() {
if (mState == null) {
mState = new State();
}
return mState;
}
private int calculateScrollDirectionForPosition(int position) {
if (getChildCount() == 0) {
return LAYOUT_START;
}
final int firstChildPos = mFirstVisiblePosition;
return position < firstChildPos ? LAYOUT_START : LAYOUT_END;
}
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
final int direction = calculateScrollDirectionForPosition(targetPosition);
PointF outVector = new PointF();
if (direction == 0) {
return null;
}
if (mOrientation == HORIZONTAL) {
outVector.x = direction;
outVector.y = 0;
} else {
outVector.x = 0;
outVector.y = direction;
}
return outVector;
}
/**
* @author chensuilun
*/
class State {
/**
* Record all item view 's last position after last layout
*/
SparseArray mItemsFrames;
/**
* RecycleView 's current scroll distance since first layout
*/
int mScrollDelta;
public State() {
mItemsFrames = new SparseArray();
mScrollDelta = 0;
}
}
@Override
public boolean canScrollHorizontally() {
return mOrientation == HORIZONTAL;
}
@Override
public boolean canScrollVertically() {
return mOrientation == VERTICAL;
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
// When dx is positive,finger fling from right to left(←),scrollX+
if (getChildCount() == 0 || dx == 0) {
return 0;
}
int delta = -dx;
int parentCenter = (getOrientationHelper().getEndAfterPadding() - getOrientationHelper().getStartAfterPadding()) / 2 + getOrientationHelper().getStartAfterPadding();
View child;
if (dx > 0) {
//If we've reached the last item, enforce limits
if (getPosition(getChildAt(getChildCount() - 1)) == getItemCount() - 1) {
child = getChildAt(getChildCount() - 1);
delta = -Math.max(0, Math.min(dx, (child.getRight() - child.getLeft()) / 2 + child.getLeft() - parentCenter));
}
} else {
//If we've reached the first item, enforce limits
if (mFirstVisiblePosition == 0) {
child = getChildAt(0);
delta = -Math.min(0, Math.max(dx, ((child.getRight() - child.getLeft()) / 2 + child.getLeft()) - parentCenter));
}
}
getState().mScrollDelta = -delta;
fillCover(recycler, state, -delta);
offsetChildrenHorizontal(delta);
return -delta;
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
int delta = -dy;
int parentCenter = (getOrientationHelper().getEndAfterPadding() - getOrientationHelper().getStartAfterPadding()) / 2 + getOrientationHelper().getStartAfterPadding();
View child;
if (dy > 0) {
//If we've reached the last item, enforce limits
if (getPosition(getChildAt(getChildCount() - 1)) == getItemCount() - 1) {
child = getChildAt(getChildCount() - 1);
delta = -Math.max(0, Math.min(dy, (getDecoratedBottom(child) - getDecoratedTop(child)) / 2 + getDecoratedTop(child) - parentCenter));
}
} else {
//If we've reached the first item, enforce limits
if (mFirstVisiblePosition == 0) {
child = getChildAt(0);
delta = -Math.min(0, Math.max(dy, (getDecoratedBottom(child) - getDecoratedTop(child)) / 2 + getDecoratedTop(child) - parentCenter));
}
}
getState().mScrollDelta = -delta;
fillCover(recycler, state, -delta);
offsetChildrenVertical(delta);
return -delta;
}
public OrientationHelper getOrientationHelper() {
if (mOrientation == HORIZONTAL) {
if (mHorizontalHelper == null) {
mHorizontalHelper = OrientationHelper.createHorizontalHelper(this);
}
return mHorizontalHelper;
} else {
if (mVerticalHelper == null) {
mVerticalHelper = OrientationHelper.createVerticalHelper(this);
}
return mVerticalHelper;
}
}
/**
* @author chensuilun
*/
public static class LayoutParams extends RecyclerView.LayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(RecyclerView.LayoutParams source) {
super(source);
}
}
private ItemTransformer mItemTransformer;
public void setItemTransformer(ItemTransformer itemTransformer) {
mItemTransformer = itemTransformer;
}
/**
* A ItemTransformer is invoked whenever a attached item is scrolled.
* This offers an opportunity for the application to apply a custom transformation
* to the item views using animation properties.
*/
public interface ItemTransformer {
/**
* Apply a property transformation to the given item.
*
* @param layoutManager Current LayoutManager
* @param item Apply the transformation to this item
* @param fraction of page relative to the current front-and-center position of the pager.
* 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
void transformItem(GalleryLayoutManager layoutManager, View item, float fraction);
}
/**
* Listen for changes to the selected item
*
* @author chensuilun
*/
public interface OnItemSelectedListener {
/**
* @param recyclerView The RecyclerView which item view belong to.
* @param item The current selected view
* @param position The current selected view's position
*/
void onItemSelected(RecyclerView recyclerView, View item, int position, boolean isScrolling);
}
private OnItemSelectedListener mOnItemSelectedListener;
public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
mOnItemSelectedListener = onItemSelectedListener;
}
public void attach(RecyclerView recyclerView) {
this.attach(recyclerView, -1);
}
/**
* @param recyclerView
* @param selectedPosition
*/
public void attach(RecyclerView recyclerView, int selectedPosition) {
if (recyclerView == null) {
throw new IllegalArgumentException("The attach RecycleView must not null!!");
}
mRecyclerView = recyclerView;
mInitialSelectedPosition = Math.max(0, selectedPosition);
recyclerView.setLayoutManager(this);
mSnapHelper.attachToRecyclerView(recyclerView);
recyclerView.addOnScrollListener(mInnerScrollListener);
}
RecyclerView mRecyclerView;
public void setCallbackInFling(boolean callbackInFling) {
mCallbackInFling = callbackInFling;
}
/**
* Inner Listener to listen for changes to the selected item
*
* @author chensuilun
*/
private class InnerScrollListener extends RecyclerView.OnScrollListener {
int mState;
boolean mCallbackOnIdle;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
View snap = mSnapHelper.findSnapView(recyclerView.getLayoutManager());
if (snap != null) {
int selectedPosition = recyclerView.getLayoutManager().getPosition(snap);
if (selectedPosition != mCurSelectedPosition) {
if (mCurSelectedView != null) {
mCurSelectedView.setSelected(false);
}
mCurSelectedView = snap;
mCurSelectedView.setSelected(true);
mCurSelectedPosition = selectedPosition;
if (!mCallbackInFling && mState != SCROLL_STATE_IDLE) {
mCallbackOnIdle = true;
return;
}
if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onItemSelected(recyclerView, snap, mCurSelectedPosition, true);
}
}
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
mState = newState;
if (mState == SCROLL_STATE_IDLE) {
View snap = mSnapHelper.findSnapView(recyclerView.getLayoutManager());
if (snap != null) {
int selectedPosition = recyclerView.getLayoutManager().getPosition(snap);
if (selectedPosition != mCurSelectedPosition) {
if (mCurSelectedView != null) {
mCurSelectedView.setSelected(false);
}
mCurSelectedView = snap;
mCurSelectedView.setSelected(true);
mCurSelectedPosition = selectedPosition;
if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onItemSelected(recyclerView, snap, mCurSelectedPosition, false);
}
} else if (!mCallbackInFling && mOnItemSelectedListener != null && mCallbackOnIdle) {
mCallbackOnIdle = false;
mOnItemSelectedListener.onItemSelected(recyclerView, snap, mCurSelectedPosition, false);
} else if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onItemSelected(recyclerView, snap, mCurSelectedPosition, false);
}
}
}
}
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
GallerySmoothScroller linearSmoothScroller = new GallerySmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
@Override
public void scrollToPosition(int position) {
super.scrollToPosition(position);
mPendingScrollPosition = position;
requestLayout();
}
private int verticalSnapPreference = SNAP_TO_START;
private int horizontalSnapPreference = SNAP_TO_START;
/**
* Implement to support {@link GalleryLayoutManager#smoothScrollToPosition(RecyclerView, RecyclerView.State, int)}
*/
private class GallerySmoothScroller extends LinearSmoothScroller {
public GallerySmoothScroller(Context context) {
super(context);
}
/**
* Calculates the horizontal scroll amount necessary to make the given view in center of the RecycleView
*
* @param view The view which we want to make in center of the RecycleView
* @return The horizontal scroll amount necessary to make the view in center of the RecycleView
*/
public int calculateDxToMakeCentral(View view) {
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
if (layoutManager == null || !layoutManager.canScrollHorizontally()) {
return 0;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
final int start = layoutManager.getPaddingLeft();
final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
final int childCenter = left + (int) ((right - left) / 2.0f);
final int containerCenter = (int) ((end - start) / 2.f);
return containerCenter - childCenter;
}
/**
* Calculates the vertical scroll amount necessary to make the given view in center of the RecycleView
*
* @param view The view which we want to make in center of the RecycleView
* @return The vertical scroll amount necessary to make the view in center of the RecycleView
*/
public int calculateDyToMakeCentral(View view) {
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
if (layoutManager == null || !layoutManager.canScrollVertically()) {
return 0;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
final int start = layoutManager.getPaddingTop();
final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
final int childCenter = top + (int) ((bottom - top) / 2.0f);
final int containerCenter = (int) ((end - start) / 2.f);
return containerCenter - childCenter;
}
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
final int dx = calculateDxToMakeCentral(targetView);
final int dy = calculateDyToMakeCentral(targetView);
final int distance = (int) Math.sqrt(dx * dx + dy * dy);
final int time = calculateTimeForDeceleration(distance);
if (time > 0) {
action.update(-dx, -dy, time, mDecelerateInterpolator);
}
}
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return GalleryLayoutManager.this.computeScrollVectorForPosition(targetPosition);
}
@Override
protected int getVerticalSnapPreference() {
return verticalSnapPreference;
}
@Override
protected int getHorizontalSnapPreference() {
return horizontalSnapPreference;
}
}
}
转化类,主要是将根据计算出的fraction将item转化为他应该缩放的大小
public class ScaleTransformer implements GalleryLayoutManager.ItemTransformer {
private int mOffset = 20;
@Override
public void transformItem(GalleryLayoutManager layoutManager, View item, float fraction) {
item.setPivotX(item.getWidth() / 2.0f);
item.setPivotY(item.getHeight() / 2.0f);
float scale;
//1.5f表示item最大为item的1.5倍,普通的item大小为初始化的大小,其他的随滑动距离计算
scale = 1.5f - 0.5f * Math.abs(fraction);
item.setScaleX(scale);
item.setScaleY(scale);
item.setTranslationX(mOffset * fraction);
}
}
//add by liuxu
layoutManager = GalleryLayoutManager(GalleryLayoutManager.HORIZONTAL)
layoutManager?.attach(recyclerView,2)
layoutManager?.setItemTransformer(ScaleTransformer())
layoutManager?.setCallbackInFling(true)
recyclerView.adapter = FundationAdapter(mList)
//RecyclerView的Adapter就是最普通的Adapter
class FundationAdapter() : RecyclerView.Adapter<FundationHolder>() {
private var mList: ArrayList<Drawable>? = null
override fun onBindViewHolder(holder: FundationHolder, position: Int) {
holder.itemView.iv_effect.setImageDrawable(mList!![position])
}
constructor(list: ArrayList<Drawable>) : this() {
mList = list
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): FundationHolder {
val v = LayoutInflater.from(parent?.context).inflate(R.layout.cax_effect_item_layout, parent, false)
return FundationHolder(v)
}
override fun getItemCount(): Int {
if (mList == null) {
return 0
}
return mList?.size!!
}
}
class FundationHolder : RecyclerView.ViewHolder {
constructor(itemView: View?):super(itemView)
}
不足之处请指正,欢迎点赞,欢迎评论,最后附上源码链接!
源码链接