【问题标题】:Why notifyItemChanged only works in post Runnable after setAdapter?为什么 notifyItemChanged 仅在 setAdapter 之后的后 Runnable 中有效?
【发布时间】:2023-03-26 23:51:02
【问题描述】:
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    RecyclerView list = (RecyclerView) findViewById(R.id.list);
    list.setLayoutManager(new LinearLayoutManager(this));
    final RecyclerView.Adapter adapter = getAdapter();
    list.setAdapter(adapter);

    adapter.notifyItemChanged(1, new Object());//this doesn't work
    list.post(new Runnable() {
        @Override
        public void run() {
            adapter.notifyItemChanged(1, new Object());//this works
        }
    });


}

@NonNull
private RecyclerView.Adapter getAdapter() {
    return new RecyclerView.Adapter() {
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ItemViewHolder( LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.list_view_item, parent, false));
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            ItemViewHolder item = (ItemViewHolder) holder;
            item.tv.setText("test");
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
            if(payloads.isEmpty())
                onBindViewHolder(holder, position);
            else{
                ItemViewHolder item = (ItemViewHolder) holder;
                item.tv.setText("changed!!!!!! ");
            }
        }

        @Override
        public int getItemCount() {
            return 40;
        }

        class ItemViewHolder extends RecyclerView.ViewHolder{
            TextView tv;
            ItemViewHolder(View itemView) {
                super(itemView);
                tv = (TextView) itemView.findViewById(R.id.tv);
            }
        }
    };
}}

这里非常简单的例子只是为了测试notifyItemChanged,它只在发布到消息队列时起作用,而不是在setAdapter之后直接调用。 setAdapter 触发对requestlayout() 的调用,这是否意味着notifyItemChanged 在布置项目时不会发生?

【问题讨论】:

    标签: android android-recyclerview android-adapter


    【解决方案1】:

    经过一番调查,事实证明notifyItemChanged 仅在附加RecyclerView 并且实际上已完成onLayout 时才有效,这发生在onCreate 之后。

    notifyItemChanged -> requestLayout() 调用的RecyclerView.onLayout() 内部,调用processAdapterUpdatesAndSetAnimationFlags() 检查要更新的项目是否有可用的ViewHolder,在这种情况下为null,因为在onCreate() , RecyclerView 未附加到窗口,因此未对 RecyclerView 进行任何测量和布局

    显然RecyclerView 的完整绘制发生在onCreate()OnResume() 之后的某个时间

    @Override
    protected void onResume() {
        super.onResume();
        boolean a = list.isAttachedToWindow();//this is false!
    }
    

    所以要让notifyItemChangedonCreate 上工作

    list.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                adapter.notifyItemChanged(1, new Object());//this will update the item
            }
        });
    
    
    
    new Thread((new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(28);//small amount of delay,below 20 doesn't seem to work
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyItemChanged(1, new Object());//also works
                    }
                });
            }
        })).start();
    

    我猜这是因为ViewRootImpl.performTraversal() 是由系统控制的,它发生在DecorView 附加到onCreate 期间的窗口之后的某个时间

    我假设这只会在onCreateonResume 期间发生,如果稍后调用它可能不会发生。

    【讨论】:

    • 很好的研究,谢谢!布局更改侦听器似乎是要走的路,但我相信您想在通知项目后立即删除侦听器,否则每次布局更改都会通知它
    【解决方案2】:

    消息队列,但不是在 setAdapter 之后直接调用。 setAdapter 触发 requestlayout() 的调用,这是否意味着如果它在布局项目的中间不会发生 notifyItemChanged?

    setAdapter 同步向 UI 消息队列添加消息以呈现项目。但是该消息将在稍后处理,而不是在您调用setAdapter 时。

    如果你立即调用notifyItemChanged,那么它会在实际渲染之前被调用,因为setAdapter生成的消息还没有被发送出去。

    另一方面,将其放入自己的消息中将与setAdapter 执行相同的操作 - 它会同步生成稍后发送的消息。因此,消息将在 setAdapter 发送的消息之后发送,这就是它起作用的原因。

    【讨论】:

    • 感谢您的回复,我也是这么想的,但是当setAdapter() 被调用时,我只能找到requestlayout()requestLayout() 是否只告诉视图重新测量并绘制自己?,也许我错了,显示setAdapter() 正在发布到 UI 消息队列的源代码在哪里?
    • 是的,但是重新测量和重新绘制视图是通过消息队列工作的,所以requestLayout() 只安排这个过程。每个视图都附加了一个 UI 处理程序。如果此时没有附加处理程序,那么所有这些渲染逻辑都是通过 RunQueue 类安排的,您可以在这里查看:grepcode.com/file/repository.grepcode.com/java/ext/…
    • 嗨,谢谢你,真的很有帮助。但是我对If you call notifyItemChanged right away, then it will be called before actual rendering because the message generated by setAdapter hasn't been dispatched yet.还是有点疑惑,为什么会这样呢?
    • 只是为了澄清,通过“发布 setAdapter 消息”,我的意思是调用 setAdapter,它调用 requestLayout(),它将渲染消息发布到队列。
    • 对此我没有答案,抱歉。我的猜测:当您在适配器对象上调用 notifyItemChanged 时,它会将调用委托给监听更改的观察者列表。可能是观察者尚未附加的情况,因为它发生在requestLayout() 完成适配器设置到回收器视图之后。这只是一个猜测,请随时从这里继续您的研究:) 我认为,将消息发布到队列会有所不同,这证明了方法调用内部没有发布。否则它可以在没有它的情况下工作,就像任何其他方法调用一样。
    猜你喜欢
    • 1970-01-01
    • 2020-08-02
    • 2018-11-16
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-21
    • 2012-05-15
    相关资源
    最近更新 更多