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

compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {minSdkVersion 15targetSdkVersion 25versionCode 1versionName "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{@Overridepublic 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;}@Overridepublic 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> {@Overridepublic 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;}@Overridepublic boolean swipeEnd(int section, float distance) {return distance > mThreshold;}@Overridepublic boolean swipeStart(int section, float distance) {return false;}@Overridepublic boolean swipeContinue(int section, float distanceX, float distanceY) {return false;}@Overridepublic 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() {@Overridepublic 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设置触摸事件监听
@Overridepublic 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);}
  @Overridepublic boolean onTouch(View v, MotionEvent event) {dragGestureDetector.onTouchEvent(event);return true;}

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

@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {cardView.setLayoutParams((LayoutParams) animation.getAnimatedValue());}});otherViewAnim.setDuration(250);collectionAnim.add(otherViewAnim);}animatorSet.playTogether(collectionAnim);animatorSet.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);//移除滑出去的viewmIndex++;initView();}});animatorSet.start();}}else{if (canSwipe) {final CardView topView =  getTopCardView();ValueAnimator rotationAnim = ValueAnimator.ofFloat(mRotation, 0f);rotationAnim.setDuration(250);rotationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic 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() {@Overridepublic void onAnimationUpdate(ValueAnimator value) {v.setLayoutParams((LayoutParams)value.getAnimatedValue());}});layoutAnim.start();}}}return true;}@Overridepublic 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。最后来看下实现的动画效果

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

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

  1. android仿微信的activity平滑水平切换动画,Android实现简单底部导航栏 Android仿微信滑动切换效果...

    Android实现简单底部导航栏 Android仿微信滑动切换效果 发布时间:2020-10-09 19:48:00 来源:脚本之家 阅读:96 作者:丶白泽 Android仿微信滑动切换最终实现效果 ...

  2. vue实现卡片式上下滑动_基于Vue.js仿制探探卡片左右滑动特效

    说明 > 最近一直在捣鼓Nuxt.js项目,项目中有个需求是实现类似探探左右滑动切换功能.要求能实现手指拖拽切换.点击按钮进行切换.拖拽回弹等功能. 如上图:最终展示效果 emmm~~ 是不是感 ...

  3. php框架加滑动条,IOS_iOS实现双向滑动条效果,最近做项目,碰到一种双向滑 - phpStudy...

    iOS实现双向滑动条效果 最近做项目,碰到一种双向滑动条,自己实现了一下,随便写一下思路,方便以后开发,避免重复写代码,以后粘贴就行了.封装了一下,代码如下: #import typedef NSSt ...

  4. ios pan手势滑动消失动画_iOS仿抖音—评论视图滑动消失

    iOS仿抖音短视频 前言 这是仿抖音短视频的第三篇-评论视图滑动消失,先来看下效果图: comment.gif 说明 通过观察可以发现抖音的评论视图是从底部弹出的,包括顶部视图和UITableView ...

  5. Android开发学习之基于ViewPager实现Gallery画廊效果

    通过我们前面的学习,我们知道ViewPager是可以做出近乎完美的滑动体验,回顾整个Android,我们发现Gallery具备同样的特点,于是我们大胆地猜想,Gallery是否和ViewPager之间 ...

  6. android 用代码模拟滑动,Android开发之使用150行代码实现滑动返回效果

    今天带大家实现滑动返回效果.,具体内容如下所示: 先看看效果图: 因为没有具体内容,也没有简书的图片资源,所以稍微简陋了点. 但是依然不妨碍我们的效果展示~ OK,接下来惯例,通过阅读本文你能学习到: ...

  7. Android仿今日头条图片滑动退出效果

    资源下载(2C币) 逛CSDN的时候,看到几篇写仿今日头条图片滑动退出效果的文章,闲着无聊便想着也给自己项目加上,实现的思路有很多种,本着就近原则选了一篇与自己思路相近的文章结合自己的实践总结一下. ...

  8. android模仿ios滚动,模仿iOS版微信的滑动View效果

    前言 最近经常交替使用Android和iOS手机.对于两个系统,从我们常用的列表来看,Android一般的列表菜单是通过长按出来的,而iOS是通过滑动出现的.比如我们常用的微信,对于Android版本 ...

  9. android微信列表滑动删除,Android仿微信对话列表滑动删除效果

    微信对话列表滑动删除效果很不错的,借鉴了github上SwipeListView(项目地址:https://github.com/likebamboo/SwipeListView),在其上进行了一些重 ...

最新文章

  1. 夏日php登录系统源码,夏日PHP企业管理系统 v0.1
  2. python模拟网页点击_python怎么模拟点击网页按钮
  3. 清除webbrowser cookie/session的6种方法
  4. java画个半径为1地圆_java - 绘制一个半径为圆的圆并围绕边缘指向 - 堆栈内存溢出...
  5. 轻松搞定 Nginx 配置代码的神器!
  6. 基于深度学习的驾驶行为预测方法
  7. android path拆分_Android架构进阶之路:Android 组件化方案探索与思考总结
  8. Mysql 5 replication(mysql主从双机策略)
  9. 新手用手机学黑客编程一秒变黑客
  10. 周志华《机器学习》书每章思维导图总结
  11. Syzmlw 蜗居大结局f
  12. android设置主题背景为壁纸_Android 应用背景加载系统动态壁纸
  13. 电影外观调色效果Lr预设
  14. dnw驱动更新,支持全系统(xp,win7,win8/win10)
  15. 学校教务管理系统(第二弹
  16. html在ie浏览器中中文为什么是乱码
  17. 关于计算机的想象作文550字,想象作文550字:未来的一天
  18. altium 交叉线_怎样设置原理图中电气连接线交叉点的属性?
  19. excel服务器okr系统,OKR工具能帮企业落地OKR吗?从飞书OKR看专业工具的价值
  20. python exception in thread_这个是什么原因,请问怎么处理Exception in thr

热门文章

  1. Streamlit(五) widgets-button
  2. R语言时间序列分析之ARIMA模型预测
  3. Arduino + Lcd1602 显示当前环境温度
  4. 2023 年软件文档工具,这5款可以看看!
  5. 【BI学习心得15-数据分析思维】
  6. PyQt5入门学习(一)【PyQt5及PyQt5-tools的安装】
  7. php外边距的代码,外边距简写属性 margin
  8. PPT中图片(形状)叠加时的透明效果
  9. 快递扫地机器人被损坏_熬夜秒到的扫地机器人丢了 快递公司最多赔几十元
  10. 【解决方案】笔记本电脑蓝牙耳机连接不稳定