学习GestureDetectorCompat,实现卡片左右滑动消失效果

转载请标明出处:
http://blog.csdn.net/iamzgx/article/details/53239874
本文出自:【iGoach的博客
这几天,android studio2.2.2和android7.0来袭,于是就更新下了哦。配置如下

compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
    minSdkVersion 15
    targetSdkVersion 25
    versionCode 1
    versionName "1.0"
}
dependencies {
       classpath 'com.android.tools.build:gradle:2.2.2'
   }

结果包名报错

The SDK platform-tools version (24.0.4) is too old to check APIs compiled with API 25; please update

解决办法就是

这里写图片描述

使用SDK Manager把Android SDK Platform-Tools 24.x.x升级到Android SDK Platform-Tools 25.x.x。然后安装,最后重新Restart下Android Studio就好了。

以上是题外之外,接下来进入这篇博客的正题。

第一次看到这种效果是在探探那里看到的,里面美女多多呀。其他一些社交类应用首页推荐好友的时候也有遇到这种效果,所以就来看看他是怎么实现的。通过网上搜索,发现网上也有很多实现的方案,其中我知道的就是通过GestureDetectorCompat实现,或者通过ViewDragHelper实现。以下,就来说说通过GestureDetectorCompat是怎么实现的。

GestureDetectorCompat类

其实GestureDetectorCompat这个类,很早之前android就提供了。这个类是为了减轻开发者在onTouchEvent处理android处理触摸和手势事件的复杂度,它提供了两个接口,OnGestureListener和OnDoubleTapListener。还有一个静态内部类SimpleOnGestureListener,实际SimpleOnGestureListener也是实现前两个接口实现的。

其中OnGestureListener有以下几个方法回调

  • onDown 手指按下触摸屏的那个瞬间回调
  • onShowPress 手指按下滑动而不是长按的时候回调
  • onSingleTapUp 手指按下迅速松开的那个瞬间回调
  • onScroll 手指在触摸屏滑动的时候回调
  • onLongPress 手指长按触摸屏的时候回调
  • onFling 手指迅速移动并松开的时候回调

而OnDoubleTapListener有以下几个方法回调

  • onSingleTapConfirmed 手指单击的时候回调
  • onDoubleTap 手指双击的时候回调
  • onDoubleTapEvent 手指双击之后触发的按下滑动松开等操作事件的回调

简单概括

上面简单介绍了GestureDetectorCompat这个类,这里实现卡片左右滑动消失效果只需要重写SimpleOnGestureListener的onScroll和onSingleTapUp两个方法。知道这些之后,那就开始动手写吧!从哪里开始呢?当然是先布局咯。怎么布局?先来看下探探的效果

这里写图片描述

忽略美女再看会发现,这个布局是一层层叠加起来的,然后每个卡片类似于RecyclerView的一个item。好了,想到这里,那我们就把父布局定位为RelativeLayout,它具有叠加效果,姑且命名为CardStack。然后每个item的父View用CardView,然后通过margin来初始化每个item的间距来控制位置。当顶部CardView滑动的时候,通过GestureDetectorCompat监听它滑动的变化值,通过变化值来改变每个item的margin,从而产生左右滑动的效果,当手指松开的时候,通过属性动画完成消失或者恢复原位的动画。

大概思路就是这样,通过这个思路,先来准备几个帮助类。

DragGestureDetector帮助类

它主要是通过实现GestureDetector.SimpleOnGestureListener来监听滑动的值。既然是监听,那么就要设计一个对外的接口DragListener。它主要回调的方法有

public interface DragListener{
      boolean onDragStart(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
      boolean onDragContinue(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
      boolean onDragEnd(MotionEvent e1, MotionEvent e2);
      boolean onTapCap();
  }
  • onDragStart 开始滑动卡片的回调
  • onDragContinue 滑动过程的回调
  • onDragEnd 手指松开的时候回调
  • onTapCap 手指按下没有滑动然后松开的回调,也就是onSingleTapUp ,比如
    点击一下非常快的(不滑动)Touchup:OnGestureListener回调为onDown->onSingleTapUp->onSingleTapConfirmed
    点击一下稍微慢点的(不滑 动)Touchup:OnGestureListener回调为onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed

外部通过传递DragGestureDetector帮助类的构造器参数传入DragListener接口,同时传入Context,供GestureDetectorCompat初始化使用,构造器如下

public DragGestureDetector(Context context,DragListener mListener){
       mGestureDetectorCompat = new GestureDetectorCompat(context,new DragGestureListener());
       this.mListener = mListener ;
   }

其中,GestureDetectorCompat的初始化,需要传入Context参数和一个实现OnGestureListener接口的参数,这里使用 继承GestureDetector.SimpleOnGestureListener静态内部类的DragGestureListener。而DragGestureListener需要重写onScroll和onSingleTapUp两个方法。在这两个方法里面调用DragListener的不同方法,代码如下

private class DragGestureListener extends GestureDetector.SimpleOnGestureListener{
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if(mListener==null)
                return true ;
            if(!mStarted){
                mListener.onDragStart(e1,e2,distanceX,distanceY);
                mStarted = true;
            }else{
                mListener.onDragContinue(e1,e2,distanceX,distanceY);
            }
            mOriginalEvent = e1 ;
            return true;
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return mListener.onTapCap();
        }
    }

上面主要是通过mStarted这个标识来判断是否刚开始滑动,同时把事件和滑动距离传递出去。以及在onSingleTapUp里面回调onTapCap。而另外的onDragEnd调用,我们实现一个方法onTouchEvent,供外部传递MotionEvent事件,然后处理onDragEnd的调用,代码如下

public void onTouchEvent(MotionEvent event){
        mGestureDetectorCompat.onTouchEvent(event);
        int action = MotionEventCompat.getActionMasked(event);
        switch (action){
            case MotionEvent.ACTION_DOWN:
                mOriginalEvent = event;
                break;
            case MotionEvent.ACTION_UP:
                if(mStarted) {
                    mListener.onDragEnd(mOriginalEvent, event);
                }
                mStarted = false;
                break;
        }
    }

通过调用mGestureDetectorCompat的onTouchEvent方法,就可以把MotionEvent 事件指给GestureDetectorCompat管理了。至于onDragEnd的调用,也就是当手指松开的时候,所以在ACTION_UP里面处理,同时把mStarted 标示复位。

CardStackUtils帮助类
这个帮助类,主要是提供一些静态方法,以备后用
clone一个RelativeLayout.LayoutParams的方法

  public static RelativeLayout.LayoutParams cloneParams(RelativeLayout.LayoutParams layoutParams){
        RelativeLayout.LayoutParams cloneParams = new RelativeLayout.LayoutParams(layoutParams.width,layoutParams.height);
        cloneParams.leftMargin = layoutParams.leftMargin ;
        cloneParams.topMargin = layoutParams.topMargin ;
        cloneParams.rightMargin = layoutParams.rightMargin;
        cloneParams.bottomMargin = layoutParams.bottomMargin;
        int[] rules = layoutParams.getRules();
        for (int i = 0; i < rules.length; i++) {
            cloneParams.addRule(i,rules[i]);
        }
        return cloneParams;
    }

计算滑动的距离方法

public static float distance(float x1,float y1,float x2,float y2){
        return (float) Math.sqrt((x2-x1)*(x2-x1)-(y2-y1)*(y2-y1));
    }

判断滑动方向的方法

  private static final int LEFT_TOP_DIRECTION = 0 ;
  private static final int LEFT_BOTTOM_DIRECTION = 1 ;
  private static final int RIGHT_TOP_DIRECTION = 2 ;
  private static final int RIGHT_BOTTOM_DIRECTION = 3;
  public static int direction(float x1,float y1,float x2,float y2){
        if(x1>x2){
            if(y1>y2){
                return LEFT_TOP_DIRECTION;
            }
            return LEFT_BOTTOM_DIRECTION;
        }
        if(y1>y2){
            return RIGHT_TOP_DIRECTION;
        }
        return RIGHT_BOTTOM_DIRECTION;
    }

计算左右滑动消失动画滑动结束的位置方法

  public static RelativeLayout.LayoutParams getMoveParams(CardView cardView
            ,int leftMargin,int topMargin){
        RelativeLayout.LayoutParams originParams = (RelativeLayout.LayoutParams) cardView.getLayoutParams();
        RelativeLayout.LayoutParams targetLayoutParams = cloneParams(originParams);
        targetLayoutParams.leftMargin += leftMargin ;
        targetLayoutParams.rightMargin -= leftMargin ;
        targetLayoutParams.topMargin += topMargin;
        targetLayoutParams.bottomMargin -= topMargin;
        return targetLayoutParams;
    }

另外两个方法,一个dp转换为px和取值范围限制方法

public static int dpToPx(Context context, float dpValue){
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue*scale+0.5f);
    }
    public static int cling(int min,int max,int value){
        return Math.min(max,Math.max(min,value));
    }

