【问题标题】:Custom RecyclerView's LayoutManager. Automeasuring after animation finished on item delete自定义 RecyclerView 的 LayoutManager。项目删除动画完成后自动测量
【发布时间】:2016-10-25 13:57:40
【问题描述】:

我为RecyclerView 创建了一个custom layout manager。我已经通过 setAutoMeasureEnabled(true) 在我的经理的构造函数中启用了自动测量

RecyclerViewlayout_height 设置为wrap_content 并使用此属性LayoutManager 能够根据其中的项目测量RecyclerView 的高度。它工作得很好,但是当我删除底部的最后一个项目时,正在播放删除动画,它会导致RecyclerView 在动画完成之前测量它的高度到结果高度。

看看这个 gif。 其中绿色背景是 RecyclerView

正如您可能猜到的那样,这种行为对于添加项目是正确的,因为在这种情况下,应该在动画之前测量容器

我该如何处理这种情况以在动画完成后处理RecyclerView 自动测量? 我在onLayoutChildren 中有所有子定位逻辑,但我认为发布它不需要这个问题,并且可能如此广泛和不清楚。

可能我应该手动处理 layoutManager 的 onMeasure 拦截器(当项目被删除时它调用了 4 次(在 onItemsRemoved 调用之前一次))。所以我可以在那里设置测量高度,基于高度,我在删除开始时收到:

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    super.onMeasure(recycler, state, widthSpec, heightSpec);
    requestSimpleAnimationsInNextLayout();
    if (!isAutoMeasureEnabled()) {
        setMeasuredDimension(getWidth(), preHeight);
    }
}

@Override
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
    super.onItemsRemoved(recyclerView, positionStart, itemCount);
    setAutoMeasureEnabled(false);

    new Handler(Looper.myLooper()).postDelayed(new Runnable() {
        @Override
        public void run() {
            setAutoMeasureEnabled(true);
            requestSimpleAnimationsInNextLayout();
            requestLayout();
        }
    }, 400);

    preHeight = //calculate height before deletion
}

如您所见,我提供了一个具有近似延迟 400ms 值的处理程序,它在动画完成后执行自动测量,因此这可能会导致所需的结果,但动画的持续时间不是静态的,我不会看到任何可能收听动画完成事件的可能性。

所以,期望的行为:

顺便说一下,LinearLayoutManager 的自动测量工作方式相同

如果你只用算法的文字描述给我指出正确的方向就足够了。

【问题讨论】:

  • 我无法提高我的问题的质量,因为你在没有任何评论的情况下对我投了反对票
  • 在我看来,这个问题被完美地提出并得到了完美的回答......

标签: android android-layout animation android-recyclerview


【解决方案1】:

我终于想通了。 所以,我的解决方案:

订阅 onAdapterChanged 方法中的 dataChanges 事件并禁用自动测量。

 @Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
                             RecyclerView.Adapter newAdapter) {
    newAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            // on api 16 this is invoked before onItemsRemoved
            super.onItemRangeRemoved(positionStart, itemCount);
            /** we detected removing event, so should process measuring manually
             */
            setAutoMeasureEnabled(false);
        }
    });
     //Completely scrap the existing layout
    removeAllViews();
}

onMeasure 中,如果手动禁用自动测量,请使用当前大小:

 @Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    super.onMeasure(recycler, state, widthSpec, heightSpec);

    if (!isAutoMeasureEnabled()) {
        // we should perform measuring manually
        // so request animations
        requestSimpleAnimationsInNextLayout();
        //keep size until remove animation will be completed
        setMeasuredDimension(getWidth(), getHeight());
    }
}

动画结束后执行自动测量:

 @Override
public void onItemsRemoved(final RecyclerView recyclerView, int positionStart, int itemCount) {
    super.onItemsRemoved(recyclerView, positionStart, itemCount);
    //subscribe to next animations tick
    postOnAnimation(new Runnable() {
        @Override
        public void run() {
            //listen removing animation
            recyclerView.getItemAnimator().isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
                @Override
                public void onAnimationsFinished() {
                    //when removing animation finished return auto-measuring back
                    setAutoMeasureEnabled(true);
                    // and process onMeasure again
                    requestLayout();
                }
            });
        }
    });

这个回调链是安全的,因为如果布局管理器与 RecyclerView 分离,postOnAnimationrunnable 将不会被执行

【讨论】:

  • 请补充一点,isAutoMeasureEnabled() 方法也应该被覆盖,因为在 LinearLayoutManager 中它总是返回 true(是的,它在那里被硬编码)所以如果没有这个,你的代码将无法工作。
  • onItemRangeRemoved 每次适配器通知删除了一个项目或一系列项目但在某些情况下删除的项目数 = 0 时调用(例如,可扩展的回收器视图 - 当组已折叠且不包含任何项目)。因此,为了也涵盖这种情况,您必须在 onItemRangeRemoved 中添加一个检查,并仅在 itemCount > 0 时调用 setAutoMeasureEnabled(false)
【解决方案2】:

@Beloo 的回答对我不起作用,我怀疑是因为 setAutoMeasureEnabled 已被弃用。我只能通过覆盖 onItemsRemovedonMeasureisAutoMeasureEnabled 来使其工作。

首先创建一个变量来跟踪isAutoMeasureEnabled 属性:

private boolean autoMeasureEnabled = true;

然后覆盖isAutoMeasureEnabled以使用变量:

@Override
public boolean isAutoMeasureEnabled (){
    return autoMeasureEnabled;
}

现在您可以随意设置变量autoMeasureEnabled。覆盖onItemsRemoved

@Override
public void onItemsRemoved(@NonNull final RecyclerView recyclerView, int positionStart, int itemCount) {
    super.onItemsRemoved(recyclerView, positionStart, itemCount);
    autoMeasureEnabled = false;
    postOnAnimation(new Runnable() {
        @Override
        public void run() {
            recyclerView.getItemAnimator().isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
                @Override
                public void onAnimationsFinished() {
                    autoMeasureEnabled = true;
                    requestLayout();
                }
            });
        }
    });
}

也覆盖onMeasure,就像@Beloo 的回答一样:

    @Override
public void onMeasure(@NotNull RecyclerView.Recycler recycler, @NotNull RecyclerView.State state, int widthSpec, int heightSpec) {
    super.onMeasure(recycler, state, widthSpec, heightSpec);

    if (!isAutoMeasureEnabled()) {
        // we should perform measuring manually
        // so request animations
        requestSimpleAnimationsInNextLayout();
        //keep size until remove animation will be completed
        setMeasuredDimension(getWidth(), getHeight());
    }
}

这似乎有效。动画速度很快,但时间正确。

【讨论】:

  • 这对我有用,但是我确实需要像 @Beloo 那样覆盖函数 onAdapterChanged
【解决方案3】:

正确的方法是在 RecyclerView 本身上使用转换,以便它也为自己的绑定更改设置动画。

发件人:https://android-developers.googleblog.com/2016/02/android-support-library-232.html

请注意,尽管 RecyclerView 为其子项设置动画,但它不会为其自身的边界更改设置动画。如果您想在 RecyclerView 边界发生变化时为其设置动画,可以使用 Transition API。

如前所述,here TransitionManager.beginDelayedTransition(recyclerView) 应该可以正常工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 2021-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-06
    相关资源
    最近更新 更多