如何动画RecyclerView像垂直Viewpager转换

Jyo*_* JK 6 android android-viewpager android-recyclerview

Vertical Viewpager在我的项目中使用过.

但是有一些问题,

  1. 当页面有很多onclick()事件时,滚动太难了

  2. fling事件不会更改页面

  3. 我尝试使用手势检测器,但它没有转换就更快地更改了页面(没有调用transformPage())

  4. 当我滚动页面时,有时onclick()事件也会被触发

所以我决定use Recyclerview as Viewpager借助PagerSnapHelper.

它工作正常.但问题是,

如何更改项目时如何进行转换或动画(就像我在ViewPager中所做的那样)

例如,Viewpager中的Zoomout转换或堆栈转换.

我尝试了stackLayoutManager,但滚动并尝试此相关链接需要更多时间.它不起作用.

我研究了两个问题如何降低viewpager的滚动速度以及如何在recyclerview中进行动画制作.我没有得到它的解决方案.

谁能帮我!!!是否可能或我需要使用任何其他小部件.

编辑

我尝试了#ADM的建议,它工作正常,但不支持PagerSnapHelper.

我在上面的链接中更改了stacklayout manager,但它不支持scrollToPosition()和PagerSnapHelper.

码:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果使用recyclerview无法实现,请帮我解决使用VerticalViewPager的问题.我已经尝试设置字段值,但它不起作用.

    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());
    }
Run Code Online (Sandbox Code Playgroud)

Nil*_*hod 1

尝试这个

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);
        }

    }


}
Run Code Online (Sandbox Code Playgroud)

布局

<?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>
Run Code Online (Sandbox Code Playgroud)

自定义垂直视图分页器

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 {
    private GestureDetector gestureDetector;
    public boolean isScrollEvent;
    public CustomVerticalViewPager(Context context) {
        super(context);
        init();
    }

    public CustomVerticalViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        gestureDetector=new GestureDetector(getContext(),new GestureListener());
        setPageTransformer(true, new VerticalPageTransformer());
        setOverScrollMode(OVER_SCROLL_NEVER);
        try {
            Field mScroller;
            mScroller = ViewPager.class.getDeclaredField("mScroller");
            mScroller.setAccessible(true);
            FixedSpeedScroller scroller = new FixedSpeedScroller(getContext(), new DecelerateInterpolator());
            scroller.setScrollDuration(250);
            mScroller.set(this, scroller);
        } catch (Exception e) {}
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return gestureDetector.onTouchEvent((ev));

    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean res= gestureDetector.onTouchEvent((ev));
        if((ev.getAction() == MotionEvent.ACTION_CANCEL ||ev.getAction()==MotionEvent.ACTION_UP)) {
            if(isScrollEvent) {
                try {
                    endFakeDrag();
                } catch (Exception e) {}
            }
            return true;
        }
       // Log.d("event", "ontouch      " + res);
        return res;
    }

    private class FixedSpeedScroller extends Scroller {

        private int mDuration = 500;

        public FixedSpeedScroller(Context context) {
            super(context);
        }

        public FixedSpeedScroller(Context context, Interpolator interpolator) {
            super(context, interpolator);
        }

        public FixedSpeedScroller(Context context, Interpolator interpolator, boolean flywheel) {
            super(context, interpolator, flywheel);
        }

        public void setScrollDuration(int duration) {
            mDuration = duration;
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, mDuration);
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy) {
            super.startScroll(startX, startY, dx, dy, mDuration);
        }
    }

    private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            isScrollEvent=false;
          // Log.d("touch","singletap");
            return false;
        }
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        //   Log.d("touch","fling");
            isScrollEvent=false;
            try {
                float diffY = e2.getY() - e1.getY();
                if (diffY > 20) {
                    onSwipeDown();
                    return  false;
                } else if(diffY<-20){
                    onSwipeUp();
                    return  false;
                }
                else
                {
                    endFakeDrag();
                }


            } catch (Exception exception) {
                exception.printStackTrace();
            }
            return false;

        }
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {
        // Log.d("touch", "scroll" + " " + distanceX);
            beginFakeDrag();
            fakeDragBy(-distanceY);
            isScrollEvent=true;
            return true;

        }
    }
    public void onSwipeUp() {
        setCurrentItem(getCurrentItem() + 1);
    }
    public void onSwipeDown() {
        setCurrentItem(getCurrentItem() - 1);

    }
}
Run Code Online (Sandbox Code Playgroud)

定制适配器

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

数据模型

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

自定义布局

<?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>
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助

编辑

用于投掷效果Move views using a fling animation

基于 Fling 的动画使用与对象速度成正比的摩擦力。使用它来为对象的属性设置动画并希望逐渐结束动画。它有一个初始动量,主要来自手势速度,然后逐渐减慢。当动画的速度足够低以至于设备屏幕上没有明显变化时,动画就会结束。