RelativeLayoutParamsEvaluator类
通过 实现TypeEvaluator自定义左右滑动消失的属性动画。主要是改变margin值,代码如下

public class RelativeLayoutParamsEvaluator implements TypeEvaluator<RelativeLayout.LayoutParams> {
    @Override
    public RelativeLayout.LayoutParams evaluate(float fraction, RelativeLayout.LayoutParams startValue
            , RelativeLayout.LayoutParams endValue) {
        RelativeLayout.LayoutParams currentParams = CardStackUtils.cloneParams(startValue);
        currentParams.leftMargin += (endValue.leftMargin-startValue.leftMargin)*fraction;
        currentParams.topMargin += (endValue.topMargin-startValue.topMargin)*fraction;
        currentParams.rightMargin += (endValue.rightMargin-startValue.rightMargin)*fraction;
        currentParams.bottomMargin += (endValue.bottomMargin-startValue.bottomMargin)*fraction;
        return currentParams;
    }
}

CardStack对外提供接口CardEventListener

   public interface CardEventListener{
        boolean swipeStart(int direction,float distance);
        boolean swipeContinue(int direction,float distanceX,float distanceY);
        boolean swipeEnd(int direction,float distance);
        void topCardTapped();
    }

以及默认实现这个接口的DefaultStackEventListener

public class DefaultStackEventListener implements CardStack.CardEventListener {

    private float mThreshold;

    public DefaultStackEventListener(int i) {
        mThreshold = i;
    }

    @Override
    public boolean swipeEnd(int section, float distance) {
        return distance > mThreshold;
    }

    @Override
    public boolean swipeStart(int section, float distance) {
        return false;
    }

    @Override
    public boolean swipeContinue(int section, float distanceX, float distanceY) {
        return false;
    }

    @Override
    public void topCardTapped() {

    }
}

其中mThreshold控制滑动消失和恢复原位移动距离的界限,通过调用swipeEnd来判断。

CardStack的实现
首先定义CardStack的属性

<declare-styleable name="CardStack">
        <attr name="stackMargin" format="dimension"/>
        <attr name="backgroundColor" format="color"/>
        <attr name="showNum" format="integer"/>
        <attr name="cardView_radius" format="dimension"/>
    </declare-styleable>

这里我总结的有上面几个属性配置。开发中,有需要,可以再加一些属性。这里暂时就这几个,通过定义stackMargin配置底部每个item之间的间距,通过backgroundColor属性配置每个item的背景颜色,然后通过showNum配置默认需要显示多少个item显示,最后通过cardView_radius配置CardView的圆角角度。

定义好属性之后,接下来就是定义CardStack的一些局部变量和常量

//默认背景颜色
private static final int DEFAULT_BACKGROUND_COLOR = Color.WHITE;
//默认显示item的个数
private static final int DEFAULT_SHOW_NUM = 4 ;
//默认CardView的圆角角度,单位dp
private static final int DEFAULT_CARD_VIEW_RADIUS = 5;
//默认底部的间距
private static final int DEFAULT_MARGIN_BOTTOM_DP = 10;
//item背景色的局部变量
private int mBackgroundColor = DEFAULT_BACKGROUND_COLOR ;
//显示item个数的局部变量
private int mShowNum = DEFAULT_SHOW_NUM ;
//CardView圆角角度变量
private int mCardViewRadius = DEFAULT_CARD_VIEW_RADIUS ;
//底部margin变量
private int mStackMargin = DEFAULT_MARGIN_BOTTOM_DP ;
//List保存显示的item的父布局CardView
private List<CardView> viewCollection;
//保存各个CardView的LayoutParams
private HashMap<View,LayoutParams> mLayoutsMap;
//绑定的adapter
private BaseAdapter mBaseAdapter;
//控制手势帮助类
private DragGestureDetector dragGestureDetector;
//对外监听事件的接口,默认通过DefaultStackEventListener实现
private CardEventListener cardEventListener = new DefaultStackEventListener(300);
//滑动效果动画的RelativeLayout.LayoutParams
private RelativeLayout.LayoutParams[] endAnimLayouts = new RelativeLayout.LayoutParams[4];
//滑动效果动画的滑动距离
private static final int ANIM_DISTANCE = 4000;
//是否可以滑动
private boolean canSwipe = true;
//滑动的时候旋转角度
private float mRotation;
//移除后开始的标志
private int mIndex ; 

