您的位置:首页 > 移动开发 > Android开发

Android关于Listview左滑动删除的原理解析

2015-12-06 15:41 579 查看

有如类似QQ左滑动删除的效果,如图



然后我们跟着代码一起看下它是怎么实现的。

先说下实现原理:也是一个ListView,然后一个adapter。然后在adapter中设置一个正常显示的view。接着自定义一个View(MyView),把adapter中get的view(contentView)作为MyView的child View,然后在添加一个删除View作为MyView的child View并且把它放置在contentView的右边,这样一个带删除按钮的itemView就好了,接着在MyView中处理复杂的手势动作,控制让MyView向左右滑动即可。

首先看看Activity源码:

package com.example.slideviewdemo;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;

public class PirvateListingActivity extends Activity {

	private static final String TAG = "MainActivity";
	private ListViewCompat mListView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_privatelisting);
		initView();
	}

	private void initView() {
		mListView = (ListViewCompat) findViewById(R.id.list);
		PrivateListingAdapter mAdapter = new PrivateListingAdapter(this);
		ArrayList<MessageBean> mMessageList = new ArrayList<MessageBean>();
		for (int i = 0; i < (20); i++) {
			MessageBean item = new MessageBean();
			if (i % 3 == 0) {
				item.iconRes = R.drawable.default_qq_avatar;
				item.title = "腾讯新闻";
				item.msg = "青岛爆炸满月:大量鱼虾死亡";
				item.time = "晚上18:18";
			} else {
				item.iconRes = R.drawable.wechat_icon;
				item.title = "微信团队";
				item.msg = "欢迎你使用微信";
				item.time = "12月18日";
			}
			mMessageList.add(item);
		}
		mAdapter.setmMessageItems(mMessageList);
		mListView.setAdapter(mAdapter);
	}
}
PirvateListingActivity比较简单,设置contentView,设置adapter,初始化listData,没什么可讲的。

看activity_privatelisting.xml布局文件,更是简单

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PirvateListingActivity" >

    <com.example.slideviewdemo.ListViewCompat
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#fff4f7f9"
        android:cacheColorHint="#00000000"
        android:divider="#dddbdb"
        android:dividerHeight="1.0px"
        android:drawSelectorOnTop="false"
        android:listSelector="@android:color/transparent"
        android:scrollbars="none" />

</RelativeLayout>
主要的就是一个自定义ListView。

接下来我们看下adapter代码:

package com.example.slideviewdemo;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.slideviewdemo.SlideView.OnSlideListener;

public class PrivateListingAdapter extends BaseAdapter implements
		OnSlideListener {
	private static final String TAG = "SlideAdapter";

	private Context mContext;
	private LayoutInflater mInflater;
	private List<MessageBean> mMessageItems = new ArrayList<MessageBean>();
	// 上次显示了删除按钮的itemView;
	private SlideView mLastSlideViewWithStatusOn;

	PrivateListingAdapter(Context context) {
		mContext = context;
		mInflater = LayoutInflater.from(mContext);
	}

	public void setmMessageItems(List<MessageBean> mMessageItems) {
		this.mMessageItems = mMessageItems;
	}

	@Override
	public int getCount() {
		if (mMessageItems == null) {
			mMessageItems = new ArrayList<MessageBean>();
		}
		return mMessageItems.size();
	}

	@Override
	public Object getItem(int position) {
		return mMessageItems.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(final int position, View convertView, ViewGroup parent) {
		ViewHolder holder;
		// 自定义ItemView
		SlideView slideView = (SlideView) convertView;
		if (slideView == null) {
			View itemView = mInflater.inflate(R.layout.privatelisting_item,
					null);

			slideView = new SlideView(mContext);
			slideView.setContentView(itemView);
			holder = new ViewHolder(slideView);
			slideView.setOnSlideListener(this);
			slideView.setTag(holder);
		} else {
			holder = (ViewHolder) slideView.getTag();
		}
		MessageBean item = mMessageItems.get(position);
		item.slideView = slideView;
		item.slideView.shrink();

		holder.icon.setImageResource(item.iconRes);
		holder.title.setText(item.title);
		holder.msg.setText(item.msg);
		holder.time.setText(item.time);
		holder.deleteHolder.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				mMessageItems.remove(position);
				notifyDataSetChanged();
			}
		});

		return slideView;
	}

	private static class ViewHolder {
		public ImageView icon;
		public TextView title;
		public TextView msg;
		public TextView time;
		public ViewGroup deleteHolder;

		ViewHolder(View view) {
			icon = (ImageView) view.findViewById(R.id.icon);
			title = (TextView) view.findViewById(R.id.title);
			msg = (TextView) view.findViewById(R.id.msg);
			time = (TextView) view.findViewById(R.id.time);
			deleteHolder = (ViewGroup) view.findViewById(R.id.holder);
		}
	}

	/**
	 * 自定义view接口回调, 更新上一次正在滑动的View
	 */
	@Override
	public void onSlide(View view, int status) {
		if (mLastSlideViewWithStatusOn != null
				&& mLastSlideViewWithStatusOn != view) {
			mLastSlideViewWithStatusOn.shrink();
		}

		if (status == SLIDE_STATUS_ON) {
			mLastSlideViewWithStatusOn = (SlideView) view;
		}
	}
}
看重点,我们看getVeiw方法。里面用的就是自定义slideView。privatelisting_item.xml的长为占满屏幕,高为包裹内容。slideView通过setContentView(itemView)把itemView作为自己的左边conentView,然后在conentView的右边添加一个删除按钮View,这样就成了一个带删除按钮的listItem View。holder.deleteHolder.setOnClickListener就是设置删除按钮的点击事件。

