Is that possible to add footer with item decoration, and not using adapter? Since I'm working with a very complex adapter with already lot of different viewholder types, I'd like to add an identical footer seamlessly to every list in my app.
As far as i can tell, this inst't best practice.
Here's description from RecyclerView.ItemDecoration class:
/**
* An ItemDecoration allows the application to add a special drawing and layout offset
* to specific item views from the adapter's data set. This can be useful for drawing dividers
* between items, highlights, visual grouping boundaries and more.
However You can set specific behaviour when implementing your own divider based on adapter viewtype divider has to deal with. Here's an example code i used in one online course:
public class Divider extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mOrientation;
public Divider(Context context, int orientation) {
mDivider = ContextCompat.getDrawable(context, R.drawable.divider);
if (orientation != LinearLayoutManager.VERTICAL) {
throw new IllegalArgumentException("This Item Decoration can be used only with a RecyclerView that uses a LinearLayoutManager with vertical orientation");
}
mOrientation = orientation;
}
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {
drawHorizontalDivider(c, parent, state);
}
}
private void drawHorizontalDivider(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left, top, right, bottom;
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
//here we check the itemViewType we deal with, you can implement your own behaviour for Footer type.
// In this example i draw a drawable below every item that IS NOT Footer, as i defined Footer as a button in view
if (Adapter.FOOTER != parent.getAdapter().getItemViewType(i)) {
View current = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) current.getLayoutParams();
top = current.getTop() - params.topMargin;
bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
}
}
Or you can use a library called Flexible Divider that allows for using a custom drawable or resource to be set.
Related
I am new to android and hence RV and I am trying to achieve the layout where the first and last card are not centered and instead show more of the cards after and before them. Maybe at In this case I can see 16dp for the second cards and same thing for the penultimate card which makes the first and last card not centered.
But 8dp each for the rest of the cards so the intermediate cards appear centered. Maybe using itemDecoration somehow for the 2nd and the penultimate card somehow.
I was able to achieve showing parts of next and prev cards by following what is suggested here, but that only centers all the cards uniformly :
How to show part of next/previous card RecyclerView
I tried overriding getItemOffsets but it gets triggered everytime I scroll to the first or the last card and moves the 2nd and 2nd to last card incorrectly
and also doesn't center them correctly when I scroll to them.
public static class MyItemDecoration extends RecyclerView.ItemDecoration {
#Override
public void getItemOffsets(#NonNull Rect outRect, #NonNull View view, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
final int itemPosition = parent.getChildAdapterPosition(view);
if (itemPosition == RecyclerView.NO_POSITION) {
return;
}
final int itemCount = state.getItemCount();
if (itemCount > 0 && itemPosition == 1) {
outRect.left -= 16;
outRect.right -= 16;
}
else if (itemCount > 0 && itemPosition == itemCount - 1) {
outRect.left += 16;
outRect.right += 16;
}
}
}
RV Setup
SnapHelper snapHelper = new PagerSnapHelper();
RecyclerView rv = getBinding().rv;
rv.setOnFlingListener(null);
snapHelper.attachToRecyclerView(rv);
PagerSnapHelper centers the RecyclerView items including the decorations, so, unless the decoration widths are balanced, they won't always be centered. This may be what you are seeing.
Try the following for the decoration. This code applies the full-width decoration to the start of the first item and the end of the last item; otherwise, a half decoration width is used. By setting up the decorations this way, you are centering items that have balanced left and right decorations.
DividerItemDecoration decoration =
new DividerItemDecoration(getApplicationContext(), HORIZONTAL) {
private int mDecorationWidth = (int) (getResources().getDisplayMetrics().density * 8);
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
final int pos = parent.getChildAdapterPosition(view);
if (pos == RecyclerView.NO_POSITION) {
return;
}
if (pos == 0) {
outRect.set(mDecorationWidth, 0, mDecorationWidth / 2, 0);
} else if (pos == parent.getAdapter().getItemCount() - 1) {
outRect.set(mDecorationWidth / 2, 0, mDecorationWidth, 0);
} else {
outRect.set(mDecorationWidth / 2, 0, mDecorationWidth / 2, 0);
}
}
};
Here is a video showing the results with gray vertical dividers.
If you already have the decorations working to your satisfaction, you can override calculateDistanceToFinalSnap() in PagerSnapHelper to center all views except the first and last view as follows. See calculatedistancetofinalsnap(). Once the PageSnapHelper identifies a target view to snap to, calculatedistancetofinalsnap() is called to determine how many pixels to move to perform the snap. Here, we are moving just enough pixels to center the view (without decorations) in the RecyclerView. PageSnapHelper does the right thing for the first and last items, so we just call the super for these.
PagerSnapHelper pagerSnapHelper = new PagerSnapHelper() {
#Override
public int[] calculateDistanceToFinalSnap(#NonNull RecyclerView.LayoutManager layoutManager,
#NonNull View targetView) {
LinearLayoutManager lm = (LinearLayoutManager) layoutManager;
int pos = mRecycler.getChildAdapterPosition(targetView);
// If first or last view, the default implementation works.
if (pos == 0 || pos == lm.getItemCount() - 1) {
return super.calculateDistanceToFinalSnap(layoutManager, targetView);
}
// Force centering in the view without its decorations.
// targetCenter is the location of the center of the view we want to center.
int targetCenter = targetView.getLeft() + targetView.getWidth() / 2;
// Distance is the number of pixels to move the target so that its center
// lines up with the center of the RecyclerView (mRecycler.getWidth() / 2)
int distance = targetCenter - mRecycler.getWidth() / 2;
return new int[]{distance, 0};
}
};
Either way will work.
I want to display a View (example TextView) over the RecyclerView after scroll to a position (example: 3) so I use
public class HeaderItemDecoration extends RecyclerView.ItemDecoration {
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int topChildPosition = parent.getChildAdapterPosition(parent.getChildAt(0));
if(topChildPosition == 3) {
Log.i("TAG", "draw header");
TextView textView = new TextView(parent.getContext());
textView.setText("bbdasdasd");
textView.setBackgroundColor(Color.RED);
textView.layout(0, 0, 100, 100);
drawText(c, textView);
}
}
private void drawText(Canvas c, View header) {
c.save();
c.translate(0, 0);
header.draw(c);
c.restore();
}
}
and
mRecyclerView.addItemDecoration(new HeaderItemDecoration());
I work but the problem is this TextView will gone if I continue scroll. How to make this View always visible after I draw it? Any help or suggestion would be great appreciated.
Just change the if condition:
if(topChildPosition == 3) {
...
}
to:
if(topChildPosition >= 3) {
...
}
So the view will remain visible if you continue to scroll down.
If you want it remain visible even if you scroll back to top, just add a member variable to remember if the view is already shown, if it's shown, keep drawing it.
How would you give a vertical offset to items in a horizontal RecyclerView based on their position on the screen? As the user scrolls left or right, I want the objects to rise as they approach the middle and to lower as they approach the ends/sides of the screen.
Here's a picture of the effect I'm going for. Blue indicates scrolling left and right. Red indicates vertical offset for each item based on their position on the screen. I'd like for them to rise and lower smoothly based on position as the user scrolls left or right.
You have to create your custom ItemDecoration, which will look like something like this:
public class MyItemDecoration extends RecyclerView.ItemDecoration {
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
final int childCount = parent.getAdapter().getItemCount();
final int center = childCount >> 1;
final int currentPosition = parent.getChildAdapterPosition(view);
if (currentPosition < center) {
outRect.set(0, 0, 0, currentPosition * 10);
} else {
outRect.set(0, 0, 0, (childCount - currentPosition) * 10);
}
}
}
Usage:
recyclerView.addItemDecoration(new MyItemDecoration());
Result:
The title pretty much explains my need. I have RecyclerView and adapter with N amount of view types. I have tried 3 different third party libraries (e.g. https://github.com/kanytu/android-parallax-recyclerview) which should support parallax header view. None of them seem to work or need custom adapter which doesn't apply to my use case. Maybe they are internally playing with view types.
Is there some library which would support multiple view types in adapter and parallax scrolling header?
Thanks.
You can use an ItemDecoration to add a parallax background to specific items. This is just some simple code and can be improved in lots of ways.
public class ParallaxHeaderDecoration extends RecyclerView.ItemDecoration {
private Bitmap mImage;
public ParallaxHeaderDecoration(final Context context, #DrawableRes int resId) {
mImage = BitmapFactory.decodeResource(context.getResources(), resId);
}
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
if (parent.getChildAdapterPosition(view) == 20) {
int offset = view.getTop() / 3;
c.drawBitmap(mImage, new Rect(0, offset, mImage.getWidth(), view.getHeight() + offset),
new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()), null);
}
}
}
}
With the item decoration characteristics you can just paint bg to specific views or positions.
And you can see a more complex sample at https://github.com/bleeding182/recyclerviewItemDecorations
I have a recyclerview with a custom first row (header) containing an image and a chatview below.
If I use stackfrombottom and there are many chat messages it works as expected, the last chat item is visible at bottom and scrolling up it scrolls to start of image, like in this image:
The problem: If there are not many chat messages the image is in the center of the recyclerview (because stacked from bottom) like here:
Can this problem be fixed without rearranging the header outside of listview? Like in this third image:
Answering own question:
Here I found a tip for item decoration that just takes the header and alters the first row:
Is it possible to have the last item in a RecyclerView to be docked to the bottom if there is no need to scroll?
The code I used with this inspiration:
public class StickySummaryDecoration extends RecyclerView.ItemDecoration {
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getAdapter().getItemCount();
int lastVisibleItemPosition =
((LinearLayoutManager) parent.getLayoutManager()).findLastVisibleItemPosition();
int firstVisiblePosition =
((LinearLayoutManager) parent.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
if ((firstVisiblePosition == 0) && (lastVisibleItemPosition == (childCount - 1))) {
View summaryView = parent.getChildAt(0);
summaryView.setY(0);
} else {
parent.getChildAt(0).setVisibility(View.VISIBLE);
}
}
}
And on the recyclerview:
recyclerView.addItemDecoration(new StickySummaryDecoration(), 0);