3

I've implemented an HorizontalScrollView with a RecyclerView data, the problem is that on my Adapter code, I've implemented a logic that when an item is clicked it zooms. The problem is crearly on this video, I have no clue what's going on - I tested everything with a class with a boolean or int saying that this item is clicked and then on theonBindViewHolder ask for this item and if it's clicked, then zoom again, and if it's not then zoom.

I know it's confusing, but with the video helps explain.

My list_row.xml is :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
>

<RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    >

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/RLimage"
        android:layout_centerHorizontal="true"
        android:layout_centerInParent="true"
        >

        <ImageView
            android:layout_centerInParent="true"
            android:id="@+id/thumbnail"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/ic_launcher"
            />

    </RelativeLayout>
    <RelativeLayout
        android:layout_centerHorizontal="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/RLimage"
        >
        <TextView
            android:layout_marginTop="14dp"
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""
            android:textColor="#222"
            android:textSize="12sp"/>
    </RelativeLayout>


</RelativeLayout>

The fragment where I have this RecyclerView is this :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:layout_weight="1">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rcyList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="-20dp"
        android:paddingLeft="8dp"
        android:paddingRight="8dp" />

</FrameLayout>

This is my onCreateView() from my Fragment

@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {
    this.mContext = getActivity();
    View rootView = inflater.inflate(R.layout.fragment_carta, container, false);
    rv = (RecyclerView) rootView.findViewById(R.id.rcyList);
    CustomLinearLayoutManager layoutManager = new CustomLinearLayoutManager(mContext);
    layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
    rv.setLayoutManager(layoutManager);
    // Adding code here
    dataModelList = new ArrayList<dataModel>();
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    dataModelList.add(new dataModel(ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher),"1234"));
    adapter = new MyRecyclerViewAdapter(mContext, dataModelList);
    rv.setAdapter(adapter);

    return rootView;

}

And the adapter is this :

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.CustomViewHolder> {

private Context mContext;
View animatedView = null;
private List<dataModel> dataModelList;
int animatedIndex = -1; // Initially no view is clicked so -1
//private PopulateListView populateListview;


public MyRecyclerViewAdapter(Context context, List<dataModel> items) {
    this.dataModelList = items;
    this.mContext = context;
    //this.populateListview = populateListview;
}

@Override
public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, final int i) {
    //View per each row
    final View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_row, null);
    CustomViewHolder viewHolder = new CustomViewHolder(view);
    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            if (animatedView == null) {
                animatedView = view;
            } else {
                animatedView.setAnimation(null);
                animatedView = view;
            }
            ScaleAnimation fade_in = new ScaleAnimation(1f, 1.2f, 1f, 1.2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            fade_in.setDuration(200);     // animation duration in milliseconds
            fade_in.setFillAfter(true);    // If fillAfter is true, the transformation that this animation performed will persist when it is finished.
            view.startAnimation(fade_in);
        }
    });
    return viewHolder;
}


@Override
public void onBindViewHolder(final CustomViewHolder customViewHolder, final int i) {
    //Setting text view title and drawable
    dataModel dataModel = dataModelList.get(i);
    customViewHolder.imageView.setImageDrawable(dataModel.icon);
    customViewHolder.textView.setText(dataModel.title);

    if(animatedIndex == i){
        ScaleAnimation fade_in = new ScaleAnimation(1f, 1.2f, 1f, 1.2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        fade_in.setDuration(200);     // animation duration in milliseconds
        fade_in.setFillAfter(true);    // If fillAfter is true, the transformation that this animation performed will persist when it is finished.
        customViewHolder.itemView.startAnimation(fade_in);
    }

    customViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            animatedIndex = i;
            if (animatedView == null) {
                animatedView = customViewHolder.itemView;
            } else {
                animatedView.setAnimation(null);
                animatedView = customViewHolder.itemView;
            }
            //populateListview.PopulateListView(String.valueOf(i));
            ScaleAnimation fade_in = new ScaleAnimation(1f, 1.2f, 1f, 1.2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            fade_in.setDuration(200);     // animation duration in milliseconds
            fade_in.setFillAfter(true);    // If fillAfter is true, the transformation that this animation performed will persist when it is finished.
            customViewHolder.itemView.startAnimation(fade_in);
        }
    });
}

