RecyclerView简单实现卡片叠加移除效果(模仿探探)

时间:2024-05-22 19:44:23

用的是(自定义)RecyclerView实现的仿探探的卡片滑动移除:

RecyclerView简单实现卡片叠加移除效果(模仿探探)


首先,布局:

item布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    android:layout_width="500dp"
    android:layout_height="700dp"
    android:orientation="vertical"
    >
    <com.facebook.drawee.view.SimpleDraweeView
        android:id="@+id/cards_sim"
        android:layout_width="500dp"
        android:layout_height="600dp"
        fresco:roundedCornerRadius="15dp"
        android:layout_gravity="center_horizontal"
        fresco:placeholderImage="@mipmap/ic_launcher"
        />
    <LinearLayout
        android:layout_width="500dp"
        android:layout_height="wrap_content"
        android:background="#fff"
        android:orientation="vertical"
        >
        <TextView
            android:id="@+id/cards_title"
            android:background="@drawable/movietext"   //这个movietext,是自定义xml样式布局,下面有
            android:layout_width="500dp"
            android:layout_height="wrap_content"
            android:textColor="#000"
            android:textSize="20dp"
            android:paddingLeft="30dp"
            android:paddingTop="20dp"
            android:paddingBottom="20dp"
            />
    </LinearLayout>
</LinearLayout>

上面那个注释说的:自定义xml样式布局:

res下,drawable下:创个文件:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <stroke android:width="1dp"  android:color="#333333" />
            <corners android:radius="15dp"/>
        </shape>
    </item>
</selector>


自定义布局:

<?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="match_parent"
    android:orientation="vertical"
    >
    <android.support.v7.widget.RecyclerView
        android:id="@+id/cardsmove_recyclerview"
        android:layout_gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="800dp">
    </android.support.v7.widget.RecyclerView>
    <Button
        android:id="@+id/btn_splace_cards"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="20dp"
        android:background="#0f0"
        android:text="换一批"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

把自定义的布局放入Activity中的View里:

<com.example.jingdong.Find.CardsView.CardsMove   //这是自定义View的类名,加入到这里,直接能用,下边会有此类
    android:id="@+id/cardsmove_show"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</com.example.jingdong.Find.CardsView.CardsMove>

控制每个item的变量,边距等等常量类:

package com.example.jingdong.Find.CardsView;

/**
 * Created by Administrator on 2017/12/13 0013.
 */

public class CardConfig {
    /**
     * 显示可见的卡片数量
     */
    public static final int DEFAULT_SHOW_ITEM = 3;
    /**
     * 默认缩放的比例
     */
    public static final float DEFAULT_SCALE = 0.1f;
    /**
     * 卡片Y轴偏移量时按照14等分计算
     */
    public static final int DEFAULT_TRANSLATE_Y = 14;
    /**
     * 卡片滑动时默认倾斜的角度
     */
    public static final float DEFAULT_ROTATE_DEGREE = 15f;
    /**
     * 卡片滑动时不偏左也不偏右
     */
    public static final int SWIPING_NONE = 1;
    /**
     * 卡片向左滑动时
     */
    public static final int SWIPING_LEFT = 1 << 2;
    /**
     * 卡片向右滑动时
     */
    public static final int SWIPING_RIGHT = 1 << 3;
    /**
     * 卡片从左边滑出
     */
    public static final int SWIPED_LEFT = 1;
    /**
     * 卡片从右边滑出
     */
    public static final int SWIPED_RIGHT = 1 << 2;
}

监视每个item滑动前,滑动后,下一个item的行动,要执行的一些操作,的一个过程类

package com.example.jingdong.Find.CardsView;

import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.View;

import java.util.List;

/**
 * Created by Administrator on 2017/12/13 0013.
 */

public class CardItemTouchHelperCallback<T> extends ItemTouchHelper.Callback{
    private final RecyclerView.Adapter adapter;
    private List<T> dataList;
    private OnSwipeListener<T> mListener;

    public CardItemTouchHelperCallback(@NonNull RecyclerView.Adapter adapter, @NonNull List<T> dataList) {
        this.adapter = checkIsNull(adapter);
        this.dataList = checkIsNull(dataList);
    }

    public CardItemTouchHelperCallback(@NonNull RecyclerView.Adapter adapter, @NonNull List<T> dataList, OnSwipeListener<T> listener) {
        this.adapter = checkIsNull(adapter);
        this.dataList = checkIsNull(dataList);
        this.mListener = listener;
    }