定义好局部变量后,然后再初始化局部变量

  • 查找CardStack定义的那些属性
 private void initStyle(AttributeSet attrs){
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs,R.styleable.CardStack);
        mBackgroundColor = typedArray.getColor(R.styleable.CardStack_backgroundColor,
                DEFAULT_BACKGROUND_COLOR);
        mShowNum = typedArray.getInteger(R.styleable.CardStack_showNum,DEFAULT_SHOW_NUM);
        mCardViewRadius = typedArray.getDimensionPixelSize(R.styleable.CardStack_showNum,
                CardStackUtils.dpToPx(getContext(),DEFAULT_CARD_VIEW_RADIUS));
        mStackMargin = typedArray.getDimensionPixelSize(R.styleable.CardStack_stackMargin,
                CardStackUtils.dpToPx(getContext(),DEFAULT_MARGIN_BOTTOM_DP));
        typedArray.recycle();
    }
  • 初始化几个变量
 private void initData(){
   viewCollection = new ArrayList<>();
   mLayoutsMap = new HashMap<>();
   dragGestureDetector = new DragGestureDetector(getContext(),this);
    }

这几个变量主要是viewCollection ,保存显示item的父布局CardView的集合;mLayoutsMap ,保存显示item父布局的LayoutParams的结合;dragGestureDetector ,初始化DragGestureDetector帮助类。

  • 添加CardStack的子View实现叠加效果,以及添加viewCollection 集合数据
private void initView(){
      removeAllViews();
      viewCollection.clear();
      for (int i = 0; i < mShowNum; i++) {
          CardView cardView = createDefaultCardView();
          viewCollection.add(cardView);
          addView(cardView,new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));
      }
      if(mBaseAdapter!=null)
      bindData();
    }
 private CardView createDefaultCardView(){
        CardView cardView = new CardView(getContext());
        cardView.setCardBackgroundColor(mBackgroundColor);
        cardView.setRadius(mCardViewRadius);
        return cardView;
    }
  • 初始化左右滑动消失动画结束位置
private void initEndAnimParams(){
        CardView topView = getTopCardView();
        endAnimLayouts[0] = CardStackUtils.getMoveParams(topView, -ANIM_DISTANCE, -ANIM_DISTANCE);
        endAnimLayouts[1] = CardStackUtils.getMoveParams(topView, -ANIM_DISTANCE, ANIM_DISTANCE);
        endAnimLayouts[2] = CardStackUtils.getMoveParams(topView, ANIM_DISTANCE, -ANIM_DISTANCE);
        endAnimLayouts[3] = CardStackUtils.getMoveParams(topView, ANIM_DISTANCE, ANIM_DISTANCE);

    }

这里移动位移为ANIM_DISTANCE(4000),以达到移除屏幕外的效果

初始化这些变量之后,接下来就是实现adapter绑定数据的方法,主要是传入BaseAdapter,代码如下

  public void setAdapter(BaseAdapter baseAdapter){
        if(baseAdapter==null)
            throw new IllegalArgumentException("传入Adapter不能为null");
        if(this.mBaseAdapter!=null)
            this.mBaseAdapter.unregisterDataSetObserver(mDb);
        this.mBaseAdapter = baseAdapter ;
        mBaseAdapter.registerDataSetObserver(mDb);
        bindData();
    }
private DataSetObserver mDb = new DataSetObserver() {
        @Override
        public void onChanged() {
            super.onChanged();
            //这里是notifyDataSetChanged更新数据用的
            initView();
        }
    };

上面使用了DataSetObserver,这个方法的主要原理就是广播机制,通过registerDataSetObserver进行广播注册,这样notifyDataSetChanged就可以实现更新数据的效果。

然后通过的BaseAdapter获取数据,对显示的CardView添加子View,同时绑定数据,其中子View通过BaseAdapter的getView方法获取,BaseAdapter的getCount比默认显示CardView小的话,就把CardView暂时隐藏,否则就显示绑定数据。代码如下

  private void bindData(){
        mLayoutsMap.clear();
        int rootViewSize = viewCollection.size();
        for(int i = rootViewSize-1 ; i>=0 ;i--){
            CardView parent = viewCollection.get(i);
            int position = mIndex+rootViewSize - i - 1;
            if(position>mBaseAdapter.getCount()-1){
                parent.setVisibility(View.GONE);
            }else{
                parent.setVisibility(View.VISIBLE);
                View childView = mBaseAdapter.getView(position,null,parent);
                parent.addView(childView);
                if(i!=0)
                    position+=1;
                initLayout(parent,((position-mIndex)*mStackMargin));
            }
        }
        CardView topCardView = getTopCardView();
        topCardView.setOnTouchListener(this);
    }