@Override
public int getItemCount() {
    return dataModelList.size();
}

public class CustomViewHolder extends RecyclerView.ViewHolder {
    protected ImageView imageView;
    protected TextView textView;

    public CustomViewHolder(View view) {
        super(view);
        this.imageView = (ImageView) view.findViewById(R.id.thumbnail);
        this.textView = (TextView) view.findViewById(R.id.title);
    }
}

NOTE : I'm also using this class to avoid the scroll if it's not on the RecyclerView

TL;DR

The main problem is that with this code I can zoom an item BUT there are moments that when I'm scrolling (left or right) this item losses the zoom and I don't know why. The bug that is shown on the video is the critical bug I guess...

Skizo-ozᴉʞS
  • 16,233
  • 16
  • 66
  • 129

4 Answers4

4

This is happening because your RecyclerView, by definition, is recycling its views. When a view pops out of the bounds of the RecyclerView, it is scrapped, and then bound with a different data model. The view's transformation data (translate, scale, rotation) is reset when this happens.

To overcome this issue, you'll need add some indication that the view is zoomed to the data model (such as isSelected).

In your onBindViewHolder method, you'll need to check the data model if the view is selected, and if so, set the scale of the view to 1.2, otherwise, 1.0. Note that here you'll want to SET the scale, not animate the scale, because we assume that the animation already occurred when the user touched the view. When we are binding the data on the view, we're merely trying to recreate the state of the view to what it was last time it was bound.

In your onCreateViewHolder method, you're setting the onClickListener on the inflated view. Inside this new onClick method, you should set the new "isSelected" field to true / false depending on the previous value.

In your onBindViewHolder method, you should remove the code adding a new onClickListener (because this is redundant). Here you should check if the dataModel.isSelected value and set the scaleX/scaleY accordingly.

Remember, the views inside the RecyclerView should be considered raw templates, and driven by the data that you bind them with in the onBindViewHolder method. You cannot rely on what already exists in them (such as their animation value, etc).

