What is the difference between getLayoutPosition() and getAdapterPosition()?

Issue

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?

Solution

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();
    }
}

Answered By – Anatolii

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published