vlayout icon indicating copy to clipboard operation
vlayout copied to clipboard

关于瀑布流的一点小建议,希望作者看到

Open Alzzzz opened this issue 7 years ago • 12 comments

经过一段时间的使用,发现瀑布流中有部分自己刷新的view,position为1的View比position为0的View要高的时候,会造成右侧自动上移一段距离。

经过查看代码,发现是每次刷新,都会调用onLayoutChildren,造成helper中layoutviews被调用。

但是由于helper.checkAnchorInfo()中,判断是否第一行用的是: boolean isStartLine = anchorPos == range.getLower(); 这个判断是判断是否是第一个元素,当第一个元素展示出来时不添加padding,当不是时添加padding。这样就造成了当第一个元素不展示了,第二个元素(另外列的第一个元素)展示时,刷新列表造成位置错乱。

修改方案: 在checkAnchorInfo()方法中添加: boolean isSpanStartLine = anchorPos - range.getLower() < mNumLanes; for (Span span : mSpans) { if (!span.mViews.isEmpty()) { //如果不是从尾部开始的 if (anchorInfo.layoutFromEnd) { span.cacheReferenceLineAndClear(helper.getReverseLayout() ^ anchorInfo.layoutFromEnd, isSpanStartLine? 0:offset, orientationHelper); }else { View view = span.mViews.get(0); if (isSpanStartLine && anchorPos == helper.getPosition(view)){//如果当前是Span的第一个 span.cacheReferenceLineAndClear(helper.getReverseLayout() ^ anchorInfo.layoutFromEnd, 0, orientationHelper); } else { span.cacheReferenceLineAndClear(helper.getReverseLayout() ^ anchorInfo.layoutFromEnd, offset, orientationHelper); } } } }

此写法感觉不太优雅,但能解决问题。

还请作者指正,感谢

Alzzzz avatar Oct 30 '17 12:10 Alzzzz

"position为1的View比position为1的View要高的时候" 这句话是否有误? 可以以pr的形式改好发过来看看,整体看一下修复的方式。

longerian avatar Oct 30 '17 12:10 longerian

@longerian 确实打错字了应该是: position为1的View比position为0的View要高的时候 以下是我修改的方式,有部分注释:

public void checkAnchorInfo(RecyclerView.State state, VirtualLayoutManager.AnchorInfoWrapper anchorInfo, LayoutManagerHelper helper) {
        super.checkAnchorInfo(state, anchorInfo, helper);
        ensureLanes();

        final Range<Integer> range = getRange();
        if (anchorInfo.layoutFromEnd) {
            if (anchorInfo.position < range.getLower() + mNumLanes - 1) {
                anchorInfo.position = Math.min(range.getLower() + mNumLanes - 1, range.getUpper());
            }
        } else {
            if (anchorInfo.position > range.getUpper() - (mNumLanes - 1)) {
                anchorInfo.position = Math.max(range.getLower(), range.getUpper() - (mNumLanes - 1));
            }
        }


        View reference = helper.findViewByPosition(anchorInfo.position);

        final boolean layoutInVertical = helper.getOrientation() == VERTICAL;
        int mainGap = layoutInVertical ? mVGap : mHGap;
        final OrientationHelperEx orientationHelper = helper.getMainOrientationHelper();

        if (reference == null) {
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "checkAnchorInfo span.clear()");
            }
            for (Span span : mSpans) {
                span.clear();
                span.setLine(anchorInfo.coordinate);
            }
        } else {
            int anchorPos = anchorInfo.layoutFromEnd ? Integer.MIN_VALUE : Integer.MAX_VALUE;
            for (Span span : mSpans) {
                if (!span.mViews.isEmpty()) {
                    if (anchorInfo.layoutFromEnd) {
                        View view = span.mViews.get(span.mViews.size() - 1);
                        anchorPos = Math.max(anchorPos, helper.getPosition(view));
                    } else {
                        View view = span.mViews.get(0);
                        anchorPos = Math.min(anchorPos, helper.getPosition(view));
                    }
                }
            }

            int offset = INVALID_OFFSET;
            if (!isOutOfRange(anchorPos)) {
                //判断anchorPos是否是第一个元素
                boolean isStartLine = anchorPos == range.getLower();
                View view = helper.findViewByPosition(anchorPos);

                if (view != null) {
                    if (anchorInfo.layoutFromEnd) {
                        anchorInfo.position = anchorPos;
                        final int endRef = orientationHelper.getDecoratedEnd(reference);
                        if (endRef < anchorInfo.coordinate) {
                            offset = anchorInfo.coordinate - endRef;
                            offset += (isStartLine ? 0 : mainGap);
                            anchorInfo.coordinate = orientationHelper.getDecoratedEnd(view) + offset;
                        } else {
                            offset = (isStartLine ? 0 : mainGap);
                            anchorInfo.coordinate = orientationHelper.getDecoratedEnd(view) + offset;
                        }

                    } else {
                        anchorInfo.position = anchorPos;
                        final int startRef = orientationHelper.getDecoratedStart(reference);
                        if (startRef > anchorInfo.coordinate) {
                            // move align up
                            offset = anchorInfo.coordinate - startRef;
                            offset -= (isStartLine ? 0 : mainGap);
                            anchorInfo.coordinate = orientationHelper.getDecoratedStart(view) + offset;
                        } else {
                            // 此时如果anchorPos的元素是第一行(startLine)中的元素,
                            // 第一个元素已经滑动出屏幕,
                            // 则offset = -mainGap;
                            // 之后执行span.cacheReferenceLineAndClear(helper.getReverseLayout() ^ anchorInfo.layoutFromEnd,
                            // offset, orientationHelper);
                            // 会造成anchorPos的元素一直往上移动
                            offset = -(isStartLine ? 0 : mainGap);
                            anchorInfo.coordinate = orientationHelper.getDecoratedStart(view) + offset;
                        }
                    }
                }
            }

            if (BuildConfig.DEBUG) {
                Log.d(TAG, "checkAnchorInfo span.cacheReferenceLineAndClear()");
            }

            boolean isSpanStartLine = anchorPos - range.getLower() < mNumLanes;
            for (Span span : mSpans) {
                if (!span.mViews.isEmpty()) {
                    //如果是从尾部开始的
                    if (anchorInfo.layoutFromEnd) {
                        span.cacheReferenceLineAndClear(helper.getReverseLayout() ^ anchorInfo.layoutFromEnd, isSpanStartLine? 0:offset, orientationHelper);
                    }else {
                        View view = span.mViews.get(0);
                        //判断是否需要添加offset
                        if (isSpanStartLine && anchorPos == helper.getPosition(view)){
                            span.cacheReferenceLineAndClear(helper.getReverseLayout() ^ anchorInfo.layoutFromEnd, 0, orientationHelper);
                        } else {
                            span.cacheReferenceLineAndClear(helper.getReverseLayout() ^ anchorInfo.layoutFromEnd, offset, orientationHelper);
                        }
                    }
                }
            }
        }
    }

