转载请标明出处:
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。最后来看下实现的动画效果
以上就是实现的卡片左右滑动消失效果的主要讲解,详细可查看源码。
下载源码