Android学习小Demo(18)Todo List 仿QQ删除任务
2014-04-14 11:34
471 查看
一般情况下,如果想要在ListView上面实现Listitem的滑动删除效果,或者仿QQ的滑动显示删除效果的时候,只需要继承ListView,自定义一个ListView就可以,不过由于之前用了开源库StickyListHeaders来实现ListView的分组,那么这一次在实现这个仿QQ的左右滑动显示隐藏删除按钮的时候,就要在StickyListHeaders的基础上实现。而StickyListHeaders要开放了一个setOnTouchListener的接口,允许调用者传进去一个OnTouchListener,来实现自定义的OnTouch效果,如下:
那么解决问题的方法就在于实现一个OnTouchListener,然后将其传给StickyListHeaderListView。
而ListitemSlopListener就是我们自定义的OnTouchListener了,其代码如下:
我们知道,在屏幕上的任何事件,包括点击,长按,滑动,还是其它手势,其实都是由一系列的Touch事件组成的,而这些Touch事件其实是下面三步组成的:
0)一个ACTION_DOWN事件
1)N个ACTION_MOVE事件
2)一个ACTION_UP事件组成的。
而根据Android的事件分发机制,在触发View本身的OnTouchEvent方法的时候,如果存在一个OnTouchListener,那么View会首先调用OnTouchListener来响应事件,只有当OnTouchListener返回false,表明其不处理该事件的时候,才会继续将事件传递给View的OnTouchEvent,那么在这里,我们就要在OnTouchListener的onTouch方法中,将这个事件给完全消费掉,来实现我们自己想要的效果。
OnTouch方法中,主要实现了下面的功能:
1)当我们手指接触到屏幕上的时候,一个Down事件就会被触发,于是会来到Listener的Down分支下面。
在这里,我们会首先利用ListView的pointToPosition方法,根据触摸的点坐标来获取ListView中item的位置,并利用getChildAt方法找出手指触摸到的那个Listitem,并将其放置到变量mSlopView中。
mSlopView其实就是TaskItem,其结构如下:
删除按钮一开始是invisible的,当Down事件发生的时候,我们要将其置为Visible的,但同时要将其alpha值置为0,因为虽然可见,但此时还不能显示出来。
而最后,要在这里返回一个true,表明在这里,Down事件已经被这个OnTouchListener给消费了,这样,后续的Move事件, Up事件才会继续被这个OnTouchListener来处理。
2)当Donw事件被这个OnTouchListener消费后,后续的Move事件也会来到这里进行处理。
在Move分支中, 我们要根据移动的距离来判断这是不是一次移动,这是通过跟mSlop变量比较实现的。mSlop值是Android定义一个滑动事件的最短距离,当移动超过这个距离,表明这是一个滑动事件,那么这个时候,我们就要根据移动的距离去动态地改变删除按钮的alpha值,来显示或者隐藏删除按钮。在这里,定义
2.1)当手指向左移动的时候,则表明这是要显示按钮,那么会将按钮的alpha值由 0 向 1 变化,最终完全显示按钮。
2.2)当手指向右移动的时候,则表明是要删除按钮,那么会将按钮的alpha值由 1 向 0 变化,最终完全隐藏按钮。
最后,这个方法也要返回 true ,表明Move事件也在这里被消费了。
3)最后就是来到Up事件了。每一个Listitem的OnTouch事件其实分两种情况:
3.1)当OnTouch事件被定义为滑动事件时,即滑动距离超过mSlop的时候,我们要根据最终滑动的距离来决定是否要显示按钮。只有当滑动距离超过按钮的一半宽度的时候,才显示按钮,如果按钮还没有完全显示,则添加一个动画效果,来显示按钮。而如果没有超过按钮的一半宽度的时候,也要为该按钮添加一个动画效果,但是这个效果则是慢慢慢将按钮的alpha值变为0,最终隐藏按钮。
3.1)当滑动的距离小于mSlop的时候,该事件只被定义为一个OnItemClick事件,那么在这里要显示地调用performClick方法来触发ListView的OnItemClick事件,这是因为不想去改变开源库的源码,所以我们必须将所有的OnTouch事件(当然,只是我们想处理的)都在OnTouchListener中处理。
最后,在Up分支中,我们处理了事件之后,同样返回一个true,表明所有的OnTouch事件都在这里被处理了,不需要再将事件传递下去。
下面是实现后的效果图:
结束。
@Override public void setOnTouchListener(final OnTouchListener l) { if (l != null) { mList.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return l.onTouch(StickyListHeadersListView.this, event); } }); } else { mList.setOnTouchListener(null); } }
那么解决问题的方法就在于实现一个OnTouchListener,然后将其传给StickyListHeaderListView。
lvTasks.setOnTouchListener(new ListitemSlopListener(this));
而ListitemSlopListener就是我们自定义的OnTouchListener了,其代码如下:
public class ListitemSlopListener implements OnTouchListener { ... private void initConfiguration() { ViewConfiguration vc = ViewConfiguration.get(mContext); mSlop = vc.getScaledTouchSlop(); mMinFling = vc.getScaledMaximumFlingVelocity(); mMaxFling = mMinFling * 8; } ... @Override public boolean onTouch(View v, MotionEvent event) { mListView = ((StickyListHeadersListView) v).getWrappedList(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = event.getX(); mDownY = event.getY(); mPosition = mListView.pointToPosition((int)mDownX, (int)mDownY); mSlopView = mListView.getChildAt(mPosition - mListView.getFirstVisiblePosition()); Log.v(Helper.TAG,"Action Down : onTouch Triggered"); if(mSlopView != null){ btnDelete = (Button) mSlopView.findViewById(R.id.btnDelete); if(btnDelete.getVisibility() == View.INVISIBLE || btnDelete.getVisibility() == View.GONE){ btnDelete.setVisibility(View.VISIBLE); ViewHelper.setAlpha(btnDelete, 0); } mBtnWidth = btnDelete.getWidth(); initVelocityTrackerIfNotExists(event); return true; } break; case MotionEvent.ACTION_MOVE: if(mVelocityTracker != null && mSlopView != null && btnDelete != null){ float dx = event.getX() - mDownX; float dy = event.getY() - mDownY; if(Math.abs(dx) > mSlop && Math.abs(dy) < mSlop){ mMoving = true; } if(mMoving){ if (dx > 0) { //Direction : right to hide the button if the button shows if(ViewHelper.getAlpha(btnDelete) > 0){ ViewHelper.setAlpha( btnDelete,Math.min(1f,Math.max(0f, 1f - Math.abs(dx) /mBtnWidth))); } } else { //Direction : Left to show the button if the button exists ViewHelper.setAlpha( btnDelete, Math.max(0f,Math.min(1f, Math.abs(dx) / mBtnWidth))); } return true; }else{ return false; } } case MotionEvent.ACTION_UP: if (mVelocityTracker != null && mSlopView != null && mMoving) { float dx = event.getX() - mDownX; mVelocityTracker.computeCurrentVelocity(1000); float vx = Math.abs(mVelocityTracker.getXVelocity()); float vy = Math.abs(mVelocityTracker.getYVelocity()); boolean isShow = false; if(Math.abs(dx) > mBtnWidth / 2){ isShow = dx < 0; }else if(vx > mMinFling && vx < mMaxFling && vx > vy){ isShow = vx < 0; } if(isShow){ ViewPropertyAnimator.animate(btnDelete).alpha(1f).setDuration(DURATION); }else{ ViewPropertyAnimator.animate(btnDelete).alpha(0f).setDuration(DURATION); } recycleVelocityTracker(); mMoving = false; }else{ //Only When the Delete Button is not visible, the the onItemClick action was fired. if(btnDelete != null && ViewHelper.getAlpha(btnDelete) > 0){ ViewPropertyAnimator.animate(btnDelete).alpha(0f).setDuration(DURATION); }else{ TodoTask todoTask = (TodoTask) mListView.getItemAtPosition(mPosition); if(todoTask != null){ mListView.performItemClick(mSlopView, mPosition, todoTask.getId()); } } } return true; } return false; } }
我们知道,在屏幕上的任何事件,包括点击,长按,滑动,还是其它手势,其实都是由一系列的Touch事件组成的,而这些Touch事件其实是下面三步组成的:
0)一个ACTION_DOWN事件
1)N个ACTION_MOVE事件
2)一个ACTION_UP事件组成的。
而根据Android的事件分发机制,在触发View本身的OnTouchEvent方法的时候,如果存在一个OnTouchListener,那么View会首先调用OnTouchListener来响应事件,只有当OnTouchListener返回false,表明其不处理该事件的时候,才会继续将事件传递给View的OnTouchEvent,那么在这里,我们就要在OnTouchListener的onTouch方法中,将这个事件给完全消费掉,来实现我们自己想要的效果。
OnTouch方法中,主要实现了下面的功能:
1)当我们手指接触到屏幕上的时候,一个Down事件就会被触发,于是会来到Listener的Down分支下面。
在这里,我们会首先利用ListView的pointToPosition方法,根据触摸的点坐标来获取ListView中item的位置,并利用getChildAt方法找出手指触摸到的那个Listitem,并将其放置到变量mSlopView中。
mSlopView其实就是TaskItem,其结构如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="48dp" android:background="@drawable/item_selector" android:padding="5dip" > <ImageView android:id="@+id/ivComplete" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:contentDescription="@string/imageview_contentdesc" android:padding="5dp" /> <TextView android:id="@+id/tvTitle" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_centerVertical="true" android:layout_toRightOf="@+id/ivComplete" android:gravity="left|center_vertical" android:padding="5dp" android:singleLine="true" android:textSize="18sp" /> <Button android:id="@+id/btnDelete" android:layout_width="wrap_content" android:layout_height="32dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="@drawable/shape_button" android:padding="5dp" android:text="@string/action_delete" android:textSize="12sp" android:visibility="invisible" /> </RelativeLayout>
删除按钮一开始是invisible的,当Down事件发生的时候,我们要将其置为Visible的,但同时要将其alpha值置为0,因为虽然可见,但此时还不能显示出来。
而最后,要在这里返回一个true,表明在这里,Down事件已经被这个OnTouchListener给消费了,这样,后续的Move事件, Up事件才会继续被这个OnTouchListener来处理。
2)当Donw事件被这个OnTouchListener消费后,后续的Move事件也会来到这里进行处理。
在Move分支中, 我们要根据移动的距离来判断这是不是一次移动,这是通过跟mSlop变量比较实现的。mSlop值是Android定义一个滑动事件的最短距离,当移动超过这个距离,表明这是一个滑动事件,那么这个时候,我们就要根据移动的距离去动态地改变删除按钮的alpha值,来显示或者隐藏删除按钮。在这里,定义
2.1)当手指向左移动的时候,则表明这是要显示按钮,那么会将按钮的alpha值由 0 向 1 变化,最终完全显示按钮。
2.2)当手指向右移动的时候,则表明是要删除按钮,那么会将按钮的alpha值由 1 向 0 变化,最终完全隐藏按钮。
最后,这个方法也要返回 true ,表明Move事件也在这里被消费了。
3)最后就是来到Up事件了。每一个Listitem的OnTouch事件其实分两种情况:
3.1)当OnTouch事件被定义为滑动事件时,即滑动距离超过mSlop的时候,我们要根据最终滑动的距离来决定是否要显示按钮。只有当滑动距离超过按钮的一半宽度的时候,才显示按钮,如果按钮还没有完全显示,则添加一个动画效果,来显示按钮。而如果没有超过按钮的一半宽度的时候,也要为该按钮添加一个动画效果,但是这个效果则是慢慢慢将按钮的alpha值变为0,最终隐藏按钮。
3.1)当滑动的距离小于mSlop的时候,该事件只被定义为一个OnItemClick事件,那么在这里要显示地调用performClick方法来触发ListView的OnItemClick事件,这是因为不想去改变开源库的源码,所以我们必须将所有的OnTouch事件(当然,只是我们想处理的)都在OnTouchListener中处理。
最后,在Up分支中,我们处理了事件之后,同样返回一个true,表明所有的OnTouch事件都在这里被处理了,不需要再将事件传递下去。
下面是实现后的效果图:
结束。
相关文章推荐
- Android学习小Demo(9)一个To Do List的实现
- [李景山php]每天laravel[023]-laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 ---删除数据
- vue demo todo-list
- Android学习小Demo(22)带删除按钮的TextView
- vue.js 实现 todo list 任务表单
- Android studio TODO 待处理任务窗口使用
- [李景山php]每天laravel[023]-laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 ---在主页上添加数据
- vue.js 实现 todo list 任务表单-2
- [李景山php]每天laravel[022]-laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 ---Task 任务处理控制器--显示主页
- TODO List based 算法、任务分解、任务顺序、分治法
- Android开发学习之路-PopupWindow和仿QQ左滑删除
- [李景山php]每天laravel[018]-laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 ---准备关系模型
- Win8:To Do List Demo
- [李景山php]每天laravel[019]-laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 ---路由
- [李景山php]每天laravel[020]-laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 ---视图
- Wunderlist 云端任务管理(Todo list)工具
- [李景山php]每天laravel[023]-laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 ---登录,该死的登录
- TODO List - 任务表
- laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 ---在主页上添加数据
- laravel 中级任务--小白教程----实际操作-创建基础任务 to-do-list 案例 --