转载(http://blog.csdn.net/xiaanming/article/details/18311877)
今天还是给大家带来自定义控件的编写,自定义一个ListView的左右滑动删除Item的效果,这个效果之前已经实现过了,有兴趣的可以看下Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果,之前使用的是滑动类Scroller来实现的,但是看了下通知栏的左右滑动删除效果,确实很棒,当我们滑动Item超过一半的时候,item的透明度就变成了0,我们就知道抬起手指的时候item就被删除了,当item的透明度不为0的时候,我们抬起手指Item会回到起始位置,这样我们就知道拖动到什么位置item会删除,什么位置Item不删除,用户体验更好了,还有一个效果,就是我们滑动删除了item的时候,ListView的其他item会出现向上或者向下滚动的效果,感觉效果很棒,所以在GitHub上面搜索了下,发现很多开源库都有这个效果,比如ListViewAnimations, android-swipelistview等等,我看了下实现原理,使用的是Jake Wharton的动画开源库NineOldAndroids,这个库究竟是干嘛的呢?在API3.0(Honeycomb), SDK新增了一个android.animation包,里面的类是实现动画效果相关的类,通过Honeycomb API,能够实现非常复杂的动画效果,但是如果开发者想在3.0以下使用这一套API, 则需要使用开源框架Nine Old Androids,在这个库中会根据我们运行的机器判断其SDK版本,如果是API3.0以上则使用Android自带的动画类,否则就使用Nine Old Androids库中,这是一个兼容库,接下来我们就来看看这个效果的具体实现吧
实现该效果的主要思路
先根据手指触摸的点来获取点击的是ListView的哪一个Item 当手指在屏幕上面滑动的时候,我们要使得Item跟随手指的滑动而滑动 当我们抬起手指的时候,我们根据滑动的距离或者手指在屏幕上面的速度来判断Item是滑出屏幕还是滑动至其实位置 Item滑出屏幕时,使ListView的其他item产生向上挤压或者向下挤压的效果 大致的思路这是这四步,其中的一些细节接下来我会一一为大家解答的,接下来我们就用代码来实现这种效果吧
首先我们新建一个工程,叫Swipedismisslistview,我们需要将Nine Old Androids这个库引入到工程,大家可以去https://github.com/JakeWharton/NineOldAndroids下载,可以使用Jar包,也可以使用工程库的形式引入到我们自己的工程,我们还需要自定义一个ListView,我们先看代码然后给大家讲解下具体的功能实现
**[java]** [view plain](http://blog.csdn.net/xiaanming/article/details/18311877#)[copy](http://blog.csdn.net/xiaanming/article/details/18311877#)[](https://code.csdn.net/snippets/159353)[](https://code.csdn.net/snippets/159353/fork) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.example.swipedismisslistview; - - <span class="keyword">import</span> <span class="keyword">static</span> com.nineoldandroids.view.ViewHelper.setAlpha; - <span class="keyword">import</span> <span class="keyword">static</span> com.nineoldandroids.view.ViewHelper.setTranslationX; - <span class="keyword">import</span> android.content.Context; - <span class="keyword">import</span> android.util.AttributeSet; - <span class="keyword">import</span> android.view.MotionEvent; - <span class="keyword">import</span> android.view.VelocityTracker; - <span class="keyword">import</span> android.view.View; - <span class="keyword">import</span> android.view.ViewConfiguration; - <span class="keyword">import</span> android.view.ViewGroup; - <span class="keyword">import</span> android.widget.AdapterView; - <span class="keyword">import</span> android.widget.ListView; - - <span class="keyword">import</span> com.nineoldandroids.animation.Animator; - <span class="keyword">import</span> com.nineoldandroids.animation.AnimatorListenerAdapter; - <span class="keyword">import</span> com.nineoldandroids.animation.ValueAnimator; - <span class="keyword">import</span> com.nineoldandroids.view.ViewHelper; - <span class="keyword">import</span> com.nineoldandroids.view.ViewPropertyAnimator; - <span class="comment">/**</span> - <span class="comment"> * @blog http://blog.csdn.net/xiaanming</span> - <span class="comment"> * </span> - <span class="comment"> * @author xiaanming</span> - <span class="comment"> *</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> SwipeDismissListView <span class="keyword">extends</span> ListView { - <span class="comment">/**</span> - <span class="comment"> * 认为是用户滑动的最小距离</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mSlop; - <span class="comment">/**</span> - <span class="comment"> * 滑动的最小速度</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mMinFlingVelocity; - <span class="comment">/**</span> - <span class="comment"> * 滑动的最大速度</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mMaxFlingVelocity; - <span class="comment">/**</span> - <span class="comment"> * 执行动画的时间</span> - <span class="comment"> */</span> - <span class="keyword">protected</span> <span class="keyword">long</span> mAnimationTime = <span class="number">150</span>; - <span class="comment">/**</span> - <span class="comment"> * 用来标记用户是否正在滑动中</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> mSwiping; - <span class="comment">/**</span> - <span class="comment"> * 滑动速度检测类</span> - <span class="comment"> */</span> - <span class="keyword">private</span> VelocityTracker mVelocityTracker; - <span class="comment">/**</span> - <span class="comment"> * 手指按下的position</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mDownPosition; - <span class="comment">/**</span> - <span class="comment"> * 按下的item对应的View</span> - <span class="comment"> */</span> - <span class="keyword">private</span> View mDownView; - <span class="keyword">private</span> <span class="keyword">float</span> mDownX; - <span class="keyword">private</span> <span class="keyword">float</span> mDownY; - <span class="comment">/**</span> - <span class="comment"> * item的宽度</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">int</span> mViewWidth; - <span class="comment">/**</span> - <span class="comment"> * 当ListView的Item滑出界面回调的接口</span> - <span class="comment"> */</span> - <span class="keyword">private</span> OnDismissCallback onDismissCallback; - - <span class="comment">/**</span> - <span class="comment"> * 设置动画时间</span> - <span class="comment"> * </span> - <span class="comment"> * @param mAnimationTime</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">void</span> setmAnimationTime(<span class="keyword">long</span> mAnimationTime) { - <span class="keyword">this</span>.mAnimationTime = mAnimationTime; - } - - <span class="comment">/**</span> - <span class="comment"> * 设置删除回调接口</span> - <span class="comment"> * </span> - <span class="comment"> * @param onDismissCallback</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">void</span> setOnDismissCallback(OnDismissCallback onDismissCallback) { - <span class="keyword">this</span>.onDismissCallback = onDismissCallback; - } - - <span class="keyword">public</span> SwipeDismissListView(Context context) { - <span class="keyword">this</span>(context, <span class="keyword">null</span>); - } - - <span class="keyword">public</span> SwipeDismissListView(Context context, AttributeSet attrs) { - <span class="keyword">this</span>(context, attrs, <span class="number"></span>); - } - - <span class="keyword">public</span> SwipeDismissListView(Context context, AttributeSet attrs, - <span class="keyword">int</span> defStyle) { - <span class="keyword">super</span>(context, attrs, defStyle); - - ViewConfiguration vc = ViewConfiguration.get(context); - mSlop = vc.getScaledTouchSlop(); - mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * <span class="number">8</span>; <span class="comment">//获取滑动的最小速度</span> - mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); <span class="comment">//获取滑动的最大速度</span> - } - - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">boolean</span> onTouchEvent(MotionEvent ev) { - <span class="keyword">switch</span> (ev.getAction()) { - <span class="keyword">case</span> MotionEvent.ACTION_DOWN: - handleActionDown(ev); - <span class="keyword">break</span>; - <span class="keyword">case</span> MotionEvent.ACTION_MOVE: - <span class="keyword">return</span> handleActionMove(ev); - <span class="keyword">case</span> MotionEvent.ACTION_UP: - handleActionUp(ev); - <span class="keyword">break</span>; - } - <span class="keyword">return</span> <span class="keyword">super</span>.onTouchEvent(ev); - } - - <span class="comment">/**</span> - <span class="comment"> * 按下事件处理</span> - <span class="comment"> * </span> - <span class="comment"> * @param ev</span> - <span class="comment"> * @return</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> handleActionDown(MotionEvent ev) { - mDownX = ev.getX(); - mDownY = ev.getY(); - - mDownPosition = pointToPosition((<span class="keyword">int</span>) mDownX, (<span class="keyword">int</span>) mDownY); - - <span class="keyword">if</span> (mDownPosition == AdapterView.INVALID_POSITION) { - <span class="keyword">return</span>; - } - - mDownView = getChildAt(mDownPosition – getFirstVisiblePosition()); - - <span class="keyword">if</span> (mDownView != <span class="keyword">null</span>) { - mViewWidth = mDownView.getWidth(); - } - - <span class="comment">//加入速度检测</span> - mVelocityTracker = VelocityTracker.obtain(); - mVelocityTracker.addMovement(ev); - } - - - <span class="comment">/**</span> - <span class="comment"> * 处理手指滑动的方法</span> - <span class="comment"> * </span> - <span class="comment"> * @param ev</span> - <span class="comment"> * @return</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> handleActionMove(MotionEvent ev) { - <span class="keyword">if</span> (mVelocityTracker == <span class="keyword">null</span> || mDownView == <span class="keyword">null</span>) { - <span class="keyword">return</span> <span class="keyword">super</span>.onTouchEvent(ev); - } - - <span class="comment">// 获取X方向滑动的距离</span> - <span class="keyword">float</span> deltaX = ev.getX() – mDownX; - <span class="keyword">float</span> deltaY = ev.getY() – mDownY; - - <span class="comment">// X方向滑动的距离大于mSlop并且Y方向滑动的距离小于mSlop,表示可以滑动</span> - <span class="keyword">if</span> (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop) { - mSwiping = <span class="keyword">true</span>; - - <span class="comment">//当手指滑动item,取消item的点击事件,不然我们滑动Item也伴随着item点击事件的发生</span> - MotionEvent cancelEvent = MotionEvent.obtain(ev); - cancelEvent.setAction(MotionEvent.ACTION_CANCEL | - (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT)); - onTouchEvent(cancelEvent); - } - - <span class="keyword">if</span> (mSwiping) { - <span class="comment">// 跟谁手指移动item</span> - ViewHelper.setTranslationX(mDownView, deltaX); - <span class="comment">// 透明度渐变</span> - ViewHelper.setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f – 2f * Math.abs(deltaX)/ mViewWidth))); - - <span class="comment">// 手指滑动的时候,返回true,表示SwipeDismissListView自己处理onTouchEvent,其他的就交给父类来处理</span> - <span class="keyword">return</span> <span class="keyword">true</span>; - } - - <span class="keyword">return</span> <span class="keyword">super</span>.onTouchEvent(ev); - - } - - <span class="comment">/**</span> - <span class="comment"> * 手指抬起的事件处理</span> - <span class="comment"> * @param ev</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> handleActionUp(MotionEvent ev) { - <span class="keyword">if</span> (mVelocityTracker == <span class="keyword">null</span> || mDownView == <span class="keyword">null</span>|| !mSwiping) { - <span class="keyword">return</span>; - } - - <span class="keyword">float</span> deltaX = ev.getX() – mDownX; - - <span class="comment">//通过滑动的距离计算出X,Y方向的速度</span> - mVelocityTracker.computeCurrentVelocity(<span class="number">1000</span>); - <span class="keyword">float</span> velocityX = Math.abs(mVelocityTracker.getXVelocity()); - <span class="keyword">float</span> velocityY = Math.abs(mVelocityTracker.getYVelocity()); - - <span class="keyword">boolean</span> dismiss = <span class="keyword">false</span>; <span class="comment">//item是否要滑出屏幕</span> - <span class="keyword">boolean</span> dismissRight = <span class="keyword">false</span>;<span class="comment">//是否往右边删除</span> - - <span class="comment">//当拖动item的距离大于item的一半,item滑出屏幕</span> - <span class="keyword">if</span> (Math.abs(deltaX) > mViewWidth / <span class="number">2</span>) { - dismiss = <span class="keyword">true</span>; - dismissRight = deltaX > <span class="number"></span>; - - <span class="comment">//手指在屏幕滑动的速度在某个范围内,也使得item滑出屏幕</span> - } <span class="keyword">else</span> <span class="keyword">if</span> (mMinFlingVelocity <= velocityX - && velocityX <= mMaxFlingVelocity && velocityY < velocityX) { - dismiss = <span class="keyword">true</span>; - dismissRight = mVelocityTracker.getXVelocity() > <span class="number"></span>; - } - - <span class="keyword">if</span> (dismiss) { - ViewPropertyAnimator.animate(mDownView) - .translationX(dismissRight ? mViewWidth : -mViewWidth)<span class="comment">//X轴方向的移动距离</span> - .alpha(<span class="number"></span>) - .setDuration(mAnimationTime) - .setListener(<span class="keyword">new</span> AnimatorListenerAdapter() { - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onAnimationEnd(Animator animation) { - <span class="comment">//Item滑出界面之后执行删除</span> - performDismiss(mDownView, mDownPosition); - } - }); - } <span class="keyword">else</span> { - <span class="comment">//将item滑动至开始位置</span> - ViewPropertyAnimator.animate(mDownView) - .translationX(<span class="number"></span>) - .alpha(<span class="number">1</span>) - .setDuration(mAnimationTime).setListener(<span class="keyword">null</span>); - } - - <span class="comment">//移除速度检测</span> - <span class="keyword">if</span>(mVelocityTracker != <span class="keyword">null</span>){ - mVelocityTracker.recycle(); - mVelocityTracker = <span class="keyword">null</span>; - } - - mSwiping = <span class="keyword">false</span>; - } - - - - <span class="comment">/**</span> - <span class="comment"> * 在此方法中执行item删除之后,其他的item向上或者向下滚动的动画,并且将position回调到方法onDismiss()中</span> - <span class="comment"> * @param dismissView</span> - <span class="comment"> * @param dismissPosition</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> performDismiss(<span class="keyword">final</span> View dismissView, <span class="keyword">final</span> <span class="keyword">int</span> dismissPosition) { - <span class="keyword">final</span> ViewGroup.LayoutParams lp = dismissView.getLayoutParams();<span class="comment">//获取item的布局参数</span> - <span class="keyword">final</span> <span class="keyword">int</span> originalHeight = dismissView.getHeight();<span class="comment">//item的高度</span> - - ValueAnimator animator = ValueAnimator.ofInt(originalHeight, <span class="number"></span>).setDuration(mAnimationTime); - animator.start(); - - animator.addListener(<span class="keyword">new</span> AnimatorListenerAdapter() { - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onAnimationEnd(Animator animation) { - <span class="keyword">if</span> (onDismissCallback != <span class="keyword">null</span>) { - onDismissCallback.onDismiss(dismissPosition); - } - - <span class="comment">//这段代码很重要,因为我们并没有将item从ListView中移除,而是将item的高度设置为0</span> - <span class="comment">//所以我们在动画执行完毕之后将item设置回来</span> - ViewHelper.setAlpha(dismissView, 1f); - ViewHelper.setTranslationX(dismissView, <span class="number"></span>); - ViewGroup.LayoutParams lp = dismissView.getLayoutParams(); - lp.height = originalHeight; - dismissView.setLayoutParams(lp); - - } - }); - - animator.addUpdateListener(<span class="keyword">new</span> ValueAnimator.AnimatorUpdateListener() { - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onAnimationUpdate(ValueAnimator valueAnimator) { - <span class="comment">//这段代码的效果是ListView删除某item之后,其他的item向上滑动的效果</span> - lp.height = (Integer) valueAnimator.getAnimatedValue(); - dismissView.setLayoutParams(lp); - } - }); - - } - - <span class="comment">/**</span> - <span class="comment"> * 删除的回调接口</span> - <span class="comment"> * </span> - <span class="comment"> * @author xiaanming</span> - <span class="comment"> * </span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">interface</span> OnDismissCallback { - <span class="keyword">public</span> <span class="keyword">void</span> onDismiss(<span class="keyword">int</span> dismissPosition); - } - - } 看过Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果你会发现,这个自定义的SwipeDismissListView只重写了onTouchEvent()方法,其实我们重写这一个方法就能实现我们需要的效果
...