Android - StaggeredGrid wrong items disposition - android

I have a problem with my Recycler view and StaggeredGrid which cut the width by 2.
I load images into items with Picasso and when I load image first time, they are disposed strangely in the recycler view.
After reloading, everything seems good.
I think problem come from image loading : the StaggeredGrid doesn't know the image height the first time, but know after reloading because of cache.
How can i solve this problem ?

I think you have answered your own question. You need to load the images/determine their dimensions before adding the data to the recycler.

Solved by changing gap strategy to :
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
manager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
recyclerView.setLayoutManager(manager);
Change the position of items automatically

This happens because the holder dose not recognize the width and height of the Image view when you scroll up and down. It clears the upper view when you scroll down and vice versa.
Use like this :
#Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
MyViewHolder vh = (MyViewHolder) viewHolder;
ImageModel item = imageModels.get(position);
RelativeLayout.LayoutParams rlp = (RelativeLayout.LayoutParams)vh.imageView.getLayoutParams();
float ratio = item.getHeight() / item.getWidth();
rlp.height = (int) (rlp.width * ratio);
vh.imageView.setLayoutParams(rlp);
vh.positionTextView.setText("pos: " + position);
vh.imageView.setRatio(item.getRatio());
Picasso.with(mContext).load(item.getUrl()).placeholder(PlaceHolderDrawableHelper.getBackgroundDrawable(position)).into(vh.imageView);
}
For clear see this link: Picasso/Glide-RecyclerView-StaggeredGridLayoutManager

Related

How to make onBindViewHolder less expensive?[Android]