privatelisting_item.xml布局如下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="58dp"
    android:background="@drawable/list_item_bg"
    android:descendantFocusability="blocksDescendants"
    android:gravity="center_vertical" >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginRight="5dp"
        android:src="@drawable/wechat_icon" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        android:text="one"
        android:textColor="@color/black"
        android:textSize="15sp" />

    <TextView
        android:id="@+id/msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/icon"
        android:layout_alignLeft="@id/title"
        android:text="two"
        android:textColor="@color/grey" />

    <TextView
        android:id="@+id/time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignTop="@id/title"
        android:text="three"
        android:textColor="@color/grey" />

</RelativeLayout>


下面看看自定义SlideView

package com.example.slideviewdemo;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import android.widget.TextView;

public class SlideView extends LinearLayout {

	private static final String TAG = "SlideView";

	private Context mContext;
	private LinearLayout mViewContent;
	private RelativeLayout mHolder;
	private Scroller mScroller;
	private OnSlideListener mOnSlideListener;
	// 删除按钮宽度
	private int mHolderWidth = 120;
	private int mLastX = 0;
	private int mLastY = 0;
	private static final int TAN = 2;

	public interface OnSlideListener {
		public static final int SLIDE_STATUS_OFF = 0;
		public static final int SLIDE_STATUS_START_SCROLL = 1;
		public static final int SLIDE_STATUS_ON = 2;

		/**
		 * @param view
		 *            current SlideView
		 * @param status
		 *            SLIDE_STATUS_ON or SLIDE_STATUS_OFF
		 */
		public void onSlide(View view, int status);
	}

	public SlideView(Context context) {
		super(context);
		initView();
	}

	public SlideView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initView();
	}

	private void initView() {
		mContext = getContext();
		mScroller = new Scroller(mContext);
		// 设置linearlayout的orientation为横向
		setOrientation(LinearLayout.HORIZONTAL);
		View.inflate(mContext, R.layout.privatelisting_delete_merge, this);
		mViewContent = (LinearLayout) findViewById(R.id.view_content);
		mHolderWidth = Math.round(TypedValue.applyDimension(
				TypedValue.COMPLEX_UNIT_DIP, mHolderWidth, getResources()
						.getDisplayMetrics()));
		Log.d(TAG, "mHolderWidth:" + mHolderWidth);
	}

	public void setButtonText(CharSequence text) {
		((TextView) findViewById(R.id.delete)).setText(text);
	}

	/**
	 * 设置左边内容View
	 * 
	 * @param view
	 */
	public void setContentView(View view) {
		mViewContent.addView(view);
	}

	public void setOnSlideListener(OnSlideListener onSlideListener) {
		mOnSlideListener = onSlideListener;
	}

	public void onRequireTouchEvent(MotionEvent event) {
		int x = (int) event.getX();
		int y = (int) event.getY();
		// 得到的相对于View初始x轴位置的距离
		int scrollX = getScrollX();
		Log.d(TAG, "x=" + x + "  y=" + y + "  scrollX=" + scrollX);
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN: {
			if (!mScroller.isFinished()) {
				mScroller.abortAnimation();
			}
			if (mOnSlideListener != null) {
				mOnSlideListener.onSlide(this,
						OnSlideListener.SLIDE_STATUS_START_SCROLL);
			}
			break;
		}
		case MotionEvent.ACTION_MOVE: {
			int deltaX = x - mLastX;
			int deltaY = y - mLastY;
			if (Math.abs(deltaX) < Math.abs(deltaY) * TAN) {
				break;
			}
			int newScrollX = scrollX - deltaX;
			if (deltaX != 0) {
				if (newScrollX < 0) {
					newScrollX = 0;
				} else if (newScrollX > mHolderWidth) {
					newScrollX = mHolderWidth;
				}
				this.scrollTo(newScrollX, 0);
			}
			break;
		}
		case MotionEvent.ACTION_UP: {
			int newScrollX = 0;
			if (scrollX - mHolderWidth * 0.75 > 0) {
				newScrollX = mHolderWidth;
			}
			this.smoothScrollTo(newScrollX, 0);
			if (mOnSlideListener != null) {
				mOnSlideListener.onSlide(this,
						newScrollX == 0 ? OnSlideListener.SLIDE_STATUS_OFF
								: OnSlideListener.SLIDE_STATUS_ON);
			}
			break;
		}
		default:
			break;
		}

		mLastX = x;
		mLastY = y;
	}

	public void shrink() {
		if (getScrollX() != 0) {
			this.smoothScrollTo(0, 0);
		}
	}

	private void smoothScrollTo(int destX, int destY) {
		// 缓慢滚动到指定位置
		int scrollX = getScrollX();
		int delta = destX - scrollX;
		mScroller.startScroll(scrollX, 0, delta, 0, Math.abs(delta) * 3);
		invalidate();
	}

	@Override
	public void computeScroll() {
		Log.d(TAG, "computeScroll");
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}
	}

}
SlideView继承LinearLayout,它里面有两个子View,SlideView把orientation设为HORIZONTAL,两个子View就横向排列了。 因为左边mViewContent宽占满屏幕,所有右边子View默认就超出屏幕。

onRequireTouchEvent(MotionEvent event)处理传过来的触摸事件。

当MotionEvent.ACTION_DOWN时,停止当前滚动,并且回调mOnSlideListener.onSlide()方法,接口的实现在PrivateListingAdapter里面。接口回调处理的是:判断当前Touch的ItemView是否和上一个Touch的View是否是同一个,如果不是,把上一个Touch的View

mLastSlideViewWithStatusOn.shrink()(即向右滑动至影藏删除按钮)。

当MotionEvent.ACTION_MOVE时,计算手指滑动的横向距离,然后this.scrollTo(newScrollX, 0)(即自己横向滚动相应的距离),主意当手指只要一直触摸着屏幕,onRequireTouchEvent方法会一直被调用,然后事件类型一直为ACTION_MOVE,所以每次move的距离不会很长。通过log可以看到每次的距离为1,2,或3等。如果滑动的越快,距离会相应的变大。

当MotionEvent.ACTION_UP时,计算当前滚动的距离是否大于mHolderWidth * 0.75,是的话就向左滚动至mHolderWidth ,否则向右滚动0(即隐藏删除按钮);最后在mOnSlideListener.onSlide()下,更新mLastSlideViewWithStatusOn指向当前Touch的View;

SlideView中的privatelisting_delete_merge.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:id="@+id/view_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >
    </LinearLayout>

    <RelativeLayout
        android:id="@+id/holder"
        android:layout_width="120dp"
        android:layout_height="match_parent"
        android:background="@drawable/holder_bg"
        android:clickable="true" >

        <TextView
            android:id="@+id/delete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:drawableLeft="@drawable/del_icon_normal"
            android:gravity="center"
            android:text="删除"
            android:textColor="@color/floralwhite" />
    </RelativeLayout>

</merge>


一个 view_content,一个删除View。因为SlideView继承LinearLayout并且设置orientation="horizontal",所以它们横向排列。

最后我们看看ListViewCompat(自定义ListView),

package com.example.slideviewdemo;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;

public class ListViewCompat extends ListView {

	private static final String TAG = "ListViewCompat";

	private SlideView mFocusedItemView;

	public ListViewCompat(Context context) {
		super(context);
	}

	public ListViewCompat(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public ListViewCompat(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public void shrinkListItem(int position) {
		View item = getChildAt(position);

		if (item != null) {
			try {
				((SlideView) item).shrink();
			} catch (ClassCastException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN: {
			int x = (int) event.getX();
			int y = (int) event.getY();
			Log.d("listView x==y:", x + ":" + y);
			int position = pointToPosition(x, y);
			Log.e(TAG, "postion=" + position);
			if (position != INVALID_POSITION) {
				MessageBean data = (MessageBean) getItemAtPosition(position);
				mFocusedItemView = data.slideView;
				// Log.e(TAG, "FocusedItemView=" + mFocusedItemView);
			}
		}
		default:
			break;
		}

		if (mFocusedItemView != null) {
			mFocusedItemView.onRequireTouchEvent(event);
			// return true;
		}

		return super.onTouchEvent(event);

	}

}


重写了onTouchEvent,当ACTION_DOWN时通过pointToPosition(x, y)找到当前touch的是哪一个item,然后通过data.slideView找到对应的 slideView,并且回调slideView的onRequireTouchEvent(event)方法,就把事件传给了每一个ItemView。这样就完成了一个类似QQ左滑动删除功能的ListView。

里面有个比较难的地方就是Scroller的使用,它的每一次滚动都要不停地刷新,要重写computeScroll方法。这是我比较难掌握的地方,特意整理出来做个笔记,供自己参考使用,也给看到这篇文章的读者参考使用!

以上如果不对的地方,还望大家多多指正,谢谢!

附本篇博客源码http://download.csdn.net/detail/u014763302/9330909
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: