有如类似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