一、Android-SwipeToDismiss简介
(一)github地址
https://github.com/romannurik/Android-SwipeToDismiss
(二)效果
解释一下github提供的demo,左边的是一列button,右边的是一个listview,当点击button或者listview的item时就会toast出“xxx被点击”之类的信息;但左滑或者右滑button或者listview的item时,该button或者item就会以动画的形式消失。
二、代码分析
(一)项目目录
可以见到,只有三个文件,非常简单,SwipeDismissListViewTouchListener.java是实现右边listView滑动使item消失的功能;
SwipeDismissTouchListener.java是实现左边button滑动消失的功能;
(二)代码详解
(1)SwipeDismissTouchListener.java
public SwipeDismissTouchListener(View view, Object token, DismissCallbacks callbacks) {
ViewConfiguration vc = ViewConfiguration.get(view.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = view.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
mView = view;
mToken = token;
mCallbacks = callbacks;
}
一个类我们从构造器看起,在构造器中进行一系列的初始化:
mslop是判断是否产生了move的最小距离;
mMinFlingVelocity和 mMaxFlingVelocity分别是判断是否发生了fling(手指离开屏幕但是继续惯性滑动)的上下限;
mView是SwipeDismissTouchListener监听的主体,可以看到在MAinActivity中,button是作为参数被传进来后赋值给mView;
mCallback是实现了内部接口DismissCallbacks的实例,代码如下:
public interface DismissCallbacks {
/**
* Called to determine whether the view can be dismissed.
*/
boolean canDismiss(Object token);
/**
* Called when the user has indicated they she would like to dismiss the view.
*
* @param view The originating {@link View} to be dismissed.
* @param token The optional token passed to this object's constructor.
*/
void onDismiss(View view, Object token);
}
它的作用就是在成功去除button后进行通知用户。后面能看到具体的运用。
既然SwipeDismissTouchListener implements View.OnTouchListener,必然要重写
public boolean onTouch(View view, MotionEvent motionEvent)
motionEvent.offsetLocation(mTranslationX, 0);//因为手指在
接下来会进入一个switch-case结构,至于motionevent.getAction()与motionEvent.getActionMask()的区别可以从链接中找到答案:
http://my.oschina.net/banxi/blog/56421
——>首先看ACTION_DOWN:
在这里面先获得点击的坐标,然后进行一次if判断,这里的mCallbacks.canDismiss(mToken)的返回值能在MainActivity中找到答案:
new SwipeDismissListViewTouchListener.DismissCallbacks() {
@Override
public boolean canDismiss(int position) {
return true;
}
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
mAdapter.remove(mAdapter.getItem(position));
}
mAdapter.notifyDataSetChanged();
}
可以看到总是返回true,所以就进入if体内;接下的
mVelocityTracker.addMovement(motionEvent);
作用是告诉mVelocityTracker去跟进这个motionEvent,相当于一个登记的操作。
—–>接着我们看ACTION_MOVE: 注意看注释
case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;//找出x方向上每次的移动距离
float deltaY = motionEvent.getRawY() - mDownY;//找出y方向上每次的移动距离
if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
//如果x方向上的距离能被判断为移动且x方向上的移动远大于y方向上的移动,就认定为水平方向上的滑动
mSwiping = true;//正在发生滑动
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
mView.getParent().requestDisallowInterceptTouchEvent(true);
//请求父容器不要拦截事件
// Cancel listview's touch
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
(motionEvent.getActionIndex() <<
MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mView.onTouchEvent(cancelEvent);//触碰mVeiw时,除了SwipeDismissTouchListener的onTouch方法会被调用,mView本身的onTouchEvent也会被调用,所以要防止onTouchEvent操作的干扰,便发送一个cancelEvent事件过去
cancelEvent.recycle();
}
if (mSwiping) {
mTranslationX = deltaX;
mView.setTranslationX(deltaX - mSwipingSlop);
// TODO: use an ease-out interpolator or such
mView.setAlpha(Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
注意的是,其中的这段代码:
if (mSwiping) {
mTranslationX = deltaX;
mView.setTranslationX(deltaX - mSwipingSlop);
// TODO: use an ease-out interpolator or such
mView.setAlpha(Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
实现的动画是发生在手指还在屏幕上拖动时,所以mView会跟着持续移动,并且逐渐透明。
—–>ACTION_UP:
当手指抬起之时,可能会发生以下的情况:
第一种:Math.abs(deltaX) > mViewWidth / 2 && mSwiping:
判断mView是否已经移动距离超过自身宽度的一半且此时正在滑动,如果为真,认为用户打算dimiss,则dimiss=true;
第二种:mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX
&& absVelocityY < absVelocityX && mSwiping:
判断水平方向上的速度是否在fling的范围中且x轴方向速度要快于y轴方向且此时正在滑动,如果为真, 也可认为用户打算dimiss,则dismiss = (velocityX < 0) == (deltaX < 0); // dismiss only if flinging in the same direction as dragging
第三种:前两种情况都为false,则发生这种情况就是cancel的情况:
if (mSwiping) {
// cancel
mView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
这时候就mView复位;
如果前两种情况为真,则
if (dismiss) {
// dismiss
mView.animate()
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
performDismiss();
}
});
}
则继续启动后续的动画,完成dimiss操作,并在动画完成后调用了 performDismiss();
那么,这个 performDismiss();是干什么的呢?
private void performDismiss() {
// Animate the dismissed view to zero-height and then fire the dismiss callback.
// This triggers layout on each animation frame; in the future we may want to do something
// smarter and more performant.
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
final int originalHeight = mView.getHeight();
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCallbacks.onDismiss(mView, mToken);
// Reset view presentation
mView.setAlpha(1f);
mView.setTranslationX(0);
lp.height = originalHeight;
mView.setLayoutParams(lp);
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
mView.setLayoutParams(lp);
}
});
animator.start();
}
}
明显,他是在dimiss的动画完成后,后面的item会向上挪一个位置(除了最后一个),这个方法就是实现这个动画的。
(2)SwipeDismissListViewTouchListener
SwipeDismissListViewTouchListener的实现大体相同,大家可以对照着来学习