添加CardView的子View之后,接下来就是通过initLayout方法初始化各个CardView的位置和间距了。上面的计算margin的方法里面,其中最后一张和倒数第二张margin是一样的,其他是上一张的mStackMargin倍。主要代码如下

 private void initLayout(CardView childView,int pixel){
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) childView.getLayoutParams();
        layoutParams.leftMargin += pixel  ;
        layoutParams.topMargin += pixel ;
        layoutParams.rightMargin += pixel;
        childView.setLayoutParams(layoutParams);
        mLayoutsMap.put(childView,CardStackUtils.cloneParams(layoutParams));
    }

同时在上面通过mLayoutsMap保存每个CardView的初始化LayoutParms

  • 处理每个item的位置之后,接下来就是通过setOnTouchListener为顶部item设置触摸事件监听
@Override
    public boolean onTouch(View v, MotionEvent event) {
        dragGestureDetector.onTouchEvent(event);
        return true;
    }

在上面bindData方法里面,我们通过getTopCardView获取到顶部CardView,然后设置了TouchEvent事件,在DragGestureDetector帮助类里面需要传递TouchEvent事件,所以我们把事件传递进去,代码如下

  public CardView getTopCardView(){
        if(viewCollection.isEmpty())
            return null;
        return viewCollection.get(viewCollection.size() - 1);
    }
  @Override
    public boolean onTouch(View v, MotionEvent event) {
        dragGestureDetector.onTouchEvent(event);
        return true;
    }

然后重写DragListener的每个方法,在DragListener不同方法里面处理不同的滑动值和效果

@Override
    public boolean onDragStart(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if(canSwipe){
            startDrag(e1,e2);
        }
        float x1 = e1.getRawX();
        float y1 = e1.getRawY();
        float x2 = e2.getRawX();
        float y2 = e2.getRawY();
        final int direction = CardStackUtils.direction(x1, y1, x2, y2);
        float distance = CardStackUtils.distance(x1, y1, x2, y2);
        cardEventListener.swipeStart(direction, distance);
        return true;
    }

    @Override
    public boolean onDragContinue(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (canSwipe) {
            startDrag(e1, e2);
        }
        float x1 = e1.getRawX();
        float y1 = e1.getRawY();
        float x2 = e2.getRawX();
        float y2 = e2.getRawY();
        final int direction = CardStackUtils.direction(x1,y1,x2,y2);
        cardEventListener.swipeContinue(direction, Math.abs(x2-x1), Math.abs(y2-y1));
        return true;
    }

    @Override
    public boolean onDragEnd(MotionEvent e1, MotionEvent e2) {
        float x1 = e1.getRawX();
        float y1 = e1.getRawY();
        float x2 = e2.getRawX();
        float y2 = e2.getRawY();
        float distance = CardStackUtils.distance(x1,y1,x2,y2);
        int direction = CardStackUtils.direction(x1,y1,x2,y2);
        boolean isSwipe = cardEventListener.swipeEnd(direction,distance);
        if(isSwipe){
            if(canSwipe){
                AnimatorSet animatorSet = new AnimatorSet();
                ArrayList<Animator> collectionAnim = new ArrayList<>();
                final CardView topCardView = getTopCardView();
                RelativeLayout.LayoutParams topLayoutParams = (LayoutParams) topCardView.getLayoutParams();
                RelativeLayout.LayoutParams currentLayoutParams = CardStackUtils.cloneParams(topLayoutParams);
                ValueAnimator topCardViewAnim = ValueAnimator.ofObject(new RelativeLayoutParamsEvaluator()
                        ,currentLayoutParams, endAnimLayouts[direction]);
                topCardViewAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        topCardView.setLayoutParams((LayoutParams) animation.getAnimatedValue());
                    }
                });
                topCardViewAnim.setDuration(250);
                collectionAnim.add(topCardViewAnim);
                int viewSize = viewCollection.size();
                for(int i = 0 ;i <viewSize ;i++){
                    final CardView cardView = viewCollection.get(i);
                    if(cardView==topCardView)
                        continue;
                    RelativeLayout.LayoutParams startLayoutParams = CardStackUtils.cloneParams((LayoutParams)cardView.getLayoutParams());
                    CardView nextCardView = viewCollection.get(i+1);
                    RelativeLayout.LayoutParams endLayoutParams = mLayoutsMap.get(nextCardView);
                    if(endLayoutParams==null)
                        continue;
                    ValueAnimator otherViewAnim = ValueAnimator.ofObject(new RelativeLayoutParamsEvaluator()
                            ,startLayoutParams, endLayoutParams);
                    otherViewAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            cardView.setLayoutParams((LayoutParams) animation.getAnimatedValue());
                        }
                    });
                    otherViewAnim.setDuration(250);
                    collectionAnim.add(otherViewAnim);
                }
                animatorSet.playTogether(collectionAnim);
                animatorSet.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        //移除滑出去的view
                        mIndex++;
                        initView();
                    }
                });
                animatorSet.start();
            }
        }else{
            if (canSwipe) {
                final CardView topView =  getTopCardView();
                ValueAnimator rotationAnim = ValueAnimator.ofFloat(mRotation, 0f);
                rotationAnim.setDuration(250);
                rotationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator v) {
                        topView.setRotation(((Float) (v.getAnimatedValue())).floatValue());
                    }
                });
                rotationAnim.start();

                for(final View v : viewCollection){
                    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
                    RelativeLayout.LayoutParams endLayout = CardStackUtils.cloneParams(layoutParams);
                    RelativeLayout.LayoutParams endLayoutParams = mLayoutsMap.get(v);
                    if(endLayoutParams==null)
                        continue;
                    ValueAnimator layoutAnim = ValueAnimator.ofObject(new RelativeLayoutParamsEvaluator(),
                            endLayout,endLayoutParams);
                    layoutAnim.setDuration(250);
                    layoutAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator value) {
                            v.setLayoutParams((LayoutParams)value.getAnimatedValue());
                        }
                    });
                    layoutAnim.start();
                }
            }
        }
        return true;
    }

    @Override
    public boolean onTapCap() {
        cardEventListener.topCardTapped();
        return true;
    }