Alzzzz avatar Oct 31 '17 03:10 Alzzzz

fixbug

修改后就正常了

Alzzzz avatar Oct 31 '17 03:10 Alzzzz

如果这个padding是staggeredLayoutHelper的属性的话,vlayout的意图还是希望第一列和第二列的第一个元素的上边距都有一个padding空白,看你截图的效果,好像第一列没有padding的空白,但第二列的有,这个是符合你的效果? 所以还需要看一下你的staggeredLayoutHelper设置的属性、以及position = 0 和 1 的两个view的属性,按照预期,他们的top本应该对齐,所以不该发生那样的问题。

longerian avatar Oct 31 '17 11:10 longerian

我的ViewHolder是同一个,只是有高有低而已,这是我瀑布流helper的设置方法 mStaggeredGridLayoutHelper.setMargin(ScreenUtils.Dp2Px(getContext(), 10), 0, ScreenUtils.Dp2Px(getContext(), 10), 0); mStaggeredGridLayoutHelper.setHGap(ScreenUtils.Dp2Px(getContext(), 5)); mStaggeredGridLayoutHelper.setVGap(ScreenUtils.Dp2Px(getContext(), 5));

如果我的VGap设置为0的话也不会出现这个问题。

根据checkAnchorInfo()方法里的判断 final boolean layoutInVertical = helper.getOrientation() == VERTICAL; 当我把第一列的第一个元素滑到屏幕外,但第二列第一个元素还在屏幕中时,这个判断就是false的,此时按照原来代码, offset = -(isStartLine ? 0 : mainGap); span.cacheReferenceLineAndClear(helper.getReverseLayout() ^ anchorInfo.layoutFromEnd, offset, orientationHelper); 第二列会自动向上移动一个VGap的距离。

Alzzzz avatar Nov 01 '17 02:11 Alzzzz

什么情况下会出现『当我把第一列的第一个元素滑到屏幕外,但第二列第一个元素还在屏幕中时』?讲道理一开始他们的顶部是对齐的,应该是一起移动到屏幕外

longerian avatar Nov 01 '17 09:11 longerian

@longerian 之前我发的那个GIF图就是这样的情况啊,左边的第一个元素已经滑上去了,右边的第一个元素那不是还在外面嘛...这时候刷新就会有问题

Alzzzz avatar Nov 01 '17 09:11 Alzzzz

嗯 我先研究一下纯瀑布流时的表现。按理不应该出现左边的滑上去,右边的没有滑动。

longerian avatar Nov 01 '17 09:11 longerian

是这样的,一开始是正常一起滑动的,但是当左边第一个View滑动出去后,此时停止手动滑动,然后现在子View中又有刷新布局的操作,例如gif图里面的动画或者是视频播放等,就会造成右边列自动往上移动一段距离。

主要原因就是布局的时候拿的mCachedStart不正确。

Alzzzz avatar Nov 01 '17 10:11 Alzzzz

我再次测了一下,确实也有这个问题,但这样改还是有问题,我在demo里跑布局彻底乱掉了,所以我再想想更好的办法。

longerian avatar Nov 09 '17 02:11 longerian

OK,由于上拉加载也经常会错位,我查了一下是因为缓存在上拉加载后被清除了,我把清楚缓存的操作放到了外面,只有在重新布局的时候才清除缓存。 @Override public void onItemsChanged(LayoutManagerHelper helper) { // mLazySpanLookup.clear(); }

Alzzzz avatar Nov 10 '17 08:11 Alzzzz

没想到过了快两年才来回复,我正在测试改动这里,按道理不应该简单粗暴的clear掉,这会导致append数据之后因为这里clear了布局开始各种问题

MikeAfc avatar Jul 23 '19 12:07 MikeAfc