smoothScrollToPosition not working in recyclerView - android

I created custom LinearLayoutManager class.My goal is to smoothScrollToPosition with animation.Here is a my code:
public class LinearLayoutManagerWithSmoothScroller extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 100f;
public LinearLayoutManagerWithSmoothScroller(Context context) {
super(context, VERTICAL, false);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
private class TopSnappedSmoothScroller extends LinearSmoothScroller {
public TopSnappedSmoothScroller(Context context) {
super(context);
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return LinearLayoutManagerWithSmoothScroller.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected float calculateSpeedPerPixel
(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH/displayMetrics.densityDpi;
}
#Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
}
}
Also,I created custom LayoutAnimation in RecyclerView.Here is a xml code
<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="#anim/item_animation_from_bottom"
android:delay="15%"
android:animationOrder="normal"
/>
And here is my java code.
leaderBoardAdapter = new SPGamificationLeaderBoardAdapter(response.list, getContext());
leaderBoardRecyclerView.setAdapter(leaderBoardAdapter);
leaderBoardRecyclerView.setHasFixedSize(true);
leaderBoardRecyclerView.setNestedScrollingEnabled(false);
LayoutAnimationController controller =
AnimationUtils.loadLayoutAnimation(getContext(), R.anim.layout_animation_from_bottom);
leaderBoardRecyclerView.setLayoutManager(smoothScroller);
leaderBoardRecyclerView.setLayoutAnimation(controller);
leaderBoardRecyclerView.scheduleLayoutAnimation();
leaderBoardRecyclerView.post(() -> leaderBoardRecyclerView.smoothScrollToPosition(getPosition(response)));
My problem is that, both options(smoothScrollToPosition and LayoutAnimation) does not working same time.I removed smoothScrollToPosition and layout animation worked ,and removed smoothScrollToPosition - layout animation has worked.
Is any way to use both functions same time? what's wrong in my code?

You can use the scrollToPosition(itemNo) for achieving your goal.
Like below:
recyclerview.scrollToPosition(20);
Using smoothScrollToPosition , items are scroll very fast thats why animation is not worked.

Related

Recyclerview animation issue

I have a Recyclerview the items of which contain icons that I am trying to rotate by specific degrees (counter clockwise). Problem is some of the icons do not rotate at all, or after I scroll (or when the activity resumes) they are set back to their initial position. As seen in the image below, the first 3 arrows have not been rotated at all, while the 4th arrow is pointing correctly.
Below is the custom adapter
public class CustomListAdapter extends RecyclerView.Adapter<CustomListAdapter.TransportationViewHolder> {
Context ctx;
ArrayList<Transportation> transportationArrayList, copy;
public CustomListAdapter(Context ctx, ArrayList<Transportation> transportation) {
this.ctx = ctx;
this.transportationArrayList = new ArrayList<>(transportation);
}
#NonNull
#Override
public TransportationViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.transportation_item_layout, parent, false);
return new CustomListAdapter.TransportationViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull TransportationViewHolder holder, int position) {
Transportation transportation = transportationArrayList.get(position);
holder.transportationFleetTypeTextView.setText(description);
rotateHeading((float)transportation.getHeading(), holder.headingImageView);
}
#Override
public int getItemCount() {
return transportationArrayList.size();
}
public static class TransportationViewHolder extends RecyclerView.ViewHolder {
TextView transportationFleetTypeTextView;
ImageView transportationImageView, headingImageView;
public TransportationViewHolder(View itemView) {
super(itemView);
transportationFleetTypeTextView = itemView.findViewById(R.id.transportation_item_text);
transportationImageView = itemView.findViewById(R.id.transportation_item_image);
headingImageView = itemView.findViewById(R.id.heading_image);
}
public void clearAnimation() {
itemView.clearAnimation();
}
}
#Override
public long getItemId(int position) {
return position;
}
public void rotateHeading(float rotation, ImageView imageView)
{
AnimationSet animSet = new AnimationSet(true);
animSet.setInterpolator(new DecelerateInterpolator());
animSet.setFillAfter(true);
animSet.setFillEnabled(true);
final RotateAnimation animRotate = new RotateAnimation(0f, 360f-rotation,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animRotate.setDuration(0);
animRotate.setFillAfter(true);
animSet.addAnimation(animRotate);
imageView.startAnimation(animSet);
}
#Override
public void onViewDetachedFromWindow(#NonNull final TransportationViewHolder holder) {
super.onViewDetachedFromWindow(holder);
holder.clearAnimation();
}
}
And below is the way I initialize the adapter (which is within a class that extends AsyncTask).
mainActivity.customListAdapter = new CustomListAdapter(mainActivity, transportationArrayList);
mainActivity.customListAdapter.setHasStableIds(true);
mainActivity.customListAdapter.notifyDataSetChanged();
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mainActivity);
recyclerView = findViewById(R.id.transportationRecyclerView);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(customListAdapter);
Could someone explain to me what I am doing wrong and why this is happening?
Since the duration is zero and you only want the result of the animation and not the animation itself, I suggest that you simplify the code by replacing the line
rotateHeading((float)transportation.getHeading(), holder.headingImageView);
with
holder.headingImageView.seRotation(360 = (float)transportation.getHeading())
and deleting all the animation code.
This change may not fix the underlying issue that you are having, but it will eliminate animation as the source of the problem.

