Wednesday, May 16, 2018

How to animate RecyclerView like vertical Viewpager transforms

Leave a Comment

I have used Vertical Viewpager in my project.

But there are some issues,

  1. when the page has lots of onclick() events, the scrolling is too hard

  2. fling event doesn't change the page

  3. I tried to use gesture detector, but it changed pages too fast without transitions (without invoking transformPage())

  4. when I scroll the page, Sometimes onclick() events also get triggered

So I decided to use Recyclerview as Viewpager with the help of PagerSnapHelper.

It works fine. But the problem is ,

how to do the transition or animation when the item is changed (like I did in ViewPager)

For example, Zoomout transition or stack transition in Viewpager.

I tried stackLayoutManager but it takes more time to scroll and tried this related link .It doesn't work.

I researched for both issues How to reduce scroll speed for viewpager and how to do animations in recyclerview. I didn't get solution for it.

Can anyone help me!!! is it possible or I need to use any other widgets.

Edit

I tried #ADM's suggestion, it works fine but doesn't support PagerSnapHelper.

I have changed stacklayout manager in the above link but it doesn't support scrollToPosition() and PagerSnapHelper.

Code:

public class StackLayoutManager  extends LinearLayoutManager {      private static final String TAG = "StackLayoutManager";      //the space unit for the stacked item     private int mSpace = 0;     /**      * the offset unit,deciding current position(the sum of  {@link #mItemWidth} and {@link #mSpace})      */     private int mUnit;     //item width     private int mItemWidth;     private int mItemHeight;     //the counting variable ,record the total offset including parallex     private int mTotalOffset;     //record the total offset without parallex     private int mRealOffset;     private ObjectAnimator animator;     private int animateValue;     private RecyclerView.Recycler recycler;     private int lastAnimateValue;     private int initialOffset;     private boolean initial;     private int mMinVelocityX;     private VelocityTracker mVelocityTracker = VelocityTracker.obtain();     private int pointerId;     private Method sSetScrollState;     RecyclerView recyclerView;       public StackLayoutManager(Context context) {         super(context, VERTICAL, false);         setAutoMeasureEnabled(true);      }      @Override     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {         this.recycler = recycler;         detachAndScrapAttachedViews(recycler);         //got the mUnit basing on the first child,of course we assume that  all the item has the same size         View anchorView = recycler.getViewForPosition(0);         measureChildWithMargins(anchorView, View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);         mItemWidth = anchorView.getMeasuredWidth();         mItemHeight = getDecoratedMeasuredHeight(anchorView);          mUnit = mItemHeight;         //because this method will be called twice         initialOffset = mUnit;         mMinVelocityX = ViewConfiguration.get(anchorView.getContext()).getScaledMinimumFlingVelocity();         fill(recycler, 0);      }       @Override     public void onLayoutCompleted(RecyclerView.State state) {         super.onLayoutCompleted(state);         if (!initial) {             fill(recycler, initialOffset, false);             initial = true;         }     }      @Override     public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {         initial = false;         mTotalOffset = mRealOffset = 0;     }       private int fill(RecyclerView.Recycler recycler, int dy, boolean apply) {         return fillFromTop(recycler, dy);     }      public int fill(RecyclerView.Recycler recycler, int dy) {         return fill(recycler, dy, true);     }      private int fillFromTop(RecyclerView.Recycler recycler, int dy) {         if (mTotalOffset + dy < 0 || (mTotalOffset + dy + 0f) / mUnit > getItemCount() - 1)             return 0;         detachAndScrapAttachedViews(recycler);         mTotalOffset += dy;         int count = getChildCount();         for (int i = 0; i < count; i++) {             View child = getChildAt(i);             if (recycleVertically(child, dy))                 removeAndRecycleView(child, recycler);         }         int curPos = mTotalOffset / mUnit;         int leavingSpace = getHeight() - (left(curPos) + mUnit);         int itemCountAfterBaseItem = leavingSpace / mUnit + 2;         int e = curPos + itemCountAfterBaseItem;          int start = curPos - 1 >= 0 ? curPos - 1 : 0;         int end = e >= getItemCount() ? getItemCount() - 1 : e;          int left = getWidth() / 2 - mItemWidth / 2;         //layout views         for (int i = start; i <= end; i++) {             View view = recycler.getViewForPosition(i);              float alpha = alpha(i);              addView(view);             measureChildWithMargins(view, 0, 0);             int top =  (left(i) /* - ( 1 - scale ) * view.getMeasuredHeight() */ );             int right = view.getMeasuredWidth() + left;             int bottom = view.getMeasuredHeight() + top;             layoutDecoratedWithMargins(view, left, top, right, bottom);             view.setAlpha(alpha);             view.setScaleY(1);             view.setScaleX(1);         }          return dy;     }       private View.OnTouchListener mTouchListener = new View.OnTouchListener() {         @Override         public boolean onTouch(View v, MotionEvent event) {              mVelocityTracker.addMovement(event);             if (event.getAction() == MotionEvent.ACTION_DOWN) {                 if (animator != null && animator.isRunning())                     animator.cancel();                 pointerId = event.getPointerId(0);              }             if (event.getAction() == MotionEvent.ACTION_UP) {                 if (v.isPressed())                     v.performClick();                 mVelocityTracker.computeCurrentVelocity(1000, 14000);                 float xVelocity = mVelocityTracker.getYVelocity(pointerId);                 int o = mTotalOffset % mUnit;                 int scrollX;                 if (Math.abs(xVelocity) < mMinVelocityX && o != 0) {                     if (o >= mUnit / 2)                         scrollX = mUnit - o;                     else                         scrollX = -o;                     Log.d("scrollx","from scroll");                      Log.d("scrollx", "" + scrollX);                     brewAndStartAnimator(300, (int) (scrollX));                 }             }             return false;         }      };      private RecyclerView.OnFlingListener mOnFlingListener = new RecyclerView.OnFlingListener() {         @Override         public boolean onFling(int velocityX, int velocityY) {             int o = mTotalOffset % mUnit;             int s = mUnit - o;             int scrollX;             int vel = absMax(velocityX, velocityY);             if (vel  > 0) {                 scrollX = s;             } else                 scrollX = -o;             Log.d("scrollx","from fling");             Log.d("scrollx",""+scrollX);             brewAndStartAnimator(100, scrollX);           //  setScrollStateIdle();             return true;         }     };      private int absMax(int a, int b) {         if (Math.abs(a) > Math.abs(b))             return a;         else return b;     }      @Override     public void onAttachedToWindow(RecyclerView view) {         super.onAttachedToWindow(view);         this.recyclerView=view;         view.setOnTouchListener(mTouchListener);         view.setOnFlingListener(mOnFlingListener);     }       private void brewAndStartAnimator(int dur, int finalX) {         animator = ObjectAnimator.ofInt(StackLayoutManager.this, "animateValue", 0, finalX);         animator.setDuration(dur);         animator.start();         animator.addListener(new AnimatorListenerAdapter() {             @Override             public void onAnimationEnd(Animator animation) {                 lastAnimateValue = 0;                 recyclerView.scrollToPosition(findFirstCompletelyVisibleItemPosition());              }              @Override             public void onAnimationCancel(Animator animation) {                 lastAnimateValue = 0;                 recyclerView.smoothScrollToPosition(findFirstCompletelyVisibleItemPosition());             }         });      }      private float alpha(int position) {         float alpha;         int curPos = mTotalOffset / mUnit;         float n = (mTotalOffset + .0f) / mUnit;         if (position > curPos)             alpha = 1.0f;         else {             alpha = 1 - (n - position) / 1;         }         return alpha <= 0.001f ? 0 : alpha;     }      @Override     public void scrollToPosition(int pos)     {           scrollToPositionWithOffset(pos, 0);     }      private int left(int position) {           int curPos = mTotalOffset / mUnit;         int tail = mTotalOffset % mUnit;         float n = (mTotalOffset + .0f) / mUnit;         float x = n - curPos;          return ltr(position, curPos, tail, x);      }      private int ltr(int position, int curPos, int tail, float x) {         int left;         if (position <= curPos) {             if (position == curPos) {                 left = (int) (mSpace * (1 - x));             } else {                 left = (int) (mSpace * (1 - x - (curPos - position)));              }         } else {             if (position == curPos + 1)                 left = mSpace + mUnit - tail;             else {                  int baseStart = (int) (mUnit +  mUnit);                 left = (int) (baseStart + (position - curPos - 2) * mUnit - (position - curPos - 2) * (mUnit));                 if (BuildConfig.DEBUG)                     Log.i(TAG, "ltr: curPos " + curPos + "  pos:" + position + "  left:" + left + "   baseStart" + baseStart + " curPos+1:" + left(curPos + 1));             }             left = left <= 0 ? 0 : left;         }         return left;     }       @SuppressWarnings("unused")     public void setAnimateValue(int animateValue) {         this.animateValue = animateValue;         int dy = this.animateValue - lastAnimateValue;         fill(recycler, dy, false);         lastAnimateValue = animateValue;     }      @SuppressWarnings("unused")     public int getAnimateValue() {         return animateValue;     }       private boolean recycleVertically(View view, int dy) {         return view != null && (view.getTop() - dy < 0 || view.getBottom() - dy > getHeight());     }       @Override     public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {         return fill(recycler, dy);     }      @Override     public boolean canScrollHorizontally() {         return false;     }      @Override     public boolean canScrollVertically() {         return true;     }      @Override     public RecyclerView.LayoutParams generateDefaultLayoutParams() {         return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT);     }      @SuppressWarnings("unused")     public interface CallBack {          float scale(int totalOffset, int position);          float alpha(int totalOffset, int position);          float left(int totalOffset, int position);     } } 

And If it is not possible with recyclerview, Please help me what to do with that VerticalViewPager. I have tried setting the field values, But it doesn't work.