在上面代码中,在onDragStart和onDragContinue方法里面,分别调用了startDrag这个方法。然后在onDragEnd方法里面,处理了左右滑动消失的动画和恢复原位的动画,其中滑动消失动画和恢复原位使用前面定义的RelativeLayoutParamsEvaluator来实现,同理,每个CardView都会绑定一个动画,从而产生放大的效果。其中,startDrag主要处理是手指滑动的时候改变CardView的margin从而产生滑动的效果,代码如下

private void startDrag(MotionEvent e1,MotionEvent e2){
        float rotation_coefficient = 50f;
        CardView topCardView = getTopCardView();
        if(topCardView==null)
            return;
        int diffX = (int) (e2.getRawX()-e1.getRawX());
        int diffY = (int)(e2.getRawY()-e1.getRawY());
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) topCardView.getLayoutParams();
        RelativeLayout.LayoutParams topViewLayouts = mLayoutsMap.get(topCardView);
        layoutParams.leftMargin = topViewLayouts.leftMargin+diffX;
        layoutParams.rightMargin = topViewLayouts.rightMargin-diffX;
        layoutParams.topMargin = topViewLayouts.topMargin + diffY;
        layoutParams.bottomMargin = topViewLayouts.bottomMargin -diffY;
        mRotation = diffX/rotation_coefficient;
        topCardView.setRotation(mRotation);
        topCardView.setLayoutParams(layoutParams);
        for(CardView cardView:viewCollection){
            if(cardView==topCardView)
                continue;
            int index = viewCollection.indexOf(cardView);
            Log.d("zgx","index====="+index);
            if(index!=0)
                scaleFrom(cardView, -Math.abs(CardStackUtils.cling(-mStackMargin,mStackMargin,(int)(diffX*0.05))));
        }
    }
    public void scaleFrom(CardView fromCardView,int pixel){
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) fromCardView.getLayoutParams();
        RelativeLayout.LayoutParams cardViewLayouts = mLayoutsMap.get(fromCardView);
        if(cardViewLayouts==null)
            return;
        layoutParams.leftMargin = cardViewLayouts.leftMargin+pixel;
        layoutParams.rightMargin = cardViewLayouts.rightMargin+pixel;
        layoutParams.topMargin = cardViewLayouts.topMargin + pixel;
    }

首先是改变顶部CardView的旋转角度,然后就是改变顶部CardView的margin,接着就是改变其他CardView的margin。最后来看下实现的动画效果
这里写图片描述

以上就是实现的卡片左右滑动消失效果的主要讲解,详细可查看源码。
下载源码

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值