can't get ScrollView from ViewPager2 in OnPageChangeCallBack

I have a ViewPager2 contains ScrollView.
What the problem is, when I try to get ScrollView of current page in onPageSelected(), it doesn't work.
Here I'd like to set previous scrollY to the ScrollView when user back to see selected page. (because scrollY is reset for some reason before that)
My code is below.
ViewPagerAdapter.java (edited)
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewHolder> {
private LayoutInflater mInflater;
private List<String> mText;
private ViewPager2 pager2;
MainActivity main;
public ViewPagerAdapter(Context context, List<String> data, ViewPager2 pager2, MainActivity main){
this.mInflater = LayoutInflater.from(context);
this.mText = data;
this.pager2 = pager2;
this.main = main;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view = mInflater.inflate(R.layout.tab_scroll_item, parent, false);
return new ViewPagerAdapter.ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, int position){
holder.scrollView.setTag("scv_tab" + position);
holder.scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY != 0) {
main.setScrollY(scrollY, getPosition());
}
System.out.println("onScrollChanged : " + scrollY);
}
});
holder.textView.setEnabled(false);
holder.textView.setEnabled(true);
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
holder.textView.setTag("tv_tab" + position);
}
#Override
public int getItemCount(){
return mText.size();
}
protected int getPosition(){
return pager2.getCurrentItem();
}
public class ViewHolder extends RecyclerView.ViewHolder{
ScrollView scrollView;
TextView textView;
public ViewHolder(View itemView){
super(itemView);
scrollView = itemView.findViewById(R.id.tab_scroll);
textView = itemView.findViewById(R.id.tab_textview);
}
}
[REVICED]onBindViewHolder
#Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
holder.scrollView.setTag("scv_tab" + position);
holder.scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY != 0) {
main.storeScrollY(scrollY, position);
}
System.out.println("onScrollChanged : " + scrollY);
}
});
int y = main.retrieveScrollY(position);
holder.scrollView.setScrollY(y);
holder.textView.setEnabled(false);
holder.textView.setEnabled(true);
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
holder.textView.setTag("tv_tab" + position);
}
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
...
mPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if(!searchView.isIconified()){
searchView.setIconified(true);
}
if(highlightModel.getHighlitedOrNot(position)){
searchText.deleteTextHighlight(position);
highlightModel.setHighlitedOrNot(position, false);
}
int positionY[] = getScrollFromViewModel();
ScrollView sv = findScrollView(); // HERE IS THE PROBLEM
if(sv != null) {
sv.setScrollY(positionY[position]);
}else{
System.out.println("sv is null"); // ALWAYS SHOWS NULL
}
}
});
...
searchView = (SearchView) findViewById(R.id.searchview);
searchView.setOnSearchClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int actualResult = searchText.scrollToHighlightedWord(findTextView()
, findScrollView() // here findScrollView() works perfectly.
, searchResultIndex);
if(actualResult == (searchResultIndex + 1)) {
++searchResultIndex;
}else if(actualResult == searchResultIndex){
showToastAtMain("last word");
}else{
forOnClose();
}
}
});
}
});
private ScrollView findScrollView(){
ScrollView sv = mPager2.findViewWithTag("scv_tab" + mPager2.getCurrentItem());
return sv;
}
tab_scroll_item.xml (edited)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="#+id/tab1_layout">
<ScrollView
android:id="#+id/tab_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tab_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lineSpacingExtra="7dp"
android:textIsSelectable="true"
android:paddingStart="9dp"
android:paddingTop="9dp"
android:paddingEnd="9dp"/>
</ScrollView>
</LinearLayout>
Any advice is truly appreciated. Thank you for reading this long question.
onPageSelected will be called way earlier than selected page drawing mechanism, before any onCreateViewHolder and onBindViewHolder. I would advise you to "incject" somehow your data into your "page" (is it View or whole Fragment? that makes big difference) and in your case set this scroll position inside onBindViewHolder. This will make rendering of RecyclerView draw even first frame in correct scrolled position. Your way, even when you will wait some time for RecyclerView drawing, will make that onBindViewHolder will draw first frame on 0 scroll and your method will scroll a bit in next frames - this will be visible like some blink or fast-autoscroll (I think this behavior isn't intended)
edit - add proper method for setting scroll for your Adapter even before super call
#Override
public void onPageSelected(int position) {
int positionY[] = getScrollFromViewModel();
adapter.setScrollForPosition(positionY[position]);
super.onPageSelected(position);
...
for adapter
private final HashMap<Integer, Integer> scrollYHistory = new HashMap<>();
public void setScrollForPosition(int position, int scrollY){
scrollYHistory.put(position, scrollY);
}
use stored scrollY value in onBindViewHolder and clean entry in your HashMap
#Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
Integer scrollY = scrollYHistory.remove(position);
if (scrollY == null) scrollYa = 0; // may be null if not stored yet!
holder.scrollView.setScrollY(scrollY);
...
also remove unnecessary reference to MainActivity main in adapter's constructor and variable inside, it already gets Context reference and doesn't (shouldn't) need to know which Activity is creating it
edit - expanding comment:
instead of calling scrollView.setScrollY straight inside onBindViewHolder you should set up your TextView at first, then wait for rendering it and then scrolling to proper position
#Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
...
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
...
holder.textView.post(new Runnable(){
// this will be called after nearest drawing
#Override
public void run() {
Integer scrollY = scrollYHistory.remove(position);
if (scrollY == null) scrollYa = 0; // may be null if not stored yet!
holder.scrollView.setScrollY(scrollY);
}
});
}
Thanks to snachmsm, I got to know that onPageSelected will be called way earlier than ViewPager2 drawing. And for some reason I found my solution using mPager2.postDelayed().
After adding these, I got NO null ScrollView here.
mPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
...
//Here I added onPageScrollStateChanged() and postDelayed() in it.
#Override
public void onPageScrollStateChanged(int state){
if(state == ViewPager2.SCROLL_STATE_SETTLING){
mPager2.postDelayed(new Runnable() {
#Override
public void run() {
int position = mPager2.getCurrentItem();
int y = scrollModel.getPreviousScrollY(position);
ScrollView sv = findScrollView();
sv.scrollTo(0, y);
}
}, 50);
}
}
});
}