    try {         Class cls = this.getClass().getSuperclass();         Field distanceField = cls.getDeclaredField("mFlingDistance");         distanceField.setAccessible(true);         distanceField.setInt(this, distanceField.getInt(this)/2);     }catch (Exception ignored) {         Log.d("error", ignored.toString());     }       try {         Class cls = this.getClass().getSuperclass();         Field minVelocityField = cls.getDeclaredField("mMinimumVelocity");         minVelocityField.setAccessible(true);         minVelocityField.setInt(this, minVelocityField.getInt(this) /2);     } catch (Exception ignored) {         Log.d("error", ignored.toString());     }       try {         Class cls = this.getClass().getSuperclass();         Field maxVelocityField = cls.getDeclaredField("mMaximumVelocity");         maxVelocityField.setAccessible(true);         maxVelocityField.setInt(this, maxVelocityField.getInt(this)/2);     }catch (Exception ignored) {         Log.d("error", ignored.toString());     } 

1 Answers

Answers 1

Try this

import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import java.util.ArrayList; import android.support.animation.DynamicAnimation; import android.support.animation.FlingAnimation;   public class MainActivity extends AppCompatActivity {       CustomVerticalViewPager viewPager;     ArrayList<DataModel> arrayList = new ArrayList<>();      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          initArray();          viewPager = findViewById(R.id.viewPager);          viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {         @Override         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {             FlingAnimation fling = new FlingAnimation(viewPager, DynamicAnimation.SCROLL_X);         }          @Override         public void onPageSelected(int position) {          }          @Override         public void onPageScrollStateChanged(int state) {          }     });           CustomAdapter adapter = new CustomAdapter(this, arrayList);         viewPager.setAdapter(adapter);     }      private void initArray() {         for (int i = 0; i < 10; i++) {             DataModel dataModel = new DataModel();             dataModel.setTitle("Title item No :- " + i);             dataModel.setContent(" Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras auctor blandit dignissim. Suspendisse id lorem nulla. Proin urna lacus, posuere sed lacus a, dapibus consectetur neque. Donec in metus sagittis, consequat tellus eget, consequat ex. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec dui nisi, scelerisque eu nunc id, aliquet sagittis leo. Nunc ac ornare diam. Vestibulum sed elit euismod, ornare metus a, convallis ipsum. Nulla aliquam mi enim, porttitor commodo lacus dictum cursus. Phasellus sed eros sagittis, feugiat sapien ac, accumsan odio. In posuere congue lorem, quis pharetra mi tincidunt et. Nam tincidunt erat eu dapibus faucibus.\n" +                     "\n");              arrayList.add(dataModel);         }      }   } 

