https://github.com/ChenLittlePing/RecyclerCoverFlow
可以在github 搜索关键字 coverflow
package com.as.demo_ok6.coverflow;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
/**
* Cover Flow布局类
* 通过重写LayoutManger布局方法{@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)}
* 对Item进行布局,并对超出屏幕的Item进行回收
*
通过重写LayoutManger中的{@link #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)}
* 进行水平滚动处理
*
* @author Chen Xiaoping ([email protected])
* @version V1.0
* @Datetime 2017-04-18
*/
public class CoverFlowLayoutManger extends RecyclerView.LayoutManager {
/**
* 最大存储item信息存储数量,
* 超过设置数量,则动态计算来获取
*/
private final int MAX_RECT_COUNT = 100;
/**滑动总偏移量*/
private int mOffsetAll = 0;
/**Item宽*/
private int mDecoratedChildWidth = 0;
/**Item高*/
private int mDecoratedChildHeight = 0;
/**Item间隔与item宽的比例*/
private float mIntervalRatio = 0.5f;
/**起始ItemX坐标*/
private int mStartX = 0;
/**起始Item Y坐标*/
private int mStartY = 0;
/**保存所有的Item的上下左右的偏移量信息*/
private SparseArray mAllItemFrames = new SparseArray<>();
/**记录Item是否出现过屏幕且还没有回收。true表示出现过屏幕上,并且还没被回收*/
private SparseBooleanArray mHasAttachedItems = new SparseBooleanArray();
/**RecyclerView的Item回收器*/
private RecyclerView.Recycler mRecycle;
/**RecyclerView的状态器*/
private RecyclerView.State mState;
/**滚动动画*/
private ValueAnimator mAnimation;
/**正显示在中间的Item*/
private int mSelectPosition = 0;
/**前一个正显示在中间的Item*/
private int mLastSelectPosition = 0;
/**滑动的方向:左*/
private static int SCROLL_LEFT = 1;
/**滑动的方向:右*/
private static int SCROLL_RIGHT = 2;
/**
* 选中监听
*/
private OnSelected mSelectedListener;
/**是否为平面滚动,Item之间没有叠加,也没有缩放*/
private boolean mIsFlatFlow = false;
/**是否启动Item灰度值渐变*/
private boolean mItemGradualGrey = false;
/**是否启动Item半透渐变*/
private boolean mItemGradualAlpha = false;
public CoverFlowLayoutManger(boolean isFlat, boolean isGreyItem,
boolean isAlphaItem, float cstInterval) {
mIsFlatFlow = isFlat;
mItemGradualGrey = isGreyItem;
mItemGradualAlpha = isAlphaItem;
if (cstInterval >= 0) {
mIntervalRatio = cstInterval;
} else {
if (mIsFlatFlow) {
mIntervalRatio = 1.1f;
}
}
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//如果没有item,直接返回
//跳过preLayout,preLayout主要用于支持动画
if (getItemCount() <= 0 || state.isPreLayout()) {
mOffsetAll = 0;
return;
}
mAllItemFrames.clear();
mHasAttachedItems.clear();
//得到子view的宽和高,这边的item的宽高都是一样的,所以只需要进行一次测量
View scrap = recycler.getViewForPosition(0);
addView(scrap);
measureChildWithMargins(scrap, 0, 0);
//计算测量布局的宽高
mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);
mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);
mStartX = Math.round((getHorizontalSpace() - mDecoratedChildWidth) * 1.0f / 2);
mStartY = Math.round((getVerticalSpace() - mDecoratedChildHeight) *1.0f / 2);
float offset = mStartX;
/**只存{@link MAX_RECT_COUNT}个item具体位置*/
for (int i = 0; i < getItemCount() && i < MAX_RECT_COUNT; i++) {
Rect frame = mAllItemFrames.get(i);
if (frame == null) {
frame = new Rect();
}
frame.set(Math.round(offset), mStartY, Math.round(offset + mDecoratedChildWidth), mStartY + mDecoratedChildHeight);
mAllItemFrames.put(i, frame);
mHasAttachedItems.put(i, false);
offset = offset + getIntervalDistance(); //原始位置累加,否则越后面误差越大
}
detachAndScrapAttachedViews(recycler); //在布局之前,将所有的子View先Detach掉,放入到Scrap缓存中
if ((mRecycle == null || mState == null) && //在为初始化前调用smoothScrollToPosition 或者 scrollToPosition,只会记录位置
mSelectPosition != 0) { //所以初始化时需要滚动到对应位置
mOffsetAll = calculateOffsetForPosition(mSelectPosition);
onSelectedCallBack();
}
layoutItems(recycler, state, SCROLL_RIGHT);
mRecycle = recycler;
mState = state;
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mAnimation != null && mAnimation.isRunning()) mAnimation.cancel();
int travel = dx;
if (dx + mOffsetAll < 0) {
travel = -mOffsetAll;
} else if (dx + mOffsetAll > getMaxOffset()){
travel = (int) (getMaxOffset() - mOffsetAll);
}
mOffsetAll += travel; //累计偏移量
layoutItems(recycler, state, dx > 0 ? SCROLL_RIGHT : SCROLL_LEFT);
return travel;
}
/**
* 布局Item
* 注意:1,先清除已经超出屏幕的item
*
2,再绘制可以显示在屏幕里面的item
*/
private void layoutItems(RecyclerView.Recycler recycler,
RecyclerView.State state, int scrollDirection) {
if (state.isPreLayout()) return;
Rect displayFrame = new Rect(mOffsetAll, 0, mOffsetAll + getHorizontalSpace(), getVerticalSpace());
int position = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
position = getPosition(child);
Rect rect = getFrame(position);
if (!Rect.intersects(displayFrame, rect)) {//Item没有在显示区域,就说明需要回收
removeAndRecycleView(child, recycler); //回收滑出屏幕的View
mHasAttachedItems.delete(position);
} else { //Item还在显示区域内,更新滑动后Item的位置
layoutItem(child, rect); //更新Item位置
mHasAttachedItems.put(position, true);
}
}
if (position == 0) position = mSelectPosition;
int min = position - 50 >= 0? position - 50 : 0;
int max = position + 50 < getItemCount() ? position + 50 : getItemCount();
for (int i = min; i < max; i++) {
Rect rect = getFrame(i);
if (Rect.intersects(displayFrame, rect) &&
!mHasAttachedItems.get(i)) { //重新加载可见范围内的Item
View scrap = recycler.getViewForPosition(i);
measureChildWithMargins(scrap, 0, 0);
if (scrollDirection == SCROLL_LEFT || mIsFlatFlow) { //向左滚动,新增的Item需要添加在最前面
addView(scrap, 0);
} else { //向右滚动,新增的item要添加在最后面
addView(scrap);
}
layoutItem(scrap, rect); //将这个Item布局出来
mHasAttachedItems.put(i, true);
}
}
}
/**
* 布局Item位置
* @param child 要布局的Item
* @param frame 位置信息
*/
private void layoutItem(View child, Rect frame) {
layoutDecorated(child,
frame.left - mOffsetAll,
frame.top,
frame.right - mOffsetAll,
frame.bottom);
if (!mIsFlatFlow) { //不是平面普通滚动的情况下才进行缩放
child.setScaleX(computeScale(frame.left - mOffsetAll)); //缩放
child.setScaleY(computeScale(frame.left - mOffsetAll)); //缩放
}
if (mItemGradualAlpha) {
child.setAlpha(computeAlpha(frame.left - mOffsetAll));
}
if (mItemGradualGrey) {
greyItem(child, frame);
}
}
/**
* 动态获取Item的位置信息
* @param index item位置
* @return item的Rect信息
*/
private Rect getFrame(int index) {
Rect frame = mAllItemFrames.get(index);
if (frame == null) {
frame = new Rect();
float offset = mStartX + getIntervalDistance() * index; //原始位置累加(即累计间隔距离)
frame.set(Math.round(offset), mStartY, Math.round(offset + mDecoratedChildWidth), mStartY + mDecoratedChildHeight);
}
return frame;
}
/**
* 变化Item的灰度值
* @param child 需要设置灰度值的Item
* @param frame 位置信息
*/
private void greyItem(View child, Rect frame) {
float value = computeGreyScale(frame.left - mOffsetAll);
ColorMatrix cm = new ColorMatrix(new float[]{
value, 0, 0, 0, 120*(1-value),
0, value, 0, 0, 120*(1-value),
0, 0, value, 0, 120*(1-value),
0, 0, 0, 1, 250*(1-value),
});
// cm.setSaturation(0.9f);
// Create a paint object with color matrix
Paint greyPaint = new Paint();
greyPaint.setColorFilter(new ColorMatrixColorFilter(cm));
// Create a hardware layer with the grey paint
child.setLayerType(View.LAYER_TYPE_HARDWARE, greyPaint);
if (value >= 1) {
// Remove the hardware layer
child.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
switch (state){
case RecyclerView.SCROLL_STATE_IDLE:
//滚动停止时
fixOffsetWhenFinishScroll();
break;
case RecyclerView.SCROLL_STATE_DRAGGING:
//拖拽滚动时
break;
case RecyclerView.SCROLL_STATE_SETTLING:
//动画滚动时
break;
}
}
@Override
public void scrollToPosition(int position) {
if (position < 0 || position > getItemCount() - 1) return;
mOffsetAll = calculateOffsetForPosition(position);
if (mRecycle == null || mState == null) {//如果RecyclerView还没初始化完,先记录下要滚动的位置
mSelectPosition = position;
} else {
layoutItems(mRecycle, mState, position > mSelectPosition ? SCROLL_RIGHT : SCROLL_LEFT);
onSelectedCallBack();
}
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
int finalOffset = calculateOffsetForPosition(position);
if (mRecycle == null || mState == null) {//如果RecyclerView还没初始化完,先记录下要滚动的位置
mSelectPosition = position;
} else {
startScroll(mOffsetAll, finalOffset);
}
}
@Override
public boolean canScrollHorizontally() {
return true;
}
@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
removeAllViews();
mRecycle = null;
mState = null;
mOffsetAll = 0;
mSelectPosition = 0;
mLastSelectPosition = 0;
mHasAttachedItems.clear();
mAllItemFrames.clear();
}
/**
* 获取整个布局的水平空间大小
*/
private int getHorizontalSpace() {
return getWidth() - getPaddingRight() - getPaddingLeft();
}
/**
* 获取整个布局的垂直空间大小
*/
private int getVerticalSpace() {
return getHeight() - getPaddingBottom() - getPaddingTop();
}
/**
* 获取最大偏移量
*/
private float getMaxOffset() {
return (getItemCount() - 1) * getIntervalDistance();
}
/**
* 计算Item缩放系数
* @param x Item的偏移量
* @return 缩放系数
*/
private float computeScale(int x) {
float scale = 1 - Math.abs(x - mStartX) * 1.0f / Math.abs(mStartX + mDecoratedChildWidth / mIntervalRatio);
if (scale < 0) scale = 0;
if (scale > 1) scale = 1;
return scale;
}
/**
* 计算Item的灰度值
* @param x Item的偏移量
* @return 灰度系数
*/
private float computeGreyScale(int x) {
float itemMidPos = x + mDecoratedChildWidth / 2; //item中点x坐标
float itemDx2Mid = Math.abs(itemMidPos - getHorizontalSpace() / 2); //item中点距离控件中点距离
float value = 1 - itemDx2Mid * 1.0f / (getHorizontalSpace() /2);
if (value < 0.1) value = 0.1f;
if (value > 1) value = 1;
value = (float) Math.pow(value,.8);
return value;
}
/**
* 计算Item半透值
* @param x Item的偏移量
* @return 缩放系数
*/
private float computeAlpha(int x) {
float alpha = 1 - Math.abs(x - mStartX) * 1.0f / Math.abs(mStartX + mDecoratedChildWidth / mIntervalRatio);
if (alpha < 0.3f) alpha = 0.3f;
if (alpha > 1) alpha = 1.0f;
return alpha;
}
/**
* 计算Item所在的位置偏移
* @param position 要计算Item位置
*/
private int calculateOffsetForPosition(int position) {
return Math.round(getIntervalDistance() * position);
}
/**
* 修正停止滚动后,Item滚动到中间位置
*/
private void fixOffsetWhenFinishScroll() {
int scrollN = (int) (mOffsetAll * 1.0f / getIntervalDistance());
float moreDx = (mOffsetAll % getIntervalDistance());
if (moreDx > (getIntervalDistance() * 0.5)) {
scrollN ++;
}
int finalOffset = (int) (scrollN * getIntervalDistance());
startScroll(mOffsetAll, finalOffset);
mSelectPosition = Math.round (finalOffset * 1.0f / getIntervalDistance());
}
/**
* 滚动到指定X轴位置
* @param from X轴方向起始点的偏移量
* @param to X轴方向终点的偏移量
*/
private void startScroll(int from, int to) {
if (mAnimation != null && mAnimation.isRunning()) {
mAnimation.cancel();
}
final int direction = from < to ? SCROLL_RIGHT : SCROLL_LEFT;
mAnimation = ValueAnimator.ofFloat(from, to);
mAnimation.setDuration(500);
mAnimation.setInterpolator(new DecelerateInterpolator());
mAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mOffsetAll = Math.round((float) animation.getAnimatedValue());
layoutItems(mRecycle, mState, direction);
}
});
mAnimation.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
onSelectedCallBack();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mAnimation.start();
}
/**
* 获取Item间隔
*/
private float getIntervalDistance() {
return mDecoratedChildWidth * mIntervalRatio;
}
/**
* 计算当前选中位置,并回调
*/
private void onSelectedCallBack() {
mSelectPosition = Math.round (mOffsetAll / getIntervalDistance());
if (mSelectedListener != null && mSelectPosition != mLastSelectPosition) {
mSelectedListener.onItemSelected(mSelectPosition);
}
mLastSelectPosition = mSelectPosition;
}
/**
* 获取第一个可见的Item位置
*
Note:该Item为绘制在可见区域的第一个Item,有可能被第二个Item遮挡
*/
public int getFirstVisiblePosition() {
Rect displayFrame = new Rect(mOffsetAll, 0, mOffsetAll + getHorizontalSpace(), getVerticalSpace());
int cur = getCenterPosition();
for (int i = cur - 1; i >= 0; i--) {
Rect rect = getFrame(i);
if (!Rect.intersects(displayFrame, rect)) {
return i + 1;
}
}
return 0;
}
/**
* 获取最后一个可见的Item位置
*
Note:该Item为绘制在可见区域的最后一个Item,有可能被倒数第二个Item遮挡
*/
public int getLastVisiblePosition() {
Rect displayFrame = new Rect(mOffsetAll, 0, mOffsetAll + getHorizontalSpace(), getVerticalSpace());
int cur = getCenterPosition();
for (int i = cur + 1; i < getItemCount(); i++) {
Rect rect = getFrame(i);
if (!Rect.intersects(displayFrame, rect)) {
return i - 1;
}
}
return cur;
}
/**
* 获取可见范围内最大的显示Item个数
*/
public int getMaxVisibleCount() {
int oneSide = (int) ((getHorizontalSpace() - mStartX) / (getIntervalDistance()));
return oneSide * 2 + 1;
}
/**
* 获取中间位置
*
Note:该方法主要用于{@link RecyclerCoverFlow#getChildDrawingOrder(int, int)}判断中间位置
*
如果需要获取被选中的Item位置,调用{@link #getSelectedPos()}
*/
public int getCenterPosition() {
int pos = (int) (mOffsetAll / getIntervalDistance());
int more = (int) (mOffsetAll % getIntervalDistance());
if (more > getIntervalDistance() * 0.5f) pos++;
return pos;
}
/**
* 设置选中监听
* @param l 监听接口
*/
public void setOnSelectedListener(OnSelected l) {
mSelectedListener = l;
}
/**
* 获取被选中Item位置
*/
public int getSelectedPos() {
return mSelectPosition;
}
/**
* 选中监听接口
*/
public interface OnSelected {
/**
* 监听选中回调
* @param position 显示在中间的Item的位置
*/
void onItemSelected(int position);
}
public static class Builder {
boolean isFlat = false;
boolean isGreyItem = false;
boolean isAlphaItem = false;
float cstIntervalRatio = -1f;
public Builder setFlat(boolean flat) {
isFlat = flat;
return this;
}
public Builder setGreyItem(boolean greyItem) {
isGreyItem = greyItem;
return this;
}
public Builder setAlphaItem(boolean alphaItem) {
isAlphaItem = alphaItem;
return this;
}
public Builder setIntervalRatio(float ratio) {
cstIntervalRatio = ratio;
return this;
}
public CoverFlowLayoutManger build() {
return new CoverFlowLayoutManger(isFlat, isGreyItem,
isAlphaItem, cstIntervalRatio);
}
}
}
package com.as.demo_ok6.coverflow;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
/**
* 继承RecyclerView重写{@link #getChildDrawingOrder(int, int)}对Item的绘制顺序进行控制
*
* @author Chen Xiaoping ([email protected])
* @version V1.0
* @Datetime 2017-04-18
*/
public class RecyclerCoverFlow extends RecyclerView {
/**
* 按下的X轴坐标
*/
private float mDownX;
/**
* 布局器构建者
*/
private CoverFlowLayoutManger.Builder mManagerBuilder;
public RecyclerCoverFlow(Context context) {
super(context);
init();
}
public RecyclerCoverFlow(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public RecyclerCoverFlow(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
createManageBuilder();
setLayoutManager(mManagerBuilder.build());
setChildrenDrawingOrderEnabled(true); //开启重新排序
setOverScrollMode(OVER_SCROLL_NEVER);
}
/**
* 创建布局构建器
*/
private void createManageBuilder() {
if (mManagerBuilder == null) {
mManagerBuilder = new CoverFlowLayoutManger.Builder();
}
}
/**
* 设置是否为普通平面滚动
* @param isFlat true:平面滚动;false:叠加缩放滚动
*/
public void setFlatFlow(boolean isFlat) {
createManageBuilder();
mManagerBuilder.setFlat(isFlat);
setLayoutManager(mManagerBuilder.build());
}
/**
* 设置Item灰度渐变
* @param greyItem true:Item灰度渐变;false:Item灰度不变
*/
public void setGreyItem(boolean greyItem) {
createManageBuilder();
mManagerBuilder.setGreyItem(greyItem);
setLayoutManager(mManagerBuilder.build());
}
/**
* 设置Item灰度渐变
* @param alphaItem true:Item半透渐变;false:Item透明度不变
*/
public void setAlphaItem(boolean alphaItem) {
createManageBuilder();
mManagerBuilder.setAlphaItem(alphaItem);
setLayoutManager(mManagerBuilder.build());
}
/**
* 设置Item的间隔比例
* @param intervalRatio Item间隔比例。
* 即:item的宽 x intervalRatio
*/
public void setIntervalRatio(float intervalRatio) {
createManageBuilder();
mManagerBuilder.setIntervalRatio(intervalRatio);
setLayoutManager(mManagerBuilder.build());
}
@Override
public void setLayoutManager(LayoutManager layout) {
if (!(layout instanceof CoverFlowLayoutManger)) {
throw new IllegalArgumentException("The layout manager must be CoverFlowLayoutManger");
}
super.setLayoutManager(layout);
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
int center = getCoverFlowLayout().getCenterPosition()
- getCoverFlowLayout().getFirstVisiblePosition(); //计算正在显示的所有Item的中间位置
if (center < 0) center = 0;
else if (center > childCount) center = childCount;
int order;
if (i == center) {
order = childCount - 1;
} else if (i > center) {
order = center + childCount - 1 - i;
} else {
order = i;
}
return order;
}
/**
* 获取LayoutManger,并强制转换为CoverFlowLayoutManger
*/
public CoverFlowLayoutManger getCoverFlowLayout() {
return ((CoverFlowLayoutManger)getLayoutManager());
}
/**
* 获取被选中的Item位置
*/
public int getSelectedPos() {
return getCoverFlowLayout().getSelectedPos();
}
/**
* 设置选中监听
* @param l 监听接口
*/
public void setOnItemSelectedListener(CoverFlowLayoutManger.OnSelected l) {
getCoverFlowLayout().setOnSelectedListener(l);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
getParent().requestDisallowInterceptTouchEvent(true); //设置父类不拦截滑动事件
break;
case MotionEvent.ACTION_MOVE:
if ((ev.getX() > mDownX && getCoverFlowLayout().getCenterPosition() == 0) ||
(ev.getX() < mDownX && getCoverFlowLayout().getCenterPosition() ==
getCoverFlowLayout().getItemCount() -1)) {
//如果是滑动到了最前和最后,开放父类滑动事件拦截
getParent().requestDisallowInterceptTouchEvent(false);
} else {
//滑动到中间,设置父类不拦截滑动事件
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.dispatchTouchEvent(ev);
}
}
https://github.com/zhangqifan1/Demo_ok6
2.https://github.com/zhangqifan1/FlyPiyInSky s烧烤摊里面有很多
3.OverFlying 这个 ViewPager 其实有个这种的 RecyclerView 的 也不错
public class OverFlyingLayoutManager extends RecyclerView.LayoutManager {
private static final String TAG = "OverFlying";
private @FloatRange(from = 0.01, to = 1.0)
float edgePercent = 0.8f;//触发边缘动画距离百分比
private @FloatRange(from = 1)
float slowTimes = 3;//到达此距离后放慢倍数
private int orientation = OrientationHelper.VERTICAL;
private boolean offsetUseful = false;
private int overFlyingDist;
private int totalHeight = 0;
private int totalWidth = 0;
private int verticalScrollOffset;
private int horizontalScrollOffset;
//头部是否也要层叠,默认需要
private boolean topOverFlying;
private int viewWidth, viewHeight;
public OverFlyingLayoutManager() {
this(OrientationHelper.VERTICAL);
}
public OverFlyingLayoutManager(int orientation) {
this(orientation, true);
}
public OverFlyingLayoutManager(int orientation, boolean topOverFlying) {
this.orientation = orientation;
this.topOverFlying = topOverFlying;
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
// 先把所有的View先从RecyclerView中detach掉,然后标记为"Scrap"状态,表示这些View处于可被重用状态(非显示中)。
// 实际就是把View放到了Recycler中的一个集合中。
if (getItemCount() == 0) {//没有Item
detachAndScrapAttachedViews(recycler);
return;
}
if (getChildCount() == 0 && state.isPreLayout()) {
return;
}
reset();
detachAndScrapAttachedViews(recycler);
calculateChildrenSite(recycler, state);
}
private void reset() {
totalHeight = 0;
totalWidth = 0;
if (!offsetUseful) {
verticalScrollOffset = 0;
horizontalScrollOffset = 0;
}
offsetUseful = false;
}
private void calculateChildrenSite(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (orientation == OrientationHelper.VERTICAL) {
calculateChildrenSiteVertical(recycler, state);
addAndLayoutViewVertical(recycler, state, verticalScrollOffset);
} else {
calculateChildrenSiteHorizontal(recycler, state);
addAndLayoutViewHorizontal(recycler, state, horizontalScrollOffset);
}
}
private void calculateChildrenSiteVertical(RecyclerView.Recycler recycler, RecyclerView.State state) {
View view = recycler.getViewForPosition(0);//暂时这么解决,不能layout出所有的子View
measureChildWithMargins(view, 0, 0);
calculateItemDecorationsForChild(view, new Rect());
viewHeight = getDecoratedMeasuredHeight(view);
overFlyingDist = (int) (slowTimes * viewHeight);
totalHeight = getItemCount() * viewHeight;
Log.d(TAG, "childCountI = " + getChildCount() + " itemCount= " + recycler.getScrapList().size());
}
private void calculateChildrenSiteHorizontal(RecyclerView.Recycler recycler, RecyclerView.State state) {
View view = recycler.getViewForPosition(0);//暂时这么解决,不能layout出所有的子View
measureChildWithMargins(view, 0, 0);
calculateItemDecorationsForChild(view, new Rect());
viewWidth = getDecoratedMeasuredWidth(view);
overFlyingDist = (int) (slowTimes * viewWidth);
totalWidth = getItemCount() * viewWidth;
Log.d(TAG, "childCountI = " + getChildCount() + " itemCount= " + recycler.getScrapList().size());
}
@Override
public boolean canScrollHorizontally() {
// 返回true表示可以横向滑动
return orientation == OrientationHelper.HORIZONTAL;
}
@Override
public boolean canScrollVertically() {
// 返回true表示可以纵向滑动
return orientation == OrientationHelper.VERTICAL;
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
//列表向下滚动dy为正,列表向上滚动dy为负,这点与Android坐标系保持一致。
int tempDy = dy;
if (verticalScrollOffset <= totalHeight - getVerticalSpace()) {
verticalScrollOffset += dy;
//将竖直方向的偏移量+travel
}
if (verticalScrollOffset > totalHeight - getVerticalSpace()) {
verticalScrollOffset = totalHeight - getVerticalSpace();
tempDy = 0;
} else if (verticalScrollOffset < 0) {
verticalScrollOffset = 0;
tempDy = 0;
}
detachAndScrapAttachedViews(recycler);
addAndLayoutViewVertical(recycler, state, verticalScrollOffset); //从新布局位置、显示View
return tempDy;
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
int tempDx = dx;
if (horizontalScrollOffset <= totalWidth - getHorizontalSpace()) {
horizontalScrollOffset += dx;
//将竖直方向的偏移量+travel
}
if (horizontalScrollOffset > totalWidth - getHorizontalSpace()) {
horizontalScrollOffset = totalWidth - getHorizontalSpace();
tempDx = 0;
} else if (horizontalScrollOffset < 0) {
horizontalScrollOffset = 0;
tempDx = 0;
}
detachAndScrapAttachedViews(recycler);
addAndLayoutViewHorizontal(recycler, state, horizontalScrollOffset); //从新布局位置、显示View
return tempDx;
}
private void addAndLayoutViewVertical(RecyclerView.Recycler recycler, RecyclerView.State state, int offset) {
int itemCount = getItemCount();
if (itemCount <= 0 || state.isPreLayout()) {
return;
}
int displayHeight = getVerticalSpace();
for (int i = itemCount - 1; i >= 0; i--) {
// 遍历Recycler中保存的View取出来
int bottomOffset = (i + 1) * viewHeight - offset;
int topOffset = i * viewHeight - offset;
boolean needAdd = true;
if (bottomOffset - displayHeight >= overFlyingDist) {
needAdd = false;
}
if (topOffset < -overFlyingDist && i != 0 && topOverFlying
|| topOffset < -overFlyingDist && !topOverFlying) {
needAdd = false;
}
if (needAdd) {
View view = recycler.getViewForPosition(i);
addView(view); // 因为刚刚进行了detach操作,所以现在可以重新添加
measureChildWithMargins(view, 0, 0); // 通知测量view的margin值
int width = getDecoratedMeasuredWidth(view); // 计算view实际大小,包括了ItemDecorator中设置的偏移量。
int height = getDecoratedMeasuredHeight(view);
//调用这个方法能够调整ItemView的大小,以除去ItemDecorator。
calculateItemDecorationsForChild(view, new Rect());
int realBottomOffset = bottomOffset;
if (topOverFlying) {
if (i != 0) {//除第一个外的顶部黏性动画
if (topOffset <= height * edgePercent) {//到达顶部边界了
int edgeDist = (int) (height * edgePercent);//边界触发距离
int top = (int) (edgeDist - (edgeDist - topOffset) / slowTimes);//到达边界后速度放慢到原来5分之一
top = Math.max(top, 0);
realBottomOffset = top + height;
}
} else {
realBottomOffset = height;
}
}
if (i != itemCount - 1) {//除最后一个外的底部慢速动画
if (displayHeight - bottomOffset <= height * edgePercent) {
int edgeDist = (int) (displayHeight - height * edgePercent);
int bottom = (int) (edgeDist + (bottomOffset - edgeDist) / slowTimes);
bottom = Math.min(bottom, displayHeight);
realBottomOffset = bottom;
}
} else {
realBottomOffset = totalHeight > displayHeight ? displayHeight : totalHeight;
}
layoutDecoratedWithMargins(view, 0, realBottomOffset - height, width, realBottomOffset);
}
}
Log.d(TAG, "childCount = " + getChildCount() + " itemCount= " + itemCount);
}
private void addAndLayoutViewHorizontal(RecyclerView.Recycler recycler, RecyclerView.State state, int offset) {
int itemCount = getItemCount();
if (itemCount <= 0 || state.isPreLayout()) {
return;
}
int displayWidth = getHorizontalSpace();
for (int i = itemCount - 1; i >= 0; i--) {
int rightOffset = (i + 1) * viewWidth - offset;
int leftOffset = i * viewWidth - offset;
boolean needAdd = true;
if (rightOffset - displayWidth >= overFlyingDist) {
needAdd = false;
}
if (leftOffset < -overFlyingDist && i != 0
|| leftOffset < -overFlyingDist && !topOverFlying) {
needAdd = false;
}
if (needAdd) {
// 遍历Recycler中保存的View取出来
View view = recycler.getViewForPosition(i);
addView(view); // 因为刚刚进行了detach操作,所以现在可以重新添加
measureChildWithMargins(view, 0, 0); // 通知测量view的margin值
int width = getDecoratedMeasuredWidth(view); // 计算view实际大小,包括了ItemDecorator中设置的偏移量。
int height = getDecoratedMeasuredHeight(view);
//调用这个方法能够调整ItemView的大小,以除去ItemDecorator。
calculateItemDecorationsForChild(view, new Rect());
int realRightOffset = rightOffset;
if (topOverFlying) {//除第一个外的左边缘慢速动画
if (i != 0) {
if (leftOffset <= width * edgePercent) {//到达边界了
int edgeDist = (int) (width * edgePercent);//边界触发距离
int left = (int) (edgeDist - (edgeDist - leftOffset) / slowTimes);///到达边界后速度放慢到原来5分之一
left = Math.max(0, left);
if (left < 0) {
left = 0;
}
realRightOffset = left + width;
}
} else {
realRightOffset = width;
}
}
if (i != itemCount - 1) {//除最后一个外的右边缘慢速动画
if (displayWidth - rightOffset <= width * edgePercent) {
int edgeDist = (int) (displayWidth - width * edgePercent);
int right = (int) (edgeDist + (rightOffset - edgeDist) / slowTimes);
if (right >= displayWidth) {
right = displayWidth;
}
realRightOffset = right;
}
} else {
realRightOffset = totalWidth > displayWidth ? displayWidth : totalWidth;
}
layoutDecoratedWithMargins(view, realRightOffset - width, 0, realRightOffset, height);
}
}
Log.d(TAG, "childCount = " + getChildCount() + " itemCount= " +itemCount);
}
private int getVerticalSpace() {
// 计算RecyclerView的可用高度,除去上下Padding值
return getHeight() - getPaddingBottom() - getPaddingTop();
}
private int getHorizontalSpace() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
public void setEdgePercent(@FloatRange(from = 0.01, to = 1.0) float edgePercent) {
this.edgePercent = edgePercent;
}
/**
* {@inheritDoc}
*/
@Override
public View findViewByPosition(int position) {
final int childCount = getChildCount();
if (childCount == 0) {
return null;
}
final int firstChild = getPosition(getChildAt(0));
final int viewPosition = position - firstChild;
if (viewPosition >= 0 && viewPosition < childCount) {
final View child = getChildAt(viewPosition);
if (getPosition(child) == position) {
return child; // in pre-layout, this may not match
}
}
return super.findViewByPosition(position);
}
@Override
public void scrollToPosition(int position) {
offsetUseful = true;
if (orientation == OrientationHelper.VERTICAL && viewHeight != 0) {
verticalScrollOffset = position * viewHeight;
} else if (orientation == OrientationHelper.HORIZONTAL && viewWidth != 0) {
horizontalScrollOffset = position * viewWidth;
}
requestLayout();
}
public void setSlowTimes(@IntRange(from = 1) int slowTimes) {
this.slowTimes = slowTimes;
}
}
List list = new ArrayList() {{
for (int i = 0; i < 100; i++) {
add(" "+i+" ");
}
}};
/*******0*******/
final RecyclerView recycelrview = findViewById(R.id.recycelrview);
OverFlyingLayoutManager overFlyingLayoutManager = new OverFlyingLayoutManager(OrientationHelper.HORIZONTAL);
recycelrview.setLayoutManager(overFlyingLayoutManager);
MyAdapter myAdapter = new MyAdapter(R.layout.item_text, list);
recycelrview.setAdapter(myAdapter);
/*******1*******/
final RecyclerView recycelrview1 = findViewById(R.id.recycelrview1);
OverFlyingLayoutManager overFlyingLayoutManager1 = new OverFlyingLayoutManager(OrientationHelper.HORIZONTAL,false);
recycelrview1.setLayoutManager(overFlyingLayoutManager1);
MyAdapter myAdapter1 = new MyAdapter(R.layout.item_text, list);
recycelrview1.setAdapter(myAdapter1);
/*******2*******/
final RecyclerView recycelrview2 = findViewById(R.id.recycelrview2);
OverFlyingLayoutManager overFlyingLayoutManager2 = new OverFlyingLayoutManager(OrientationHelper.VERTICAL);
recycelrview2.setLayoutManager(overFlyingLayoutManager2);
MyAdapter myAdapter2 = new MyAdapter(R.layout.item_text, list);
recycelrview2.setAdapter(myAdapter2);
/*******3*******/
final RecyclerView recycelrview3 = findViewById(R.id.recycelrview3);
OverFlyingLayoutManager overFlyingLayoutManager3 = new OverFlyingLayoutManager(OrientationHelper.VERTICAL,false);
recycelrview3.setLayoutManager(overFlyingLayoutManager3);
MyAdapter myAdapter3 = new MyAdapter(R.layout.item_text, list);
recycelrview3.setAdapter(myAdapter3);
首先RecyclerView 不能wrap_content 第二 条目的 宽高影响着效果
4.https://github.com/DingMouRen/LayoutManagerGroup 这个也很多