    private <T> T checkIsNull(T t) {
        if (t == null) {
            throw new NullPointerException();
        }
        return t;
    }

    public void setOnSwipedListener(OnSwipeListener<T> mListener) {
        this.mListener = mListener;
    }


    /**
     * 设置滑动类型标记
     *
     * @param recyclerView
     * @param viewHolder
     * @return
     *          返回一个整数类型的标识,用于判断Item那种移动行为是允许的
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = 0;
        int swipeFlags = 0;
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof CardLayoutMannager) {
            swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        }
        return makeMovementFlags(dragFlags, swipeFlags);
    }
    /**
     * 拖拽切换Item的回调
     *
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     *          如果Item切换了位置,返回true;反之,返回false
     */

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

    /**
     *
     * 划出时会执行
     * @param viewHolder
     * @param direction:左侧划出为:4,右侧划出为:8
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        Log.d("mylog", "onSwiped: " + direction);
        // 移除 onTouchListener,否则触摸滑动会乱了
        viewHolder.itemView.setOnTouchListener(null);
        int layoutPosition = viewHolder.getLayoutPosition();
        T remove = dataList.remove(layoutPosition);
        adapter.notifyDataSetChanged();
        if (mListener != null ) {
            mListener.onSwiped(viewHolder, remove, direction == ItemTouchHelper.LEFT ? CardConfig.SWIPED_LEFT : CardConfig.SWIPED_RIGHT);
        }
        // 当没有数据时回调 mListener
        if (adapter.getItemCount() == 0) {
            if (mListener != null) {
                mListener.onSwipedClear();
            }
        }
    }
    /**
     * Item是否支持滑动
     *
     * @return
     *          true  支持滑动操作
     *          false 不支持滑动操作
     */

    @Override
    public boolean isItemViewSwipeEnabled() {
        return false;
    }

    /**
     * 拖动时会执行的方法
     * @param c
     * @param recyclerView
     * @param viewHolder
     * @param dX
     * @param dY
     * @param actionState
     * @param isCurrentlyActive
     */
    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                            float dX, float dY, int actionState, boolean isCurrentlyActive) {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
        // Log.d("mylog", "onChildDraw: 拖动");
        View itemView = viewHolder.itemView;
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            float ratio = dX / getThreshold(recyclerView, viewHolder);
            // ratio 最大为 1  -1
            if (ratio > 1) {
                ratio = 1;
            } else if (ratio < -1) {
                ratio = -1;
            }
            Log.d("mylog", "onChildDraw: " + ratio);
            itemView.setRotation(ratio * CardConfig.DEFAULT_ROTATE_DEGREE);
            int childCount = recyclerView.getChildCount();
            // 当数据源个数大于最大显示数时
            if (childCount > CardConfig.DEFAULT_SHOW_ITEM) {
                for (int position = 1; position < childCount - 1; position++) {
                    int index = childCount - position - 1;
                    View view = recyclerView.getChildAt(position);
                    view.setScaleX(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);
                    view.setScaleY(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);

                   /* view.setScaleX(1 - index * CardConfig.DEFAULT_SCALE );
                    view.setScaleY(1 - index * CardConfig.DEFAULT_SCALE);*/
                    view.setTranslationY((index - Math.abs(ratio)) * itemView.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
                }
            } else {
                // 当数据源个数小于或等于最大显示数时
                for (int position = 0; position < childCount - 1; position++) {
                    int index = childCount - position - 1;
                    View view = recyclerView.getChildAt(position);
                    view.setScaleX(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);
                    view.setScaleY(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);
                    view.setTranslationY((index - Math.abs(ratio)) * itemView.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
                }
            }
            if (mListener != null) {
                if (ratio != 0) {
                    //  Log.d("mylog", "onChildDraw: 不为零");
                    mListener.onSwiping(viewHolder, ratio, ratio < 0 ? CardConfig.SWIPING_LEFT : CardConfig.SWIPING_RIGHT);
                } else {
                    //  Log.d("mylog", "onChildDraw: 为零");
                    mListener.onSwiping(viewHolder, ratio, CardConfig.SWIPING_NONE);
                }
            }
        }
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        viewHolder.itemView.setRotation(0f);
    }

    private float getThreshold(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        return recyclerView.getWidth() * getSwipeThreshold(viewHolder);
    }
}

这里是需要自己做一个布局管理者,不能用系统给的布局管理者,item是需要层叠加:

package com.example.jingdong.Find.CardsView;