RecyclerView ItemAnimator animateAdd & animateChange both at the same time?

I'm trying to do this animation:
to insert a new item others move down as it does by default. but the first item and the second change. the first is inserted with ViewType 1 then the second switches the ViewType to 2. I currently manage 3 view types(adaptadorNotificaciones.java).
1 full size. notificacion_item1.xml
2 padding. notificacion_item2.xml
3 separator. section.xml
I extend from DefaultItemAnimator as follows (animadroNotificaciones):
public class animadroNotificaciones extends DefaultItemAnimator {
#Override
public boolean animateAdd(RecyclerView.ViewHolder holder){
return true;
}
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY){
return super.animateChange(oldHolder,newHolder,fromX,fromY,toX,toY);
}
#Override
public void onAnimationFinished(RecyclerView.ViewHolder holder){
super.onAnimationFinished(holder);
}
#Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY){
return super.animateMove(holder,fromX,fromY,toX,toY);
}}
I'm also picturing it like a stack:
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
llm.setReverseLayout(true);
llm.setStackFromEnd(true);
recyclerView.setLayoutManager(llm);
so when I add an item the scroll has to move up:
recyclerView.scrollToPosition(arreglo.size()-1);
The whole code to insert an item (adaptadorNotificaciones.java):
public void addNotificacion(ItemLista notificacion,RecyclerView recyclerView){
String mesAnterior = "";
String mesInsertado = "";
if(arreglo.size()!=0)
mesAnterior = ManejadorFechas.getFecha(arreglo.get(arreglo.size()-1).getTiempo(),false);
mesInsertado = ManejadorFechas.getFecha(notificacion.getTiempo(),false);
if(!mesInsertado.equals(mesAnterior) && arreglo.size()!=0){
arreglo.get(arreglo.size()-1).setTipo(2);
arreglo.add(new ItemLista(3,mesAnterior));
arreglo.add(notificacion);
notifyItemRangeChanged(arreglo.size()-3,arreglo.size()-1);
recyclerView.scrollToPosition(arreglo.size()-1);
}else if(arreglo.size()!=0){
arreglo.get(arreglo.size()-1).setTipo(2);
arreglo.add(notificacion);
notifyItemRangeChanged(arreglo.size()-2,arreglo.size()-1);
recyclerView.scrollToPosition(arreglo.size()-1);
}else{
arreglo.add(notificacion);
notifyItemInserted(arreglo.size()-1);
recyclerView.scrollToPosition(arreglo.size()-1);
}
}
but the above code gives me the next result:
I think It is caused by the following line:
notifyItemRangeChanged(arreglo.size()-3,arreglo.size()-1);
anyone has an idea how to fix this? Thanks in advance.
I solved it, this way:
rv.setItemAnimator(new animadroNotificaciones(){
#Override
public void onAnimationFinished(RecyclerView.ViewHolder viewHolder) {
adaptador.notifyDataSetChanged();
}
});