Gil Moshayof
  • 16,053
  • 4
  • 41
  • 54
  • Oh, I understand but as I said I tried the way "isSelected"... and it doesn't work – Skizo-ozᴉʞS Nov 09 '15 at 00:17
  • @GilMoshayof: I am facing a related problem ... could you please help me out with my question [here](http://stackoverflow.com/q/34431734/3287204) ? – Y.S Dec 27 '15 at 10:37
2

As others have suggested in the answer, the problem here is related to View Recycling.

I debugged the above code found that while scrolling sometimes onBindViewHolder() is not called , causing the view to loose the animation effect. So using a boolean in the data model didn't helped.

As suggested in other answer by using setIsRecyclable(false) will do the trick for you, so you can definetly use that.

One other thing i noticed that the view was scrolling when you scroll anywhere on the screen, and to overcome that you might have to override LinearLayoutManager, but i think that will mess with the each item view's height. Will require to dig somewhat deep in to that.

As a alternative you can use TwoWayView to achieve the same instead of RecyclerView.

You can find the library for the same here TwoWayView

I have tested your above code with TwoWayView and it works fine. I am posting the files here, you can check for yourself. If you need more explanation please ask..

layout_twowayview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

<org.lucasr.twowayview.TwoWayView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/lvItems"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:drawSelectorOnTop="false"
    android:orientation="horizontal"
    tools:context=".MainActivity" />

 </LinearLayout>

TwoWayViewActivity

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import com.savitriya.samples.R;
import com.savitriya.samples.adapter.UsersAdapter;
import com.savitriya.samples.model.DataModel;
import org.lucasr.twowayview.TwoWayView;
import java.util.ArrayList;

/**
* Created by Satyen on 11/7/15.
*/
public class TwoWayViewActivity extends Activity {

ArrayList<DataModel> dataModels;
ScaleAnimation scaleAnimation = null, emptyAnimation = null;
TwoWayView twoWayView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.layout_twowayview);

    twoWayView = (TwoWayView) findViewById(R.id.lvItems);
    scaleAnimation = new ScaleAnimation(1f, 1.3f, 1f, 1.3f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    scaleAnimation.setInterpolator(new AccelerateInterpolator());
    scaleAnimation.setStartTime(0);
    scaleAnimation.setDuration(0);     // animation duration in milliseconds
    scaleAnimation.setFillAfter(true);

    emptyAnimation = new ScaleAnimation(1f, 1f, 1f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    emptyAnimation.setInterpolator(new AccelerateInterpolator());
    emptyAnimation.setStartTime(0);
    emptyAnimation.setDuration(0);     // animation duration in milliseconds
    emptyAnimation.setFillAfter(true);

    dataModels = new ArrayList<>();
    dataModels.add(new DataModel(0, R.mipmap.ic_launcher, "Data 1", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(1, R.mipmap.ic_launcher, "Data 2", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(2, R.mipmap.ic_launcher, "Data 3", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(3, R.mipmap.ic_launcher, "Data 4", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(4, R.mipmap.ic_launcher, "Data 5", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(5, R.mipmap.ic_launcher, "Data 6", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(6, R.mipmap.ic_launcher, "Data 7", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(7, R.mipmap.ic_launcher, "Data 8", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(8, R.mipmap.ic_launcher, "Data 9", 0, emptyAnimation, Color.parseColor("#ffffff")));
    dataModels.add(new DataModel(9, R.mipmap.ic_launcher, "Data 10", 0, emptyAnimation, Color.parseColor("#ffffff")));

    UsersAdapter usersAdapter = new UsersAdapter(TwoWayViewActivity.this, dataModels);
    twoWayView.setAdapter(usersAdapter);
}
}

Adapter Code

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.savitriya.samples.R;
import com.savitriya.samples.model.DataModel;
import java.util.ArrayList;

/**
* Created by Satyen on 11/7/15.
*/
public class UsersAdapter extends ArrayAdapter<DataModel> {

ArrayList<DataModel> users;
ScaleAnimation scaleAnimation = null, emptyAnimation = null;

View view;
int animatedIndex = -1;

// View lookup cache
private static class ViewHolder {
    TextView name;
    ImageView icon;
}

public UsersAdapter(Context context, ArrayList<DataModel> users) {
    super(context, R.layout.list_row, users);
    this.users = users;

    scaleAnimation = new ScaleAnimation(1f, 1.3f, 1f, 1.3f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    scaleAnimation.setInterpolator(new AccelerateInterpolator());
    scaleAnimation.setStartTime(0);
    scaleAnimation.setDuration(0);     // animation duration in milliseconds
    scaleAnimation.setFillAfter(true);

    emptyAnimation = new ScaleAnimation(1f, 1f, 1f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    emptyAnimation.setInterpolator(new AccelerateInterpolator());
    emptyAnimation.setStartTime(0);
    emptyAnimation.setDuration(0);     // animation duration in milliseconds
    emptyAnimation.setFillAfter(true);

}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    // Get the data item for this position
    final DataModel user = getItem(position);
    // Check if an existing view is being reused, otherwise inflate the view
    ViewHolder viewHolder; // view lookup cache stored in tag
    view = convertView;
    if (view == null) {
        viewHolder = new ViewHolder();
        LayoutInflater inflater = LayoutInflater.from(getContext());
        view = inflater.inflate(R.layout.list_row, parent, false);
        viewHolder.name = (TextView) view.findViewById(R.id.title);
        viewHolder.icon = (ImageView) view.findViewById(R.id.thumbnail);
        view.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) view.getTag();
    }
    // Populate the data into the template view using the data object
    viewHolder.name.setText(user.getTitle());
    viewHolder.icon.setImageResource(R.mipmap.ic_launcher);

    view.startAnimation(user.getAnimation());
    // Return the completed view to render on screen

    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //view.startAnimation(scaleAnimation);
            if (animatedIndex != -1) {
                DataModel dataModel = getItem(animatedIndex);
                dataModel.setAnimation(emptyAnimation);
            }
            animatedIndex = position;
            user.setAnimation(scaleAnimation);
            notifyDataSetChanged();
        }
    });
    return view;
}

@Override
public int getCount() {
    return users.size();
}
}

Adapter Row

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="120dp"
android:layout_height="120dp"
android:animateLayoutChanges="true"
android:animationCache="true"
android:gravity="center"
android:orientation="vertical">

<RelativeLayout
    android:id="@+id/targetView"
    android:layout_width="80dp"
    android:layout_height="100dp"
    android:padding="5dp">

    <ImageView
        android:id="@+id/thumbnail"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_centerInParent="true"
        android:background="@drawable/rectangle"
        android:scaleType="centerCrop"
        android:src="@drawable/locate" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/thumbnail"
        android:layout_centerHorizontal="true"
        android:text="dafdafda"
        android:textColor="#222"
        android:textSize="12sp" />

</RelativeLayout>

</LinearLayout>
Satyen Udeshi
  • 3,175
  • 1
  • 16
  • 25
  • @SatyenUdeshi: I am facing a related problem ... could you please help me out with my question [here](http://stackoverflow.com/q/34431734/3287204) ? – Y.S Dec 27 '15 at 10:40
1

I think this may helpRecyclerView Clicks

Mahmoud.M
  • 186
  • 11
1

i think i solved it try this as adapter

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

/**
 * Created by Ma7mo0oed on 11/8/2015.
 */
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.CustomViewHolder> {

    private Context mContext;
    View animatedView = null;
    private List<dataModel> dataModelList;
    int animatedIndex = -1; // Initially no view is clicked so -1

    public MyRecyclerViewAdapter(Context context, List<dataModel> items) {
        this.dataModelList = items;
        this.mContext = context;
    }

    @Override
    public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        //View per each row
        final View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_row, null);
        CustomViewHolder viewHolder = new CustomViewHolder(view);
        return viewHolder;
    }


    @Override
    public void onBindViewHolder(CustomViewHolder customViewHolder, final int i) {
        dataModel dataModel = dataModelList.get(i);
        customViewHolder.imageView.setImageDrawable(dataModel.icon);
        customViewHolder.textView.setText(dataModel.title);
        customViewHolder.setIsRecyclable(false);
        if (animatedIndex == i) {
            animat(customViewHolder.itemView);
        }
        customViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animatedIndex = i;
                animat(v);
            }
        });
    }

    public void animat(View v) {
        if (animatedView == null) {
            animatedView = v;
        } else {
            animatedView.setAnimation(null);
            animatedView = v;
        }
        ScaleAnimation fade_in = new ScaleAnimation(1f, 1.2f, 1f, 1.2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        fade_in.setDuration(200);     // animation duration in milliseconds
        fade_in.setFillAfter(true);    // If fillAfter is true, the transformation that this animation performed will persist when it is finished.
        v.startAnimation(fade_in);
    }

    @Override
    public int getItemCount() {
        return dataModelList.size();
    }

    public class CustomViewHolder extends RecyclerView.ViewHolder {
        protected ImageView imageView;
        protected TextView textView;


        public CustomViewHolder(View view) {
            super(view);
            this.imageView = (ImageView) view.findViewById(R.id.thumbnail);
            this.textView = (TextView) view.findViewById(R.id.title);

        }
    }
}   
Mahmoud.M
  • 186
  • 11
  • Let's say under this HorizontalScrollView I'll add a ListView, and if user starts to scroll horizontally it will load the ScrollView everytime? – Skizo-ozᴉʞS Nov 09 '15 at 00:30
  • 1
    i'm not sure what are you asking but since the two views are separated the change in one of them won't affect the other unless you want too btw did the adapter worked – Mahmoud.M Nov 09 '15 at 00:39
  • 1
    What does `customViewHolder.setIsRecyclable(false);` do? – Skizo-ozᴉʞS Nov 09 '15 at 00:49
  • your good you notice that ok i was thinking what is wrong with this code so i started to debug it and i notice that in onBindViewHolder the value of the view is changing and i remembered that the recyclerView works in a way that it always Recycling the view and not Creating new one like ListView so i thought there must be a method that can stop the Recycling and make it some how Static and i found it and it worked – Mahmoud.M Nov 09 '15 at 01:00