自定义ListView实现仿QQ消息列表滑动item出现删除按钮
来源:互联网 发布:js undefined判断 编辑:程序博客网 时间:2024/06/05 15:54
最近项目组中要出线下版产品,其中一个界面需要实现一个右滑Listview中item,然后可以删除和重命名该item。正好我最近在看自定义控件,所以自荐做这个页面的交互设计,也算练手。刚开始我想的太简单了,我以为添加一个滑动的action,然后item去响应这个action就好了,至于滑动多少距离,或者说item滚动多少距离,不同的滚动会有怎样的效果,好多实际的问题没有考虑进去,当初自己写出来,也是各种crash。所以上网去看别人写的,后来看了一个,模仿写了一个只能左滑,但是项目需求右滑,我就觉得这简单嘛,结果反过来后滑是滑了,item设置的一些控件没有显示出来,也是醉。最终找到一个比较合适的,然后根据那个自己重写了一下ListView,和一个适配器Adapter。
首先写一个类继承ListView,我定义为CustomListView:
public class CustomListView extends ListView {/**禁止侧滑模式*/public static int MOD_FORBID = 0;/**从左向右滑出菜单模式*/public static int MOD_LEFT = 1;/**当前的模式*/private int mode = MOD_FORBID;/**左侧菜单的长度*/private int leftLength = 0;/** * 当前滑动的ListView position */private int slidePosition;/** * 手指按下X的坐标 */private int downX;/** * 手指按下Y的坐标 */private int downY;/** * ListView的item */private View itemView;/** * 滑动类 */private Scroller scroller;/** * 认为是用户滑动的最小距离 */private int mTouchSlop;/** * 判断是否可以侧向滑动 */private boolean canMove = false;/** * 标示是否完成侧滑 */private boolean isSlided = false;public CustomListView(Context context) {this(context,null);}public CustomListView(Context context, AttributeSet attrs) {this(context, attrs,0);}public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);scroller = new Scroller(context);mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();}//初始化菜单的划出模式,供外部调用public void initSlideMode(int mode){this.mode = mode;}/** * 处理我们拖动ListView item的逻辑 */@Overridepublic boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();int lastX = (int) ev.getX();switch (action) {case MotionEvent.ACTION_DOWN:/*当前模式不允许滑动,则直接返回,交给ListView自身去处理*/if(this.mode == MOD_FORBID){return super.onTouchEvent(ev);}// 如果处于侧滑完成状态,侧滑回去,并直接返回if (isSlided) {scrollBack();return false;}// 假如scroller滚动还没有结束,我们直接返回 if (!scroller.isFinished()) {return false;}downX = (int) ev.getX();downY = (int) ev.getY();slidePosition = pointToPosition(downX, downY);// 无效的position, 不做任何处理if (slidePosition == AdapterView.INVALID_POSITION) {return super.onTouchEvent(ev);}// 获取我们点击的item viewitemView = getChildAt(slidePosition - getFirstVisiblePosition());/*此处根据设置的滑动模式,自动获取左侧菜单的长度*/if(this.mode == MOD_LEFT){this.leftLength = -itemView.getPaddingLeft();}break;case MotionEvent.ACTION_MOVE:if (!canMove&& slidePosition != AdapterView.INVALID_POSITION&& (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev.getY() - downY) < mTouchSlop)) {int offsetX = downX - lastX;if(offsetX < 0 && this.mode == MOD_LEFT){/*从左向右滑*/canMove = true;}else{canMove = false;}/*此段代码是为了避免我们在侧向滑动时同时出发ListView的OnItemClickListener时间*/MotionEvent cancelEvent = MotionEvent.obtain(ev);cancelEvent.setAction(MotionEvent.ACTION_CANCEL| (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));onTouchEvent(cancelEvent);}if (canMove) {/*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/requestDisallowInterceptTouchEvent(true);// 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚int deltaX = downX - lastX;if(deltaX < 0 && this.mode == MOD_LEFT){/*向左滑*/itemView.scrollTo(deltaX, 0);}else{itemView.scrollTo(0, 0);}return true; // 拖动的时候ListView不滚动}case MotionEvent.ACTION_UP:if (canMove){canMove = false;scrollByDistanceX();}break;}// 否则直接交给ListView来处理onTouchEvent事件return super.onTouchEvent(ev);}/** * 根据手指滚动itemView的距离来判断是滚动到开始位置还是向左或者向右滚动 */private void scrollByDistanceX() {/*当前模式不允许滑动,则直接返回*/if(this.mode == MOD_FORBID){return;}if(itemView.getScrollX() < 0 && this.mode == MOD_LEFT){/*从左向右滑*/if (itemView.getScrollX() <= -leftLength / 2) {scrollRight();} else {// 滚回到原始位置scrollBack();}}else{// 滚回到原始位置scrollBack(); }}/** * 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值 */private void scrollRight() {isSlided = true;final int delta = (leftLength + itemView.getScrollX());// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动itemscroller.startScroll(itemView.getScrollX(), 0, -delta, 0,Math.abs(delta));postInvalidate(); // 刷新itemView}/** * 滑动会原来的位置 */private void scrollBack() {isSlided = false;scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(),0, Math.abs(itemView.getScrollX()));postInvalidate(); // 刷新itemView}@Overridepublic void computeScroll() {// 调用startScroll的时候scroller.computeScrollOffset()返回true,if (scroller.computeScrollOffset()) {// 让ListView item根据当前的滚动偏移量进行滚动itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());postInvalidate();}}/** * 提供给外部调用,用以将侧滑出来的滑回去 */public void slideBack() {this.scrollBack();}}这里定义了几个常量,负责控制item是否可以滑动,这样的好处是方便管理,和控制。然后用到了Scroller类,这个类负责item的滚动位置。这里注意,因为继承的ListView,所以在解析xml文件时,调用的构造函数是一个参数的构造函数,而我们需要的是三个参数的,所以要一步一步的调用到三个参数的构造函数。这里面做些初始化工作。所有滑动的逻辑都是响应了touch事件,所以实现onTouchEvent方法,里面就是一些坐标的计算,当计算完后,会调用实现的scroller类里面的方法,去完成滑动。这里使用的padding实现的,我也看过几个margin实现的,但是觉得没有这个好。
我写的item的布局文件如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="80dp" android:paddingLeft="-160dp" android:paddingRight="0dp" android:background="#ffffff" android:orientation="horizontal" > <RelativeLayout android:layout_width="160dp" android:layout_height="80dp" > <LinearLayout android:id="@+id/option" android:layout_width="160dp" android:layout_height="match_parent" android:background="#ffffff" android:orientation="horizontal" > <RelativeLayout android:id="@+id/delete" android:layout_width="80dp" android:layout_height="match_parent" android:clickable="true"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="15sp" android:text="删除"/> </RelativeLayout> <View android:layout_width="1dp" android:layout_height="50dp" android:layout_gravity="center_vertical" android:background="#ededed"/> <RelativeLayout android:id="@+id/rename" android:layout_width="80dp" android:layout_height="match_parent" android:clickable="true"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="15sp" android:text="重命名"/> </RelativeLayout> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:layout_alignParentBottom="true" android:background="#ededed"/> </RelativeLayout> <View android:layout_width="1dp" android:layout_height="70dp" android:layout_gravity="center_vertical" android:background="#ededed"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="80dp" > <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/script_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:textColor="#000000" android:textSize="20sp" /> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:layout_alignParentBottom="true" android:background="#ededed"/> </RelativeLayout></LinearLayout>在主布局文件中直接写入自定义的ListView就ok了。
之后重写了一个Adapter,去适配这个listview,及它的item,如下:
public class SlideAdapter extends BaseAdapter {private List<String> data;private LayoutInflater mInflate;private Context context;private CustomListView listView;private onDeleteListener deleteListener;private onRenameListener renameListener;public interface onDeleteListener {public void deleteScript(int position);}public interface onRenameListener {public void renameScript(int position);}public SlideAdapter(List<String> data, Context context, CustomListView listView) {this.data = data;this.context = context;this.mInflate = LayoutInflater.from(context);this.listView = listView;}public SlideAdapter(List<String> data, Context context, CustomListView listView, onDeleteListener deleteListener,onRenameListener renameListener) {this.data = data;this.context = context;this.mInflate = LayoutInflater.from(context);this.listView = listView;this.deleteListener = deleteListener;this.renameListener = renameListener;}@Overridepublic int getCount() {return data.size();}@Overridepublic Object getItem(int position) {return data.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {ViewHolder holder = new ViewHolder();if (convertView == null) {convertView = mInflate.inflate(R.layout.script_list_item, null);holder.title = (TextView) convertView.findViewById(R.id.script_name);holder.delete = (RelativeLayout) convertView.findViewById(R.id.delete);holder.rename = (RelativeLayout) convertView.findViewById(R.id.rename);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}String name = data.get(position);holder.title.setText(name);holder.delete.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {// 刪除操作if(deleteListener!=null){deleteListener.deleteScript(position);listView.slideBack();}}});holder.rename.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//重命名if(renameListener!=null){renameListener.renameScript(position);listView.slideBack();}}});return convertView;}private static class ViewHolder {public TextView title;public RelativeLayout delete;public RelativeLayout rename;}}
这里想的是,如果我确定了listview中item的类型,我就完全封装这个adapter,至于右滑出现的按钮的响应事件,使用回调函数在使用这个自定义Listview的Activity自己去实现,那样就会很方便开发人员。ListView并没有进行什么优化,这方面我还得再找时间学习。
最后在主Activity中使用该Listview就好了,
public class MainActivity extends Activity implements onDeleteListener,onRenameListener{private CustomListView mCustomListView;SlideAdapter mAdapter;private List<String> mData;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mData = new ArrayList<String>();mData.add("第一个测试用例");mData.add("第二个测试用例");mData.add("第三个测试用例");mCustomListView = (CustomListView) findViewById(R.id.custom_list);mCustomListView.initSlideMode(CustomListView.MOD_LEFT);mAdapter = new SlideAdapter(mData, getApplicationContext(), mCustomListView,this,this);mCustomListView.setAdapter(mAdapter);}@Overridepublic void renameScript(int position) {mData.set(position, "更改后的名称");mAdapter.notifyDataSetChanged();}@Overridepublic void deleteScript(int position) {mData.remove(position);mAdapter.notifyDataSetChanged();}}
这里我很简单的添加了几个数据便于测试。
运行效果如下(我依然不会改成gif……T_T):
参考了这位大神的博客:http://www.2cto.com/kf/201407/319941.html
自定义控件的路还有好长。
0 0
- 自定义ListView实现仿QQ消息列表滑动item出现删除按钮
- 仿QQ消息列表item横向滑动删除ListView中item侧滑删除
- Android仿QQ消息列表ListView滑动删除效果
- Android ListView实现单击item出现删除按钮以及滑动出现删除按钮
- Android ListView实现单击item出现删除按钮以及滑动出现删除按钮
- Android仿QQ实现ListView滑动删除
- Android仿QQ实现ListView滑动删除
- ViewDragHelper实现仿qq列表滑动删除
- Android仿QQ、微信ListView滑动删除item
- android listview 滑动删除加刷新功能 仿QQ消息列表
- 高仿QQ侧滑item删除的自定义ListView
- 仿android手机qq消息列表中删除按钮效果
- 仿android手机qq消息列表中删除按钮效果
- 自定义View之仿QQ消息滑动删除
- 仿ios短信列表滑动出现删除按钮
- 自定义listview 实现仿qq聊天列表左滑删除效果
- Android学习自定义View(四)——继承控件(滑动时ListView的Item出现删除按钮)
- 重写listview,横向滑动出现删除按钮,点击按钮删除item
- codeforces round# 302 (div1 C) (状压dp)
- 数据库Sharding的基本思想和切分策略
- 局域网中其他电脑访问我的电脑上的apache服务器
- 读取单个图片
- TopCoder SRM 667 Div1 Problem 250 - OrderOfOperations (状压dp)
- 自定义ListView实现仿QQ消息列表滑动item出现删除按钮
- SQL语法优化
- iOS 开发中关于时间显示问题(今天/昨天/前天/星期几/具体日期)
- 正确理解ThreadLocal
- 设计模式之工厂方法模式
- 【KMP】【HDU3746】【最小循环节】
- hdu 5442 Favorite Donut 最小表示法+KMP
- struts2 拦截器
- 【2015/9/15】漫漫大神路——非常精彩的java内存分析