RecyclerView disappearing images

What I am trying to create is a horizontal scrolling image gallery. I have a RecyclerView (support 22.0.0). The problem I am having is that when I scroll to the end and then scroll back, usually one image will be missing sometimes two. Strangely when I keep swiping back and forth, a different image could be missing. Here is the layout for the item:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="160dp">
<ImageView
android:id="#+id/product_variation_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:layout_gravity="center"/>
Here is the Adaper:
public class TestAdapter extends RecyclerView.Adapter<TestAdapter.ViewHolder> {
private String[] mDataset;
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView mImageView;
public ViewHolder(View v) {
super(v);
mImageView = (ImageView) v.findViewById(R.id.product_variation_image);
}
}
public TestAdapter(String[] myDataset) {
mDataset = myDataset;
}
#Override
public TestAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.variaton_list_item, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mImageView.setImageDrawable(null);
String url = mDataset[position];
Log.i("TEST", "position = " + position);
((MainActivity)MainActivity.getInstance()).imageDownloader.download(url, holder.mImageView);
}
#Override
public int getItemCount() {
return mDataset.length;
}
The download method fetches the image, from a URL or gets it from the memory if it has been cached. This works fine in all other layouts e.g. ListView or GridView. Here is the code I use to set it up in the Fragment:
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(layoutManager);
This is in the onCreateView method. When I get the urls I populate them and set the adapter using:
myDataset[i] = imageURL; // for each image
mAdapter = new TestAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
The interesting thing is the line in the onBindViewHolder method in the adapter, where I log the position. What I have found is that cells where the image is not shown is that this method is not being called. It is like it is skipping that cell for some reason. Even stranger, if I hold a cell and keep swiping from left to right, if a cell goes off screen and then comes back in, its image as gone as again the onBindViewHolder method is not called.
Would it be possible to test something out? Could you use this library to load the images from the URLs ? http://square.github.io/picasso/ It caches everything and it handles everything in an async manner.
Use it something like ...
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Picasso.with(mImageView.getContext()).cancelRequest(holder.mImageView);
String url = mDataset[position];
Picasso.with(mImageView.getContext()).load(url).placeholder(R.drawable.placeholder).into(holder.mImageView);
}
... and see if it still doesn't display some images. If it does, then at least you'll be 100% sure the problem is not in your downloading mechanism (which I think it might be).
If you're using Android Studio then just add the dependency compile 'com.squareup.picasso:picasso:2.5.2', if not you can add the library you find at the above link.
It's worth a try ...
The one class that I did not think would matter was the one that was causing the issue. I am not sure what the reason is, but it resides in a custom ImageView class that I am using for recycling that I got from the BitmapFun sample.
public class RecyclingImageView extends ImageView {
public RecyclingImageView(Context context) {
super(context);
}
public RecyclingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* #see android.widget.ImageView#onAttachedToWindow()
*/
#Override
protected void onAttachedToWindow() {}
/**
* #see android.widget.ImageView#onDetachedFromWindow()
*/
#Override
protected void onDetachedFromWindow() {
// This has been detached from Window, so clear the drawable
setImageDrawable(null);
super.onDetachedFromWindow();
}
/**
* #see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
*/
#Override
public void setImageDrawable(Drawable drawable) {
// Keep hold of previous Drawable
final Drawable previousDrawable = getDrawable();
// Call super to set new Drawable
super.setImageDrawable(drawable);
// Notify new Drawable that it is being displayed
notifyDrawable(drawable, true);
// Notify old Drawable so it is no longer being displayed
notifyDrawable(previousDrawable, false);
}
/**
* Notifies the drawable that it's displayed state has changed.
*
* #param drawable
* #param isDisplayed
*/
private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
if (drawable instanceof RecyclingBitmapDrawable) {
// The drawable is a CountingBitmapDrawable, so notify it
((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
} else if (drawable instanceof LayerDrawable) {
// The drawable is a LayerDrawable, so recurse on each layer
LayerDrawable layerDrawable = (LayerDrawable) drawable;
for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
}
}
}
}
When I replace this with a normal ImageView, I no longer get the problem.
We can fix the issue by extends LinearLayoutManager and ImageView.
1. Creats a PrecachingLinearLayoutManager
public class PrecachingLinearLayoutManager extends LinearLayoutManager {
private static final int DEFAULT_EXTRA_LAYOUT_SPACE = 600;
private int extraLayoutSpace = -1;
#SuppressWarnings("unused")
private Context mContext;
public PrecachingLinearLayoutManager(Context context) {
super(context);
this.mContext = context;
}
public PrecachingLinearLayoutManager(Context context, int extraLayoutSpace) {
super(context);
this.mContext = context;
this.extraLayoutSpace = extraLayoutSpace;
}
public PrecachingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.mContext = context;
}
public void setExtraLayoutSpace(int extraLayoutSpace) {
this.extraLayoutSpace = extraLayoutSpace;
}
#Override
protected int getExtraLayoutSpace(RecyclerView.State state) {
if (extraLayoutSpace > 0) {
return (extraLayoutSpace);
}
return (DEFAULT_EXTRA_LAYOUT_SPACE);
}
}
2. Use PrecachingLinearLayoutManager to replace LinearLayoutManager
DisplayMetrics displayMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
PrecachingLinearLayoutManager layout = new PrecachingLinearLayoutManager(getActivity());
layout.setExtraLayoutSpace(displayMetrics.heightPixels);
recyclerview.setLayoutManager(layout);
3. Creats a RecycleImageView
private Object tag = null;
#Override
protected void onAttachedToWindow() {
Object tag = getTag();
if (tag == null || !tag.equals(this.tag)) {
// Will cause displayed bitmap wrapper to
// be 'free-able'
setImageDrawable(null);
this.tag = null;
super.onDetachedFromWindow();
}
super.onAttachedToWindow();
}
#Override
protected void onDetachedFromWindow() {
Object tag = getTag();
if (tag != null) {
this.tag = tag;
} else {
// Will cause displayed bitmap wrapper to
// be 'free-able'
setImageDrawable(null);
this.tag = null;
super.onDetachedFromWindow();
}
}
4. Use RecycleImageView to replace ImageView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:extends="http://schemas.android.com/apk/res/com.yourdomain.yourpackage"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/viewgroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<com.yourdomain.yourpackage.RecycleImageView
android:id="#+id/photo"
android:layout_width="40dp"
android:layout_height="40dp"
extends:delayable="true"
android:contentDescription="#string/nothing"
android:src="#drawable/photo_placeholder" >
</com.yourdomain.yourpackage.RecycleImageView>
</LinearLayout>