I read many articles about optimization and UI performance improvement of recyclerview. Their main point is make onBindViewHolder lighter.
List of issues I have :
My item layout has a frameLayout to show video with different width and height .I want to change the aspect ratio of frame layout according to video. My code inside onBindViewHolder is:
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) frameLayout.getLayoutParams();
if ((float) height / (float) width > (float) 1.25) {
params.dimensionRatio = "4:5";
} else {
params.dimensionRatio = width + ":" + height;
}
frameLayout.setLayoutParams(params);
This is the cause of lag. I can clearly observe. Tell me how instagram,Twitter show different ratio of video in their feed without lag
I have custom recylerView I'm creating a viewHolder instance inside it and performing some actions such as:
addOnChildAttachStateChangeListener(new OnChildAttachStateChangeListener() {
#Override
public void onChildViewAttachedToWindow(#NonNull View view) {
VideoAdapter.ViewHolder holder = (VideoAdapter.ViewHolder) view.getTag();
holder.styledPlayerView.setPlayer(exoPlayer);
holder.framelayout.addView(holder.styledPlayerView);
}
I'm doing this inside the recyclerView class. I think it is a part of onBindViewHolder as the action is on current instance of viewHolder. here adding view to frame layout is expensive. How to handle this?
I'm using 4 shape drawable,4 vector drawable & 3 custom styles. I can clearly see that when I'm using drawable it is lagging but after removing drawable the lag is decreasing to 40-50%

How to set recycler height to highest item in recyclerView?

I need to make sure that horizontal recyclerView height is the same as the height of the biggest item.
Items can have a different height (Item = always the same image + title + subtitle, title and subtitle could have infinite length).
When I set wrap_content for my recyclerView it would resize, basing on the height of visible items which makes content below recyclerView jump, and that's something I want to avoid.
What I want to achieve:
The gray area is visible viewport.
So basically I would like to get somehow hight of the biggest item, then put recyclerView height to that number.
What I already tried is approximation high of items based on length of title + subtitle but it's very inaccurate because for example even if two titles have the same text length they could have different width because of font that I use which is not a monospace font.
I just had this issue as well. My solution is:
Wrap the RecyclerView inside a ConstraintLayout.
Set the ConstraintLayout's layout_height to wrap_content.
Add an item view to the ConstraintLayout and populate it with the data of the item you expect to be the highest based on the length of its title for example.
Set the item view's visibility to invisible.
Set the RecyclerView's layout_height to zero, and make its top and bottom constraints match that of the item view.
Too late for an answer, but maybe this will help someone.
I struggled with the same issue and couldn't find an acceptable solution.
Solved by following:
First, you need to override onMeasure from the RecyclerView to save the largest element height:
class CustomRecycleView(ctx: Context, attrs: AttributeSet) : RecyclerView(ctx, attrs) {
private var biggestHeight: Int = 0
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
for (i in 0 until childCount) {
val child = getChildAt(i)
child.measure(widthSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
val h = child.measuredHeight
if (h > biggestHeight) biggestHeight = h
}
super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(biggestHeight, MeasureSpec.EXACTLY))
}
}
In you layout replace RecycleView with this CustomRecycleView:
onMeasure is called when a new element in the list is visible, and if the element is the highest, then we save this value. For example: if the first element has lowest height but lates has highest then at start RecycleView will be have height match to first element but after scrolling it will stay match to highest.
If you don't need to make RecycleView height match to highest item at start then you can stop here.
To do this at the beginning, you must make a hack (based on #MidasLefko suggestion):
To find out initially what the height of the highest element will be, you need to add a scroll mechanism to the end and the beginning. I did it as follows:
private fun initRecycleView(items: ArrayList<Object>) {
val adapter = Adapter()
rv.visibility = View.INVISIBLE
rv.vadapter = adapter
rv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
rv.setHasFixedSize(true)
rv.smoothScrollToPosition(pinnedPosts.size)
Handler().postDelayed({
rv.smoothScrollToPosition(0)
}, 300)
Handler().postDelayed({
rv.visibility = View.VISIBLE
}, 700)
}
Set the visibility of Recycle view to INVISIBLE and after 700 milliseconds to VISIBLE to make this process invisible for user. Also, scrolling to start is performed with a delay of 300 milliseconds, because without some delay it can work incorrectly. In my case, this is needed for a list of 3 elements, and these delays is optimal for me.
Also remember to remove all Handler callbacks in onStop ()
I don't think that this is possible out of the box.
Let's think for a minute about how a RecyclerView works. In order to save memory it reuses the same View objects and just binds them to new data from the list as the user scrolls. So, for example, if the user sees item's 0 and 1 then the system has only measured and laid out 2 items (and perhaps one or two more to help scroll performance).
But let's say that your tall item is number 50 in the list, when the RecyclerView binds the first few items it has no idea at all that item 50 even exists, let alone how tall it will be.
However, you can do something a bit hacky. For example, you can measure each items height after it is bound, keep track of the tallest, and then manually set the RecyclerView height to that size. With that mechanism in place you can make the RecyclerView be hidden, then manually scroll to the end of the list, scroll back to the beginning of the list, then show the RecyclerView.
Not the most elegant solution, but it should work.
Created a method to calculate the projected height of textView by trying all the description in the list to get the highest height.
public static int getHeightOfLargestDescription(final Context context, final CharSequence text, TextView textView) {
final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Point displaySize = new Point();
wm.getDefaultDisplay().getSize(displaySize);
final int deviceWidth = displaySize.x;
textView.setTypeface(Typeface.DEFAULT);
textView.setText(text, TextView.BufferType.SPANNABLE);
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
textView.measure(widthMeasureSpec, heightMeasureSpec);
return textView.getMeasuredHeight();
}
then used this method to in onCreateViewHolder to get ready with the highest height to be used while binding the view.
MyViewHolder myViewHolder = new MyViewHolder(itemView);
for (Model m : modelList) {
currentItemHeight = getHeightOfLargestDescription(context, m.description, myViewHolder.description);
if (currentItemHeight > highestHeight) {
highestHeight = currentItemHeight;
}
}
Then used this highestHeight in onBindViewHolder` to set the height of the description TexView, so that all the views always have the same height that is equal to the highest height.
viewHolder.description.setHeight(highestHeight);
Code is committed in the
https://github.com/dk19121991/HorizontalRecyclerWithDynamicHeight
Let me know if this solves your problem, if you have some more question feel free to ask.
Thanks
To view a full discussion on this solution please see below
https://stackoverflow.com/a/67403898/4828650
You may try this:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
final int newHeight = recyclerView.getMeasuredHeight();
if (0 != newHeight && minHeight < newHeight) {
// keep track the height and prevent recycler view optimizing by resizing
minHeight = newHeight;
recyclerView.setMinimumHeight(minHeight);
}
}
});
you should try with different item_view type
Try this
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = mLayoutInflater.inflate(R.layout.view_item, parent, false);
// work here if you need to control height of your items
// keep in mind that parent is RecyclerView in this case
int height = parent.getMeasuredHeight() / 4;
itemView.setMinimumHeight(height);
return new ItemViewHolder(itemView);
}
Or you can try this also
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.itemview, parent, false);
ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
layoutParams.height = (int) (parent.getHeight() * 0.3);
itemView.setLayoutParams(layoutParams);
return new MyViewHolder(itemView);
}
You can also set your itemView with fixed height.
I disabled the recycling in recycler view and it solved the issue.
recyclerView.getRecycledViewPool().setMaxRecycledViews(TYPE_CAROUSEL, 0);
this solution may have a performance issue if there are a lot of items but will work fine for a few items lets say 5 to 20 which was case for me.
recyclerViewHorizontal.setMinimumHeight(maxItemHeight) has worked well for me.

Android GridLayoutManager span size

I am implementing Dashboard in android with Recyclerview and Grid layout manager with 3 items in a row. I will get the dashboard items from server. According to the no of Items, I need to adjust the Recyclerview items to centre.
For example, If I have 11 items, I need to align last 2 items to the centre.
If I have , 10 items, last one item need to be aligned centre . For this Logic I made lot of research but didn't find any solution. Even I tried using spansize concept of GridLayoutManager but no luck.
Any help/example code would be very thankful.
Thanks in advance
If I understand your requirements correctly, there is no way to accomplish what you want with RecyclerView + GridLayoutManager. Even if you implement a custom GridLayoutManager.SpanSizeLookup, you will only be able to have your extra one or two items stretch to fill the row... you can't center them.
You can, however, accomplish what you want by using RecyclerView with a FlexboxLayoutManager, which is a part of Google's FlexboxLayout project: https://github.com/google/flexbox-layout
When you create your FlexboxLayoutManager, you need to set the FlexDirection to "row" and the JustifyContent to "center":
FlexboxLayoutManager manager = new FlexboxLayoutManager(this, FlexDirection.ROW);
manager.setJustifyContent(JustifyContent.CENTER);
Then, when you create your ViewHolders, you need to size them to one third of the RecyclerView's width:
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.item_view, parent, false);
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
layoutParams.width = (parent.getWidth() / 3) - layoutParams.leftMargin - layoutParams.rightMargin;
itemView.setLayoutParams(layoutParams);
return new MyViewHolder(itemView);
}
Here's a link to a gist for a small app that demonstrates this: https://gist.github.com/zizibaloob/0c44bfe59b371b5ae0bd2edcb4a7e592
set spanSizeLookup on your GridLayoutManage
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if(ItemList.size() % 3 == 0){
return 3;
} else if( ItemList.size() % 3 == 2){
return 2;
} else{
return 1; // number of items to span
}
}
});

layoutManager.findFirstVisibleItemPosition() with a horizontal RecyclerView

I am coding an Android application for a school project.
I have implemented a RecyclerView with horizontal scroll using LinearLayoutManager.HORIZONTAL.
I would like to make it such that a specific element, for example element n, is in the middle of the set of currently visible elements, like so:
I have searched around, and found this link: https://www.reddit.com/r/androiddev/comments/3o0xzw/how_do_you_scroll_to_an_item_with_recyclerview_to/
The solution involved taking half the sum of layoutManager.findFirstVisibleItemPosition() and layoutManager.findLastVisibleItemPosition() and adding it to the position of the element you want to be in the middle.
However, these two methods always returned RecyclerView.NO_POSITION when I used this horizontally scrolling RecyclerView, hence half the sum would be 0, and it would render the calculation useless.
Here is my code:
RecyclerView selectDay = (RecyclerView) findViewById(R.id.lv_selectDay);
daySelectorAdapter adapter = new daySelectorAdapter(this, arrayList);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
selectDay.setAdapter(adapter);
selectDay.setLayoutManager(layoutManager);
int firstVisible = layoutManager.findFirstVisibleItemPosition();
Log.d("FIRSTVISIBLE", String.valueOf(firstVisible)); // returns -1
int lastVisible = layoutManager.findLastVisibleItemPosition();
Log.d("LASTVISIBLE", String.valueOf(lastVisible)); // also -1
int halfScreenOffset = (lastVisible - firstVisible) / 2;
Log.d("HALFOFFSET", String.valueOf(halfScreenOffset));
layoutManager.scrollToPositionWithOffset((15 + halfScreenOffset), 0);
May I know if anyone has a workaround to this problem? Thanks in advance.
EDIT: When I tried implementing a onScrollListener and cast recyclerView.getLayoutManager() to LinearLayoutManager, these methods worked perfectly fine. May I know why?
EDIT 2:
I used this solution right here: RecyclerView smoothScroll to position in the center. android, However why do the methods return NO_POSITION?

Parallax effect on each item in a recycler view?

I'm trying to play with Parallax and getting some weird bugs, wondering if anyone can add some input to it. The only app I've seen implement parallax effectively is soundcloud. It's quite subtle, but each item has an image background and it has he parallax effect as you scroll.
I've created a custom RecyclerView to handle this, here is what I have so far:
public class ParallaxScrollListener extends RecyclerView.OnScrollListener {
private float scrollSpeed = 0.5f;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisible = layoutManager.findFirstVisibleItemPosition();
int visibleCount = Math.abs(firstVisible - layoutManager.findLastVisibleItemPosition());
Matrix imageMatrix;
float tempSpeed = -100;
if (dy > 0) {
tempSpeed = scrollSpeed;
} else if (dy < 0) {
tempSpeed = -scrollSpeed;
}
for (int i = firstVisible; i < (firstVisible + visibleCount); i++) {
ImageView imageView = ((MyClass.MyAdapter.MyViewHolder) recyclerView.getLayoutManager().findViewByPosition(i).getTag()).image;
if (imageView != null) {
imageMatrix = imageView.getImageMatrix();
imageMatrix.postTranslate(0, tempSpeed);
imageView.setImageMatrix(imageMatrix);
imageView.invalidate();
}
}
}
In my RecyclerView Adapter's onBindView I have the following as well:
Matrix matrix = viewHolder.image.getImageMatrix();
matrix.postTranslate(0, 0);
viewHolder.image.setImageMatrix(matrix);
viewHolder.itemView.setTag(viewHolder);
Finally inside the onViewRecycled method I have the following:
#Override
public void onViewRecycled(MyViewHolder viewHolder) {
super.onViewRecycled(viewHolder);
if (viewHolder.image != null) {
viewHolder.image.setScaleType(ImageView.ScaleType.MATRIX);
Matrix matrix = viewHolder.image.getImageMatrix();
// this is set manually to show to the center
matrix.reset();
viewHolder.image.setImageMatrix(matrix);
}
}
I been working with this code on Github to get the idea
So the parallax works, but but views in my RecyclerView move as well. I have a CardView beneath the image and it moves, creating big gaps between each item. Scrolling is what causes this, the more the scroll up and down the bigger the gaps get, and the images get smaller as the parallax moves them out of their bounds.
I've tried messing with the numbers like scrollSpeed in the OnScrollListener but while it reduces the bug it also reduces the parallax.
Has anyone got any ideas on how I can achieve a bug free parallax effect on each item in my RecyclerView? I feel like I'm getting somewhere with this but it's still very buggy and I don't know what the next step is.
P.s I've tried looking at 3rd party libraries but they all seem to only use header parallax like the CoordinatorLayout, I haven't found any that do it just on each item in a list.
I'm hoping this question gets a good discussion going even if I don't solve my problem because Parallax seems to be underused in Android and there's very little around about it.
Thanks for you time, appreciate any help.
I managed to get Parallax working with this library: https://github.com/yayaa/ParallaxRecyclerView
For anyone doing this themselves, it's still a good thing to play and see how it works.
Similar concept to my code but it actually works! haha.
You are on the right track. You have to use a ScrollListener. Furtermore, you have to access RecyclerViews LayoutManager and iterate over all items that are visible and set translateY according to the amount of pixels scrolled.
The things get a little bit more complicated, because you can't use recyclerView.getChildAt(pos) because LayoutManager is responsible to layout elements and they might be in different order in the LayoutManager than getChildAt(pos).
So the algorithm basically should look like this (pseudo code, assuming LinearLayoutManager is used):
for (int i = layoutManager.findFirstVisibleItemPosition(); i <= layoutmanager.findLastVisibleItemPosition; i++){
// i is the adapter position
ViewHolder vh = recyclerView.findViewHolderForAdapterPosition(i);
vh.imageView.setTranslationY( computedParalaxOffset ); // assuming ViewHolder has a imageView field on which you want to apply the parallax effect
}

Categories

Resources