import android.support.annotation.NonNull;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by Administrator on 2017/12/13 0013.
 */

public class CardLayoutMannager extends RecyclerView.LayoutManager{
    private RecyclerView mRecyclerView;
    private ItemTouchHelper mItemTouchHelper;

    public CardLayoutMannager(@NonNull RecyclerView recyclerView, @NonNull ItemTouchHelper itemTouchHelper) {
        this.mRecyclerView = checkIsNull(recyclerView);
        this.mItemTouchHelper = checkIsNull(itemTouchHelper);
    }

    private <T> T checkIsNull(T t) {
        if (t ==null ) {
            throw new NullPointerException();
        }
        return t;
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    @Override
    public void onLayoutChildren(final RecyclerView.Recycler recycler, RecyclerView.State state) {
        detachAndScrapAttachedViews(recycler);
        int itemCount = getItemCount();
        // 当数据源个数大于最大显示数时
        if (itemCount > CardConfig.DEFAULT_SHOW_ITEM) {
            for (int position = CardConfig.DEFAULT_SHOW_ITEM; position >= 0; position--) {
                final View view = recycler.getViewForPosition(position);
                addView(view);
                measureChildWithMargins(view, 0, 0);
                int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
                int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);
                // recyclerview 布局
                layoutDecoratedWithMargins(view, widthSpace / 2, heightSpace / 2,
                        widthSpace / 2 + getDecoratedMeasuredWidth(view),
                        heightSpace / 2 + getDecoratedMeasuredHeight(view));

                if (position == CardConfig.DEFAULT_SHOW_ITEM) {
                    view.setScaleX(1 - (position - 1) * CardConfig.DEFAULT_SCALE);
                    view.setScaleY(1 - (position - 1) * CardConfig.DEFAULT_SCALE);
                    view.setTranslationY((position - 1) * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
                } else if (position > 0) {
                    view.setScaleX(1 - position * CardConfig.DEFAULT_SCALE);
                    view.setScaleY(1 - position * CardConfig.DEFAULT_SCALE);
                    view.setTranslationY(position * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
                } else {
                    view.setOnTouchListener(mOnTouchListener);
                }
            }
        } else {
            // 当数据源个数小于或等于最大显示数时
            for (int position = itemCount - 1; position >= 0; position--) {
                final View view = recycler.getViewForPosition(position);
                addView(view);
                measureChildWithMargins(view, 0, 0);
                int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
                int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);
                // recyclerview 布局
                layoutDecoratedWithMargins(view, widthSpace / 2, heightSpace / 2,
                        widthSpace / 2 + getDecoratedMeasuredWidth(view),
                        heightSpace / 2 + getDecoratedMeasuredHeight(view));

                if (position > 0) {
                    view.setScaleX(1 - position * CardConfig.DEFAULT_SCALE);
                    view.setScaleY(1 - position * CardConfig.DEFAULT_SCALE);
                    view.setTranslationY(position * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
                } else {
                    view.setOnTouchListener(mOnTouchListener);
                }
            }
        }
    }

    private View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(v);
            if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
                mItemTouchHelper.startSwipe(childViewHolder);
            }
            return false;
        }
    };
}

和上边的一个回调类一起用的,配合使用的一个接口,回调到请求数据的页面,也就是配置RecyclerView的界面:

package com.example.jingdong.Find.CardsView;

import android.support.v7.widget.RecyclerView;

/**
 * Created by Administrator on 2017/12/13 0013.
 */

public interface OnSwipeListener<T> {
    /**
     * 卡片还在滑动时回调
     *
     * @param viewHolder 该滑动卡片的viewHolder
     * @param ratio      滑动进度的比例
     * @param direction  卡片滑动的方向,CardConfig.SWIPING_LEFT 为向左滑,CardConfig.SWIPING_RIGHT 为向右滑,
     *                   CardConfig.SWIPING_NONE 为不偏左也不偏右
     */
    void onSwiping(RecyclerView.ViewHolder viewHolder, float ratio, int direction);

    /**
     * 卡片完全滑出时回调
     *
     * @param viewHolder 该滑出卡片的viewHolder
     * @param t          该滑出卡片的数据
     * @param direction  卡片滑出的方向,CardConfig.SWIPED_LEFT 为左边滑出;CardConfig.SWIPED_RIGHT 为右边滑出
     */
    void onSwiped(RecyclerView.ViewHolder viewHolder, T t, int direction);

    /**
     * 所有的卡片全部滑出时回调
     */
    void onSwipedClear();
}