StaggeredGridLayoutManager and moving items

I have created a very simple project, displaying 28 images with StaggeredGridLayoutManager by recyclerview. but as I scroll the recyclerview it moves items for example from left to right or swap the column of left and right.
codes:
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
public class MainActivity extends Activity {
String mImageDir;
private RecyclerView mRecyclerView;
private StaggeredGridLayoutManager mLayoutManager;
MyRecyclerAdapter myRecyclerAdapter;
List<ImageModel> mImageList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView)findViewById(R.id.recyclerview_rootview);
mLayoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
mLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setHasFixedSize(false);
mImageList = new ArrayList<ImageModel>();
for (int i = 1; i < 29 ; i++) {
ImageModel img = new ImageModel();
img.setTitle("Image No " + i);
int drawableResourceId = this.getResources().getIdentifier("image"+String.valueOf(i), "drawable", this.getPackageName());
img.setResId(drawableResourceId);
mImageList.add(img);
}
myRecyclerAdapter = new MyRecyclerAdapter(MainActivity.this,mImageList);
mRecyclerView.setAdapter(myRecyclerAdapter);
}
}
And the adapter:
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {
private List<ImageModel> mItems;
Context mContext;
public MyRecyclerAdapter(Context context,List<ImageModel> objects) {
mContext = context;
mItems = objects;
}
static class ViewHolder extends RecyclerView.ViewHolder{
public ImageView mImageView;
public TextView mTextView;
public View rootView;
public ViewHolder(View itemView) {
super(itemView);
rootView = itemView;
mImageView =(ImageView)itemView.findViewById(R.id.image);
mTextView =(TextView)itemView.findViewById(R.id.title);
}
}
#Override
public int getItemCount() {
return mItems.size();
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
ImageModel item = mItems.get(position);
Picasso.with(mContext).load(item.getResId()).into(holder.mImageView);
holder.mTextView.setText(item.getTitle());
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int arg1) {
LayoutInflater inflater =
(LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View convertView = inflater.inflate(R.layout.item, parent, false);
return new ViewHolder(convertView);
}
}
and a sample moving item:
http://i.imgur.com/FUapm2K.gif?1
if you play (scroll up and down) you can discover more interesting animation :-)
How to prevent that and having stable layout like an ordinary listview?
Edit
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
ImageModel item = mItems.get(position);
RelativeLayout.LayoutParams rlp = (RelativeLayout.LayoutParams)holder.mImageView.getLayoutParams();
float ratio = item.getHeight()/item.getWidth();
rlp.height = (int)(rlp.width * ratio);
holder.mImageView.setLayoutParams(rlp);
Picasso.with(mContext).load(item.getResId()).into(holder.mImageView);
holder.mTextView.setText(item.getTitle());
}
This is happening because SGLM does not keep any w/h information about the views. So each time a View is rebound, it gets the place holder size first and then the final size when the image is loaded.
Loading the actual image w/ different size (than place holder) triggers a new layout request, and during that layout request, SGLM detects that there will be gaps in the UI (or some item w/ higher position appears below an item w/ lower position) thus re-orders items w/ an animation.
You can avoid it by setting your place holder image to the dimensions of the actual image. If you don't have it ahead of time, you can save them after the image is loaded for the first-time and use it in the next onBind call.
What worked for me was to disable all animation on the recycle view when using StaggeredGridLayoutManager.
mRecyclerView.setItemAnimator(null);
You can create your own animator if you only want to restrict moving animations and keep the add and remove item animations.
You can try this
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL);
manager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
mRecyclerView.setLayoutManager(manager);
after you set this, you'll find there is a blank at the top when you scroll to the top. continue to set this
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
((StaggeredGridLayoutManager)recyclerView.getLayoutManager()).invalidateSpanAssignments();
}
});
It's work for me, I get this way somewhere. I hope it can help you!
Change this line
mLayoutManager.setGapStrategy(
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
to this line
mLayoutManager.setGapStrategy(
StaggeredGridLayoutManager.GAP_HANDLING_NONE);
Add the following line at the end of the method:
holder.mImageView.requestLayout();
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
...
holder.mImageView.requestLayout();
}
This fixed the issue for me.
Maybe this is a more efficient way:
mBrandRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if(newState == RecyclerView.SCROLL_STATE_IDLE){
mBrandRecyclerView.invalidateItemDecorations();
}
}
});
For more information:
https://github.com/ibosong/CommonItemDecoration
Set your recyclerview's height fixed and item height and width wrap-content for staggered-layout-manager
First set gap strategy like following code :
mLayoutManager = new StaggeredGridLayoutManager(SPAN_COUNT, StaggeredGridLayoutManager.VERTICAL);
mLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
and then add your item to mItems and then use:
mAdapter.notifyItemInserted(mItems.size() - 1);
this method is better than using:
mAdapter.notifyDataSetChanged();

Categories

Resources