LAYOUT

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical">      <com.example.nilesh.testapp.CustomVerticalViewPager         android:layout_width="match_parent"         android:id="@+id/viewPager"         android:layout_height="match_parent" />   </LinearLayout> 

CustomVerticalViewPager

import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View;  public class CustomVerticalViewPager extends ViewPager {      public CustomVerticalViewPager(Context context) {         super(context);         init();     }      public CustomVerticalViewPager(Context context, AttributeSet attrs) {         super(context, attrs);         init();     }      private void init() {         setPageTransformer(true, new VerticalPageTransformer());         setOverScrollMode(OVER_SCROLL_NEVER);     }      private class VerticalPageTransformer implements ViewPager.PageTransformer {          @Override         public void transformPage(View view, float position) {              if (position < -1) {                 view.setAlpha(0);              } else if (position <= 1) {                 view.setAlpha(1);                  view.setTranslationX(view.getWidth() * -position);                  float yPosition = position * view.getHeight();                 view.setTranslationY(yPosition);              } else {                 view.setAlpha(0);             }         }     }       private MotionEvent swapXY(MotionEvent ev) {         float width = getWidth();         float height = getHeight();          float newX = (ev.getY() / height) * width;         float newY = (ev.getX() / width) * height;          ev.setLocation(newX, newY);          return ev;     }      @Override     public boolean onInterceptTouchEvent(MotionEvent ev) {         boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));         swapXY(ev);         return intercepted;     }      @Override     public boolean onTouchEvent(MotionEvent ev) {         return super.onTouchEvent(swapXY(ev));     }  } 

