1. 程式人生 > >一個可以上下左右滑動且當前項居中的ViewPager

一個可以上下左右滑動且當前項居中的ViewPager

背景介紹

最近專案需求,做一個訂製的ViewPager,要求在能夠左右滑動的同時,ViewPager的item本身也是一個疊放的容器且可上下滑動實現業務要求,如上滑刪除、下滑加入收藏等,於是寫一篇部落格記錄一下控制元件的實現。

首先來看一下效果:

這裡寫圖片描述

基本思路

  • 外層為ViewPager,實現條目的左右滑動

  • ViewPager的子Item為RecyclerView,自定義該RecyclerView的LayoutManager及ItemTouchHelper.SimpleCallback實現控制元件疊放及上下滑動

實現過程

首先是佈局檔案:

<android.support
.constraint.ConstraintLayout 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:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false"
tools:context="com.mapleaf.centerviewpager.MainActivity"> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="wrap_content" android:layout_height="wrap_content" android:clipChildren="false" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> </android.support.constraint.ConstraintLayout>

記住viewpager及父控制元件都要新增android:clipChildren="false",這句程式碼的含義是子控制元件可以超過自己的位置顯示出來。

接下來初始化ViewPager:

    private void initViewPager() {
        mPagerAdapter = new CustomPagerAdapter<CustomRecyclerView<String>>();
        mViewPager.setAdapter(mPagerAdapter);
        mViewPager.setOffscreenPageLimit(2);
        ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams();
        layoutParams.width = ((Activity) mViewPager.getContext()).getWindowManager().getDefaultDisplay().getWidth() / 21 * 10;
        mTransformer = new ScaleTransformer();
        mViewPager.setPageTransformer(false, mTransformer);
    }

ScaleTransformer繼承自ViewPager.PageTransformer,它的作用是實現了ViewPager切換時的動畫效果,具體見程式碼。

然後來實現自定義的RecyclerView,其核心在於自定義LayoutManager實現層疊擺放;以及實現ItemTouchHelper.SimpleCallback來訂製上下滑動的操作。

自定義的LayoutManager程式碼如下:

public class SimilarItemLayoutManager extends RecyclerView.LayoutManager {

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() <= 0 || state.isPreLayout()) {
            return;
        }
        detachAndScrapAttachedViews(recycler);

        int visibleCount = 2;
        if (getItemCount() < visibleCount) {
            visibleCount = getItemCount();
        }

        for(int i=visibleCount;i>=1;i--){
            View view=recycler.getViewForPosition(i-1);
            addView(view);
            measureChildWithMargins(view, 0, 0);
            int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
            int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);
            layoutDecorated(view,
                    widthSpace / 2,
                    heightSpace / 2,
                    widthSpace / 2 + getDecoratedMeasuredWidth(view),
                    heightSpace / 2 + getDecoratedMeasuredHeight(view));
        }
    }
}

因為我們的RecyclerView是層疊擺放,因此只需要顯示上面的2個view即可,最終將view擺放在RecyclerView的中間。

最後就是實現ItemTouchHelper.SimpleCallback

public class ItemSwipeCallBack extends ItemTouchHelper.SimpleCallback {
    private CustomRecyclerViewAdapter mAdapter;
    private ViewPager mViewPager;

    public ItemSwipeCallBack(CustomRecyclerViewAdapter adapter, ViewPager viewPager) {
        super(0, ItemTouchHelper.UP | ItemTouchHelper.DOWN);
        mAdapter = adapter;
        mViewPager = viewPager;
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        if(direction== ItemTouchHelper.UP){
            //上滑將該頁插入最後
            mAdapter.moveItemToBottom(viewHolder.getAdapterPosition());
        }else if(direction== ItemTouchHelper.DOWN){
            viewHolder.itemView.animate()
                    .translationYBy(1000)
                    .scaleX(0)
                    .scaleY(0)
                    .rotation(720)
                    .setDuration(500)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            super.onAnimationEnd(animation);
                            mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1, true);
                        }
                    });
        }
    }

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        if (dY < 0) {
            viewHolder.itemView.setTranslationY(dY);
        } else if (dY < viewHolder.itemView.getHeight()) {
            viewHolder.itemView.setTranslationY(dY);
        }
    }
}

因為在我的控制元件裡只需要上下滑動,因此在建構函式中super(0, ItemTouchHelper.UP | ItemTouchHelper.DOWN);第一個引數0表示禁止拖動,第二個引數表示支援上下滑動。

onSwiped中可以監測到上下滑動,實現自己的邏輯即可,我在上滑時將第一個元素刪除並新增到最後,在下滑時做了動畫並移動ViewPager到下一個條目。

onChildDraw中可以自定義動畫,想幹嘛幹嘛(~ ̄▽ ̄)~

那麼,最後一步就是將上面兩個自定義的東東和我們的RecyclerView關聯起來即可:

        SimilarItemLayoutManager layoutManager = new SimilarItemLayoutManager();
        setLayoutManager(layoutManager);

        ItemSwipeCallBack callback=new ItemSwipeCallBack(adapter,viewPager);
        ItemTouchHelper helper=new ItemTouchHelper(callback);
        helper.attachToRecyclerView(this);