3

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?

desertnaut
  • 57,590
  • 26
  • 140
  • 166
CodingChap
  • 1,088
  • 2
  • 10
  • 23

3 Answers3

5

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.

enter image description here

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):

enter image description here

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();
    }
}
Community
  • 1
  • 1
Anatolii
  • 14,139
  • 4
  • 35
  • 65
  • 1
    In which scenarios would you use either of them? Ex: Click events, notifications, pop ups, etc. and why? Thank you! – CodingChap Apr 02 '21 at 17:13
  • 1
    @CodingChap I would almost always use `getAdapterPosition()` in my `RecyclerView` because `getLayoutPosition()` is usually used by `RecyclerView` components internally. Maybe there's a use case for it but I can't manage to recall it right now. – Anatolii Apr 02 '21 at 18:27
  • @CodingChap `triggerUpdateProcessor()` calls `requestLayout()`. `requestLayout()` will signal the system that our layout has to be updated and returns. Then, after some time the system invokes an `onLayout` method through which the layout position is updated. – Anatolii Apr 02 '21 at 21:42
0

the getLayoutPosition() function returns the position of the ViewHolder in terms of the latest layout pass but getAdapterPosition() returns the Adapter position of the item represented by this ViewHolder and that this might be different than the getLayoutPosition() if there are pending adapter updates but a new layout pass has not happened yet.

Hamid Sj
  • 983
  • 7
  • 19
0

getAdapterPosition() is deprecated and the new method is getBindingAdapterPosition()

Returns the Adapter position of the item represented by this ViewHolder with respect to the RecyclerView.Adapter that bound it.


getLayoutPosition()

Returns the position of the ViewHolder in terms of the latest layout pass.

This position is mostly used by RecyclerView components to be consistent while RecyclerView lazily processes adapter updates.


Official doc ref

Medium ref

akhilesh0707
  • 6,709
  • 5
  • 44
  • 51