CustomAdapter

import android.content.Context; import android.support.v4.view.PagerAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import java.util.ArrayList;  public class CustomAdapter extends PagerAdapter {     Context context;     ArrayList<DataModel> arrayList = new ArrayList<>();      LayoutInflater mLayoutInflater;      public CustomAdapter(Context context, ArrayList<DataModel> arrayList) {         this.context = context;         this.arrayList = arrayList;         mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);     }       @Override     public int getCount() {         return arrayList.size();     }      @Override     public boolean isViewFromObject(View view, Object object) {         return view == ((LinearLayout) object);     }      @Override     public Object instantiateItem(ViewGroup container, final int position) {         View itemView = mLayoutInflater.inflate(R.layout.custom_layout, container, false);          TextView tvTitle = itemView.findViewById(R.id.tvTitle);         TextView tvContent = itemView.findViewById(R.id.tvContent);          tvContent.setText(arrayList.get(position).getContent());         tvTitle.setText(arrayList.get(position).getTitle());           tvTitle.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 Toast.makeText(context, "clicked position :" + position + " " + arrayList.get(position).getTitle(), Toast.LENGTH_SHORT).show();             }         });           container.addView(itemView);          return itemView;     }      @Override     public void destroyItem(ViewGroup container, int position, Object object) {         container.removeView((LinearLayout) object);     } } 

DataModel

public class DataModel {     String title, content;      public String getTitle() {         return title;     }      public void setTitle(String title) {         this.title = title;     }      public String getContent() {         return content;     }      public void setContent(String content) {         this.content = content;     } } 

custom_layout

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical">       <TextView         android:id="@+id/tvTitle"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:padding="5dp"         android:text="Nilesh"         android:textColor="#000"         android:textSize="20sp"         android:textStyle="bold" />      <TextView         android:id="@+id/tvContent"         android:layout_width="match_parent"         android:layout_height="wrap_content" /> </LinearLayout> 

Hope this helps

EDIT

For fling effect use Move views using a fling animation

Fling-based animation uses a friction force that is proportional to an object's velocity. Use it to animate a property of an object and want to end the animation gradually. It has an initial momentum, which is mostly received from the gesture velocity, and gradually slows down. The animation comes to an end when the velocity of the animation is low enough that it makes no visible change on the device screen.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment