Android学习小Demo(18)Todo List 仿QQ删除任务

一般情况下,如果想要在ListView上面实现Listitem的滑动删除效果,或者仿QQ的滑动显示删除效果的时候,只需要继承ListView,自定义一个ListView就可以,不过由于之前用了开源库StickyListHeaders来实现ListView的分组,那么这一次在实现这个仿QQ的左右滑动显示隐藏删除按钮的时候,就要在StickyListHeaders的基础上实现。而StickyListHeaders要开放了一个setOnTouchListener的接口,允许调用者传进去一个OnTouchListener,来实现自定义的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(18)Todo List 仿QQ删除任务

结束。

Android学习小Demo(18)Todo List 仿QQ删除任务,布布扣,bubuko.com

Android学习小Demo(18)Todo List 仿QQ删除任务

上一篇:Ubuntu中手动安装配置JDK, Android SDK, NDK


下一篇:object-c AppDelegate代理函数生命周期详解