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
相关文章推荐
- Android Studio——Dialogde 的主题报错
- Android中Handler消息机制
- Android Studio 快捷键
- Android Studio 设置内存大小及原理,优化运行速度
- Android 权限列表
- Android-View-绘制
- Android群英传学习笔记——ListView使用技巧
- ndk获取Android进程PID和名称
- Android:自适应不同分辨率的字体大小
- android-app manifest(5) - API 23
- Android中自动连接到指定SSID的Wi-Fi
- android横竖屏切换和布局的问题
- android横竖屏切换和布局的问题
- Android数据存储五种方式总结
- Android人脸识别——眼睛是窗口
- Android Rild 概述
- Android 操作系统的内存回收机制
- Android:Theme总结--小白总结
- Android AutoLayout全新的适配方式
- android典型代码系列(三十)------DES加密算法