在这里:数据的Bean类就不放了,下面的网络请求路径给出了,在代码里,自己请求一下,看下数据类型,自己打个Bean类,如果请求路径无效了,自己换个,在下面有的地方需要加入泛型,泛型类型是Bean类,自己复制的时候注意,改一改泛型,不然会没数据


自定义View主页:

package com.example.jingdong.Find.CardsView;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

import com.example.jingdong.R;
import com.example.jingdong.RecyclerView.Precenter.RecyclerViewTuijianPrecenter;
import com.example.jingdong.RecyclerView.View.RecyclerViewIView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by Administrator on 2017/12/13 0013.
 */

public class CardsMove extends LinearLayout implements RecyclerViewIView{
	//网络请求路径,下面有给出的随机数方法,最后拼接到路径后面,自己注意一下.
    String path = "http://api.svipmovie.com/front/columns/getVideoList.do?catalogId=402834815584e463015584e539330016&pnum=";
    RecyclerView rv;
    Button btn;
    RecyclerViewAdpt adpt;
    List<CardsBean.RetBean.ListBean> list;
    RecyclerViewTuijianPrecenter rp;
    Context context;
    public CardsMove(Context context) {
        this(context,null);
    }

    public CardsMove(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CardsMove(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        InitView(context, attrs, defStyleAttr);
    }

    private void InitView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this.context = context;
        View v = View.inflate(context, R.layout.cardsmove,this);
        rv = v.findViewById(R.id.cardsmove_recyclerview);
        btn = v.findViewById(R.id.btn_splace_cards);
        btn.setOnClickListener(new OnClickListener() {   //换一换按钮点击事件
            @Override
            public void onClick(View view) {
                rp.gett(path+getNextPage(),CardsBean.class);
            }
        });

        rp = new RecyclerViewTuijianPrecenter();   //MVP的P层
        rp.attchView(this);  //绑定P层接口

        rv.setItemAnimator(new DefaultItemAnimator());   //动画效果
        list = new ArrayList<>();
        adpt = new RecyclerViewAdpt(context,list);   //适配器(自己敲就行)
        rv.setAdapter(adpt);
	//监视item的行动的回调类
        CardItemTouchHelperCallback cc = new CardItemTouchHelperCallback(rv.getAdapter(), list);
        cc.setOnSwipedListener(new OnSwipeListener<CardsBean.RetBean.ListBean>() {   //尖括号中的泛型放
	的和适配器的数据类型一样,注意,注意,注意,放入之后,在重写下边的方法
            @Override
            public void onSwiping(RecyclerView.ViewHolder viewHolder, float ratio, int direction) {
                RecyclerViewAdpt.ViewHold vh = (RecyclerViewAdpt.ViewHold) viewHolder;
                vh.itemView.setAlpha(1 - Math.abs(ratio) * 0.2f);
            }

            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, CardsBean.RetBean.ListBean listBean, int direction) {
                RecyclerViewAdpt.ViewHold vh = (RecyclerViewAdpt.ViewHold) viewHolder;
                vh.itemView.setAlpha(1f);
            }

            @Override
            public void onSwipedClear() {
                rv.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        rp.gett(path+getNextPage(),CardsBean.class);
                    }
                },3000L);
            }
        });

        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(cc);   //这个我也不懂
        CardLayoutMannager cardLayoutMannager = new CardLayoutMannager(rv, itemTouchHelper);   //自己定义的管理者
        rv.setLayoutManager(cardLayoutMannager);
        itemTouchHelper.attachToRecyclerView(rv);   //这个我也不懂,自己试一试,看用不用加

        rp.gett(path+getNextPage(),CardsBean.class);
    }

    @Override
    public void onChengo(Object object) {   //请求网络成功回调方法,刷新适配器
        Log.e("CardsMove","卡片请求网络成功");
        CardsBean cb = (CardsBean) object;
        List<CardsBean.RetBean.ListBean> t = cb.getRet().getList();
        if(t!=null||t.size()!=0){
            if(list!=null||list.size()!=0){
                list.clear();
            }
            list.addAll(t);
            adpt.notifyDataSetChanged();
        }

    }

    @Override
    public void onShba(Exception exception) {
        Log.e("CardsMove","卡片请求网络失败");
    }

    private int getNextPage() {   //随机数方法
        int page = getRandomNumber(1, 108);
        return page;
    }
    public static int getRandomNumber(int min, int max) {
        return new Random().nextInt(max) % (max - min + 1) + min;
    }

}