What is the difference between getAdapterPosition()
and getLayoutPosition
?
https://proandroiddev.com/difference-between-position-getadapterposition-and-getlayoutposition-in-recyclerview-80279a2711d1. I have read this article, but don’t really understand the part about getLayoutPosition()
Please explain it and the process behind it in simpler terms.
When would you use either while building an app?
Advertisement
Answer
Short Answer
getAdapterPosition()
returns the updated position for the current ViewHolder
object right after any of the notify*()
calls.
getLayoutPosition()
returns the updated value a bit later, after a new layout has been dispatched.
Long Answer
getAdapterPosition()
Adapter position returned by getAdapterPosition()
will be updated immediately in one of the notify*
calls that you make when your data source in Adapter
changes:
notifyItemInserted(...) notifyItemRemoved(...) ... notifyDataSetChanged()
For example, if we look into notifyItemInserted()
then it will invoke:
notifyItemInserted(int position) | v ... | v public void onItemRangeInserted(int positionStart, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { triggerUpdateProcessor(); } }
mAdapterHelper.onItemRangeInserted(positionStart, itemCount)
will basically update mPendingUpdates
that is used for retrieving a current adapter position. The next call triggerUpdateProcessor()
will request a layout update, but more in the next section.
Now, if you call getAdapterPosition()
for your ViewHolder
right afterwards, it will return the updated position. However, getLayoutPosition()
will still return the old value.
getLayoutPosition()
triggerUpdateProcessor
will call requestLayout()
and return. That will schedule the layout update of RecyclerView
(later onLayout
will be invoked to signal the layout change):
public void onItemRangeInserted(int positionStart, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { triggerUpdateProcessor(); } }
That requires some time, though, but the update will become possible after onLayout
callback is fired and dispatchLayout()
is called:
protected void onLayout(boolean changed, int l, int t, int r, int b) { ... dispatchLayout(); ... }
Basically, after onLayout
returns, the layout position will already be updated.
Illustration
This can be illustrated with a simple RecyclerView
in which notifyInserted()
is called when a new element is added at the top of the list:
... public void addElementToHead() { elements.add(0, "New Element"); notifyItemInserted(0); Toast.makeText(context, "Adapter position : " + currentFirstHolder.getAdapterPosition() + ", Layout position : " + currentFirstHolder.getLayoutPosition(), Toast.LENGTH_LONG).show(); } ...
In this case, currentFirstHolder
is a ViewHolder
that corresponds to the initial first element in the adapter. However, after a new element is added as a first element, a position of currentFirstHolder
must change to 1
. So, getAdapterPosition()
is 1
, while getLayoutPosition()
is still 0
because RecyclerView
hasn’t completed a layout update yet.
Here I log the content of the ViewHolder
to show how it changes after onLayout
is finally called:
... @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); ViewHolder viewHolder = findViewHolderForAdapterPosition(0); Toast.makeText(getContext(), "From RecyclerView#onLayout - Adapter position : " + viewHolder.getAdapterPosition() + ", Layout position : " + viewHolder.getLayoutPosition(), Toast.LENGTH_LONG).show(); } ...
So, it will look as below (it will be shown right after the first toast is gone):
Now, the full relevant code can be seen below. I didn’t include layouts and other files for brevity.
MainActivity.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerViewAdapter adapter = new RecyclerViewAdapter(this); MyRecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); Button button = findViewById(R.id.add_element_button); button.setOnClickListener(v -> adapter.addElementToHead()); } private static class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> { private final Context context; private final List<String> elements = new ArrayList<>(); private RecyclerViewAdapter(Context context) { this.context = context; for (int i = 0; i < 1; i++) { elements.add("Element " + i); } } public void addElementToHead() { elements.add(0, "New Element"); notifyItemInserted(0); Toast.makeText(context, "Adapter position : " + currentFirstHolder.getAdapterPosition() + ", Layout position : " + currentFirstHolder.getLayoutPosition(), Toast.LENGTH_LONG).show(); } private RecyclerViewHolder currentFirstHolder; @NonNull @Override public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View item = LayoutInflater.from(context). inflate(R.layout.list_item, parent, false); return new RecyclerViewHolder(item); } @Override public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) { holder.textView.setText(elements.get(position)); if (position == 0) { currentFirstHolder = holder; } } @Override public int getItemCount() { return elements.size(); } public static class RecyclerViewHolder extends RecyclerView.ViewHolder { private final TextView textView; public RecyclerViewHolder(@NonNull View itemView) { super(itemView); this.textView = itemView.findViewById(R.id.element_view); } } } }
MyRecyclerView.java
public class MyRecyclerView extends RecyclerView { public MyRecyclerView(@NonNull Context context) { super(context); } public MyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); ViewHolder viewHolder = findViewHolderForAdapterPosition(0); Toast.makeText(getContext(), "From RecyclerView#onLayout - Adapter position : " + viewHolder.getAdapterPosition() + ", Layout position : " + viewHolder.getLayoutPosition(), Toast.LENGTH_LONG).show(); } }