地址https://github.com/dubaofeng/CopyOppoWatcheLayoutManager
效果图
image参考并使用了一些代码
1、 张旭童的掌握自定义LayoutManager(一) 系列开篇 常见误区、问题、注意事项,常用API
2、张旭童的掌握自定义LayoutManager(二) 实现流式布局
3、陈小缘的自定义LayoutManager第十一式之飞龙在天
4、勇潮陈的Android仿豆瓣书影音频道推荐表单堆叠列表RecyclerView-LayoutManager
要自定义自己的LayoutManager的几个必要的方法
public class CopyOppoWatcheLayoutManagerextends RecyclerView.LayoutManager{
/*** {@inheritDoc}*/
@Override
public RecyclerView.LayoutParamsgenerateDefaultLayoutParams() {
//1.必须重写的方法,直接复制我们常用LinearLayoutManager里的过来就可以了
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//2.入口,初始化列表时,列表滑动时有回调
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
//3.列表滑动时回调,dy就是滑动的距离>0列表上滚动,还有水平方向的方法,
return dy;
}
@Override
public boolean canScrollVertically() {
//4.能否上下滑动,true表示可以,还有左右的方法
return true;
}
}
实现思路:
控制滑动:
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
//没有位移或者没有子View直接返回0
if (dy ==0 || getChildCount() ==0) {
return 0;
}
int realOffset = dy;//实际滑动的距离
mCurrentOffset += realOffset;//累加实际滑动距离,通过与itemHSize求余得出每个要摆放的子view的垂直方向的偏移量。
if (mCurrentOffset <= -itemHSize) {//<0列表向下滚动
realOffset =0;//返回0列表就划不动了
/*限制允许下拉出一个item的空白高度,不能超过一个item的高度,超了会改变下标,所以限制1个像素**/
mCurrentOffset =(int) -itemHSize +1;
}
//底部偏移和上部偏移一个道理,totalOffset总偏移是通过摆放多少个正常大小的item计算出来的
if (mCurrentOffset >=totalOffset) {
mCurrentOffset =totalOffset -1;//防止越界
realOffset =0;//列表不滑动
}
fill(recycler, state, realOffset);//填充子view,真正做布局的地方,realOffset其实没用到
// offsetChildrenVertical(-realOffset);//滑动,这个方法可以滑动所有摆放的子View 没有用到它
return realOffset;//返回多少就滑动多少,0则不滑动,在边界滑动会出现表示拉不动的弧
}
//填充:
private int fill(RecyclerView.Recycler recycler, RecyclerView.State state, int dy) {
detachAndScrapAttachedViews(recycler);
int fs = (int) Math.floor(Math.abs(mCurrentOffset) / itemHSize) * hCount;
if (fs <= getItemCount() - 1) {
mFirsItemPosition = fs;
}
int LastItemPosition = mFirsItemPosition + screenItemCount - 1 + hCount;
if (LastItemPosition > getItemCount() - 1) {
LastItemPosition = getItemCount() - 1;
}
mLastItemPosition = LastItemPosition;
int buttomItemCount = (mLastItemPosition + 1) % hCount;
if (buttomItemCount == 0) {
buttomItemCount = hCount;
}
int leftOffset = getPaddingLeft();
int lastOffset = (vCount - 1) * itemHSize + mSetOffset;
float itemOffset = (mCurrentOffset + 0f) % itemHSize;
float frac = itemOffset / itemHSize;
if (mCurrentOffset >= totalOffset) {
frac = 1f;
}
int scrollY = (int) (itemHSize * frac);
int viewTopOffset = getPaddingTop() + mSetOffset;
viewTopOffset -= scrollY;//偏移量
//顶部小item
if (mFirsItemPosition >= hCount) {
for (int k = mFirsItemPosition - hCount; k < mFirsItemPosition; k++) {
View child = recycler.getViewForPosition(k);
addChild(child, leftOffset, smallCircleTopToScreenOffset, leftOffset + itemWSize, smallCircleTopToScreenOffset + itemHSize, minScale);
leftOffset += itemWSize;
}
}
//画底部小圆
leftOffset = getPaddingLeft();
int startButItemPosition = 0;
int endButItemPosition = 0;
if (mLastItemPosition + hCount < getItemCount()) {
startButItemPosition = mLastItemPosition + 1;
endButItemPosition = mLastItemPosition + hCount;
} else {
startButItemPosition = mLastItemPosition + 1;
endButItemPosition = getItemCount() - 1;
}
if (startButItemPosition != 0 && endButItemPosition != 0) {
for (int i = mLastItemPosition + 1; i < getItemCount(); i++) {
View child = recycler.getViewForPosition(i);
addChild(child, leftOffset, bottomSmallCircleTopOffset, leftOffset + itemWSize, bottomSmallCircleTopOffset + itemHSize, minScale);
leftOffset += itemWSize;
}
}
//有最后一行就得干,下滑,从底部开始布局,解决Android4.4没有设置view Z轴方法
if (mCurrentOffset < 0 && mLastItemPosition >= (vCount - 1) * hCount) {
for (int i = mLastItemPosition; i >= 0; i--) {
View child = recycler.getViewForPosition(i);
int iwCount = i % hCount + 1;//有几个宽度
int l = iwCount * itemWSize + getPaddingLeft() - itemWSize;
int r = iwCount * itemWSize + getPaddingLeft();
int ihCount = i / hCount + 0;//在第几行
if (i >= vCount * hCount) {
addChild(child, l, bottomSmallCircleTopOffset, r, bottomSmallCircleTopOffset + itemHSize, minScale);
} else if (i >= (vCount - 1) * hCount) {
int interceptOffset = lastOffset + viewTopOffset - mSetOffset;
if (interceptOffset > bottomSmallCircleTopOffset) {
interceptOffset = bottomSmallCircleTopOffset;
}
float realScale = 1f - (1f - minScale) * (interceptOffset - lastOffset) / withSmallCircleDist;
addChild(child, l, interceptOffset, r, interceptOffset + itemHSize, realScale);
} else {
addChild(child, l, ihCount * itemHSize + viewTopOffset, r, (ihCount + 1) * itemHSize + viewTopOffset, 1f);
}
}
return dy;
}
leftOffset = getHorizontalSpace() + getPaddingLeft();
for (int i = mFirsItemPosition; i <= mLastItemPosition; i++) {
View child = recycler.getViewForPosition(i);
int iwCount = i % hCount + 1;//有几个宽度
int l = iwCount * itemWSize + getPaddingLeft() - itemWSize;
int r = iwCount * itemWSize + getPaddingLeft();
if (i - mFirsItemPosition < hCount) {
//第一行
int oneLineTopOffset = 0;
float realScale = 1f - (1f - minScale) * frac;
if (viewTopOffset < mSetOffset) {
realScale = 1f - (1f - minScale) * (mSetOffset - viewTopOffset) / withSmallCircleDist; //计算出滑动到小item的百分比
}
oneLineTopOffset = viewTopOffset;
if (viewTopOffset <= smallCircleTopToScreenOffset) {
oneLineTopOffset = smallCircleTopToScreenOffset;//上滑到此停住
}
addChild(child, l, oneLineTopOffset, l + itemWSize, oneLineTopOffset + itemHSize, realScale);
} else if (i > mLastItemPosition - buttomItemCount && i >= screenItemCount) {
//画底部小圆,下滑时秒变正常大小item的最后一行,要注意
float realScale = minScale + (1f - minScale) * frac;
if (viewTopOffset < lastOffset) {
//计算出滑动到小item的百分比
realScale = minScale + (1f - minScale) * (lastOffset - viewTopOffset + smallCircleTopToScreenOffset) / withSmallCircleDist;
}
int myTopoffset = bottomSmallCircleTopOffset;//固定住底部小item
if (viewTopOffset <= bottomSmallCircleTopOffset - itemHSize) {//上滑时跟随上滑
myTopoffset = viewTopOffset + itemHSize;
}
if (myTopoffset > bottomSmallCircleTopOffset) {//下滑时固定
myTopoffset = bottomSmallCircleTopOffset;
}
if (mCurrentOffset > totalOffset - itemHSize - 1) {//最后一行正常大小显示
myTopoffset = viewTopOffset + itemHSize;
realScale = 1f;
}
addChild(child, l, myTopoffset, r, myTopoffset + itemHSize, realScale);
} else { //正常大小item的第二行到倒数第二行
if (leftOffset + itemWSize <= getHorizontalSpace() + getPaddingLeft()) { //当前行还排列的下
addChild(child, leftOffset, viewTopOffset, leftOffset + itemWSize, viewTopOffset + itemHSize, 1f);
leftOffset += itemWSize;
} else {
leftOffset = getPaddingLeft();
viewTopOffset += itemHSize;
addChild(child, leftOffset, viewTopOffset, leftOffset + itemWSize, viewTopOffset + itemHSize, 1f);
leftOffset += itemWSize;
}
}
}
return dy;
}
完整的源码 https://github.com/dubaofeng/CopyOppoWatcheLayoutManager
不足的地方欢迎在评论指出,欢迎大家分享更优的实现!多多交流,相互学习!
网友评论