RecyclerView - Getting adapter position in ItemDecoration's onDraw method? - android

In itemDecoration's on draw method it has a override method called getItemOffsets which I can get the adapter's position using:
#Override
public void getItemOffsets(#NonNull Rect outRect, #NonNull View view, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
int childCount = parent.getChildCount();
int position = parent.getChildAdapterPosition(view);
}
How ever if I try this method inside onDraw:
#Override
public void onDraw(#NonNull Canvas c, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition( ??? );
}
What do I pass for the view parameters?

In onDraw() You are working on one canvas. To modify one child, You have to iterate by all the parent's children.
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
int childAdapterPosition = parent.getChildAdapterPosition(child);
// ...
}

Related

How to use scroll listener in onBindViewHolder in Adapter of a RecyclerView?

I have a long itemView in RecyclerView adapter and what I want is a listener to check if a view in itemView is visible or not, while scrolling recyclerView.
What I need is somthing like "onRecyclerViewScrolled()" in this example code:
public class MyAdapter extends RecyclerView.Adapter<ViewHolder> {
#Override
public void onBindViewHolder(#NonNull final ViewHolder myViewHolder, int
position) {
onRecyclerViewScrolled() {
if (!isVisible(myViewHolder.myView)) {
//do something
}
}
}
}
public static boolean isVisible(final View view) {
if (view == null) {
return false;
}
if (!view.isShown()) {
return false;
}
final Rect actualPosition = new Rect();
view.getGlobalVisibleRect(actualPosition);
final Rect screen = new Rect(0, 0, getScreenWidth(), getScreenHeight());
return actualPosition.intersect(screen);
}
public static int getScreenWidth() {
return Resources.getSystem().getDisplayMetrics().widthPixels;
}
public static int getScreenHeight() {
return Resources.getSystem().getDisplayMetrics().heightPixels;
}
You can set addOnScrollListener on your RecyclerView to get notified when the user scrolls, and you can get the first/last visible views from LinearLayoutMangar or GridLayoutManager:
RecyclerView recycler = findViewById(R.id.recycler_view);
reycler.setAdapter(YOUR_ADAPTER);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recycler.setLayoutManager(layoutManager);
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//First visible item position.
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
//Last visible item position.
int lastVisiblePosition = layoutManager.findLastVisibleItemPosition();
/*If you really need to access your visible views,
Loop through all the visible views*/.
for (int position = firstVisiblePosition; position == lastVisiblePosition; position++) {
View lastVisibleView = layoutManager.getChildAt(firstVisiblePosition);
//do something
}
dataList.get(firstVisiblePosition);
}
});
More info from documentation RecyclerView.OnScrollListener have to functions:
onScrolled(RecyclerView recyclerView, int dx, int dy)
Callback method to be invoked when the RecyclerView has been scrolled.
onScrollStateChanged(RecyclerView recyclerView, int newState)
Callback method to be invoked when RecyclerView's scroll state changes.
Apparently, onScrolled is called when the scroll is completed.
For more info see Yigit answer`s

RecyclerView ItemDecoration - How to draw a different width divider for every viewHolder?

Currently my divider is only drawing one width:
How would can I add an extra divider for every increment position in my recyclerview?
Here is my ItemDecoration class:
public SimpleDivider(Context mContext, ArrayList<Integer> mDepth) {
mDivider = ContextCompat.getDrawable(mContext, R.drawable.recycler_view_divider);
this.mContext = mContext;
this.mDepth = mDepth;
dividerMargin = 15;
}
#Override
public void onDraw(#NonNull Canvas c, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
int top = 0;
int bottom = parent.getHeight();
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; ++i) {
int right = dividerMargin;
int left = 0;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
Edit1: Here's the Adapter. I thought it wouldn't be needed because all the logic would be written inside the ItemDecoration class.
private ArrayList<String> mList;
public class ViewHolder extends RecyclerView.ViewHolder{
TextView singleMessageComment;
public ViewHolder(#NonNull View itemView) {
super(itemView);
singleMessageComment = itemView.findViewById(R.id.item_child_comment);
}
}
public AdapterTest(ArrayList<String> mList) {
this.mList = mList;
}
#NonNull
#Override
public AdapterTest.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_single_layout, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull AdapterTest.ViewHolder viewHolder, int i) {
viewHolder.singleMessageComment.setText(mList.get(i));
}
#Override
public int getItemCount() {
return mList.size();
}
Adding decorations:
recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
recyclerView.addItemDecoration(new LeftDividerItemDecorator(this));
Declaration of the left divider item decorator:
public class LeftDividerItemDecorator extends RecyclerView.ItemDecoration {
private final Drawable mDivider;
private final Rect mBounds = new Rect();
private final Context mContext;
LeftDividerItemDecorator(Context context) {
mContext = context;
mDivider = context.getResources().getDrawable(R.drawable.divider);
}
public void onDraw(#NonNull Canvas c, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
if (parent.getLayoutManager() != null && mDivider != null) {
drawLeftDivider(c, parent);
}
}
private void drawLeftDivider(Canvas canvas, RecyclerView parent) {
canvas.save();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, mBounds);
int childAdapterPosition = parent.getChildAdapterPosition(child);
int left = parent.getPaddingLeft();
// Solid size according to divider.xml width
//int right = left + (mDivider.getIntrinsicWidth());
// Dynamic size according to divider.xml width multiplied by child number
int right = left + (mDivider.getIntrinsicWidth() * (childAdapterPosition + 1));
int top = child.getTop();
int bottom = child.getBottom();
// Draw left vertical divider
mDivider.setBounds(
left,
top,
right,
bottom
);
mDivider.draw(canvas);
}
canvas.restore();
}
// Handles dividers width - move current views to right
public void getItemOffsets(#NonNull Rect outRect, #NonNull View view, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
if (mDivider == null) {
outRect.set(0, 0, 0, 0);
} else {
int childAdapterPosition = parent.getChildAdapterPosition(view);
outRect.set(mDivider.getIntrinsicWidth() * childAdapterPosition, 0, 0, 0);
}
}
}
Divider's xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="4dp"
android:height="4dp" />
<solid android:color="#color/colorAccent" />
</shape>
Preview:

RecyclerView - Shifting layout when creating item divider

I'm implementing a vertical divider for each of my viewHolders using itemDecoration. How would I make it so the layout draws the dividers first, and then it draws the views to the RIGHT of the dividers?
#Override
public void onDraw(#NonNull Canvas c, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
int top = 0;
int bottom = parent.getHeight();
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
parent.getLayoutManager().getDecoratedBoundsWithMargins(child, this.mBounds);
int right = 150;
int left = 0;
this.mDivider.setBounds(left, top, right, bottom);
this.mDivider.draw(c);
}
}
You need to override getitemoffsets() as well. There you apply offsets to the items:
#Override
public void getItemOffsets(
Rect outRect,
View view,
RecyclerView parent,
RecyclerView.State state
) { outRect.left = 150; }

Decorate only the absolute first item of RecyclerView

I need to add a separator only to the absolute first item of my recycle view.
I have already read How to selectively decorate RecyclerView items , and i understand that
The (onDraw) method loops over all the child views currently in the RecyclerView visible on the screen.
and my problem is exactly that. since it executes every time the views in the RecycleView change, even if i am able to locate and decorate only the first item, as soon as i scroll down, the decoration shifts to the current first item.
In that link the selection is done by the method isDecorated which looks at the instance of the current child's ViewHolder. My guess is that the guy wanted to decorate with respect to the ViewHolder type, which is not my problem since i have only one type of element in my RecyclerView
This is my DividerItemDecoration.java
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
if (parent.getChildAdapterPosition(view) == 1) {
outRect.top = outRect.top + mDivider.getIntrinsicHeight();
}
return;
}
#Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
int dividerLeft = parent.getPaddingLeft();
int dividerRight = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
if(i==0 ){
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int dividerTop = child.getBottom() + params.bottomMargin;
int dividerBottom = dividerTop + mDivider.getIntrinsicHeight();
mDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
mDivider.draw(canvas);
}
}
}
}
Please consider that i have searched all day and could only find examples and gists to add decorator to every item, to selectively add it based on the type, but nothing really tackling with the absolute position.
Also, please no libraries like https://github.com/yqritc/RecyclerView-FlexibleDivider, i want to learn , not to copy.
You can check if it's the first element,
Here's an example:
Just implement getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().
So you do something like:
#Override
public int getItemViewType(int position) {
if (position == 0)
return 1;
else
return 2;
}
then in onCreateViewHolder inflate your different layout according to your viewType.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1) {
// inflate your first item layout & return that viewHolder
} else {
// inflate your other item layout & return that viewHolder
}
}

How to avoid decorating the first row of recyclerview

How can I go about not decorating the first row in my recyclerview so that my Item decorator decorates every other row?
Just check the child position in
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
}
}
in your RecyclerView.ItemDecoration class.
Also a nice solution (to learn something more) can be found at https://github.com/yqritc/RecyclerView-FlexibleDivider
In your onBindView() method, test the position var for 0.
Agree with korrekorre's answer.
My code looks like:
#Override public void getItemOffsets(
Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int l, t, r, b;
l = leftMargin;
t = topMargin;
r = rightMargin;
b = bottomMargin;
if (mIgnoreAfterLast && parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() - 1) {
if (mHorizontal) {
r = 0;
} else {
b = 0;
}
}
if (mIgnoreBeforeFirst && parent.getChildAdapterPosition(view) == 0) {
if (mHorizontal) {
l = 0;
} else {
t = 0;
}
}
outRect.set(l, t, r, b);
}
Just check for first position and 'return' in your custom RecyclerView.ItemDecoration. To be more exact, something like this:
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
if (position == RecyclerView.NO_POSITION || position == 0) {
return;
}
...The rest of your decorator logic goes here...
}

Categories

Resources