问题
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);
}
}
};
}}
very simple example here just to test notifyItemChanged
, it works only when post to the Message queue, but not by invoking directly after setAdapter
.
setAdapter
triggers a call to requestlayout()
, does that mean notifyItemChanged
can't happen if it is in middle of laying out items?
回答1:
After a bit of investigation it turns out that notifyItemChanged
only works when RecyclerView
is attached and actually has completed onLayout
which happens after onCreate
.
internally during RecyclerView.onLayout()
which is called by notifyItemChanged -> requestLayout()
, processAdapterUpdatesAndSetAnimationFlags()
is called which checks if the item that is to be updated has an available ViewHolder
which in this case is null
because during onCreate()
, RecyclerView
is not attached to the window, thus no measurement and layout has done to RecyclerView
Apparently complete drawing of RecyclerView
happens sometime after onCreate()
and OnResume()
@Override
protected void onResume() {
super.onResume();
boolean a = list.isAttachedToWindow();//this is false!
}
So to make notifyItemChanged
work on onCreate
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();
I guess the reason for this is ViewRootImpl.performTraversal()
is controlled by the system and it happens sometime after DecorView
is attached to the window which is during onCreate
I'm assuming this can only happen during onCreate
and onResume
, it may not happen if called later.
回答2:
Message queue, but not by invoking directly after setAdapter. setAdapter triggers a call to requestlayout(), does that mean notifyItemChanged can't happen if it is in middle of laying out items?
setAdapter
synchronously adds a message to the UI message queue to render items. But the message will be handled later, not when you call setAdapter
.
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.
On the other hand, putting it in its own message will do the same thing as setAdapter
does - it will synchronously generate a message which will be dispatched later. Thus, the message will be dispatched after the one that was sent by setAdapter
and that's why it works.
来源:https://stackoverflow.com/questions/41992586/why-notifyitemchanged-only-works-in-post-runnable-after-setadapter