自定义SwipeLayout控件实现ListView条目侧滑出现删除按钮,点击实现删除ListView条目
2016-04-08 22:03
525 查看
今天,我们来实现类似于QQ最近联系人的聊天记录向右滑动出现删除按钮的功能。效果图如下:
布局文件如下:
这里这需要自定义一个SwipeLayout实现滑动出现删除按钮的效果,SwipeLayout.java代码如下:
activity_main.xml布局文件代码如下:
listview的条目item_list布局文件如下,这里注意要使用自己定义的SwipeLayout控件作为根布局:
MainActivity.java布局文件如下:
将上述布局文件和java代码布局好,编译运行就可以实现listview向右滑动点击删除按钮达到删除条目的效果了。代码中的一些注意点我都注释了,如果有不明白的地方可以留言哦,我尽量帮大家解决。
布局文件如下:
这里这需要自定义一个SwipeLayout实现滑动出现删除按钮的效果,SwipeLayout.java代码如下:
package com.example.myoperation; import android.content.Context; import android.graphics.Rect; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; /** * 侧拉删除控件 */ public class SwipeLayout extends FrameLayout { private Status status = Status.Close; private OnSwipeLayoutListener swipeLayoutListener; public Status getStatus() { return status; } public void setStatus(Status status) { this.status = status; } public OnSwipeLayoutListener getSwipeLayoutListener() { return swipeLayoutListener; } public void setSwipeLayoutListener(OnSw 4000 ipeLayoutListener swipeLayoutListener) { this.swipeLayoutListener = swipeLayoutListener; } /** * 三种状态,关闭开启和正在拖拽 */ public static enum Status{ Close, Open, Draging } public static interface OnSwipeLayoutListener { void onClose(SwipeLayout mSwipeLayout); void onOpen(SwipeLayout mSwipeLayout); void onDraging(SwipeLayout mSwipeLayout); // 要去关闭 void onStartClose(SwipeLayout mSwipeLayout); // 要去开启 void onStartOpen(SwipeLayout mSwipeLayout); } /** * 第一步实现父类方法,初始化ViewDragHelper */ public SwipeLayout(Context context) { this(context, null); } public SwipeLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwipeLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mDragHelper = ViewDragHelper.create(this, 1.0f, mCallback); } /** * 第三步重写触摸事件方法 */ ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() { /** * 根据返回结果决定当前child是否可以被拖拽 */ @Override public boolean tryCaptureView(View view, int id) { return true;//表示子View都可以被拖拽 } /** * 根据建议值修正将要移动到的(横向)位置 * @param child:当前拖拽的view * @param left:建议值 * return: 真正值 */ public int clampViewPositionHorizontal(View child, int left, int dx) { if(child == mFrontView){ if(left > 0){ return 0; }else if(left < -mRange){ return -mRange; } }else if (child == mBackView) { if(left > mWidth){ return mWidth; }else if (left < mWidth - mRange) { return mWidth - mRange; } } return left; } /** * 当View位置改变的时候。处理要做的事(更新状态,伴随动画,重绘界面) * @param changedView:改变位置的View * @param left:新的左边值 * @param dx:水平方向变化量 */ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { // 传递事件,是两个布局同时移动 if(changedView == mFrontView){ mBackView.offsetLeftAndRight(dx); }else if (changedView == mBackView) { mFrontView.offsetLeftAndRight(dx); } dispatchSwipeEvent(); // 兼容老版本 invalidate(); } /** * 当View被释放的时候,处理的事情(执行动画) * @param releasedChild:被释放的子View * @param xvel:释放时水平方向的速度 * @param yvel:释放时竖直方向的速度 */ public void onViewReleased(View releasedChild, float xvel, float yvel) { if (xvel == 0 && mFrontView.getLeft() < -mRange / 2.0f) { //当水平方向无速度并且向左滑动的距离大于范围的一半则打开 open(); }else if (xvel < 0) { //当有水平方向向左的速度则打开 open(); }else { //其余情况关闭 close(); } } }; /** * 第二步将触摸事件传递给ViewDragHelper,计算子View宽高和移动范围 */ private ViewDragHelper mDragHelper; private View mBackView; private View mFrontView; private int mHeight; private int mWidth; private int mRange; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mDragHelper.shouldInterceptTouchEvent(ev); } protected void dispatchSwipeEvent() { if(swipeLayoutListener != null){ swipeLayoutListener.onDraging(this); } // 记录上一次的状态 Status preStatus = status; // 更新当前状态 status = updateStatus(); if (preStatus != status && swipeLayoutListener != null) { if (status == Status.Close) { swipeLayoutListener.onClose(this); } else if (status == Status.Open) { swipeLayoutListener.onOpen(this); } else if (status == Status.Draging) { if(preStatus == Status.Close){ swipeLayoutListener.onStartOpen(this); }else if (preStatus == Status.Open) { swipeLayoutListener.onStartClose(this); } } } } /** * 更新状态 */ private Status updateStatus() { int left = mFrontView.getLeft(); if(left == 0){ return Status.Close; }else if (left == -mRange) { return Status.Open; } return Status.Draging; } /** * 关闭条目 */ public void close() { close(true); } public void close(boolean isSmooth){ int finalLeft = 0; if(isSmooth){ //开始动画 if(mDragHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){ ViewCompat.postInvalidateOnAnimation(this); } }else { layoutContent(false); } } public void open() { open(true); } public void open(boolean isSmooth){ int finalLeft = -mRange; if(isSmooth){ //开始动画 if(mDragHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){ ViewCompat.postInvalidateOnAnimation(this); } }else { layoutContent(true); } } @Override public void computeScroll() { super.computeScroll(); //持续平滑动画 (高频率调用) if(mDragHelper.continueSettling(true)){ //如果返回true, 动画还需要继续执行 ViewCompat.postInvalidateOnAnimation(this); } } @Override public boolean onTouchEvent(MotionEvent event) { try { mDragHelper.processTouchEvent(event); } catch (Exception e) { e.printStackTrace(); } return true; } /** * 获取前后两个布局 */ @Override protected void onFinishInflate() { super.onFinishInflate(); // 当xml被填充完毕时调用 mBackView = getChildAt(0); mFrontView = getChildAt(1); } /** * 计算前后两个布局的宽高和移动的范围 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mHeight = mFrontView.getMeasuredHeight(); mWidth = mFrontView.getMeasuredWidth(); mRange = mBackView.getMeasuredWidth(); } /** * 对前后两个布局进行摆放 */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); // 摆放位置 layoutContent(false); } private void layoutContent(boolean isOpen) { // 摆放前View Rect frontRect = computeFrontViewRect(isOpen); mFrontView.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom); // 摆放后View Rect backRect = computeBackViewViaFront(frontRect); mBackView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom); // 调整顺序, 把mFrontView前置 bringChildToFront(mFrontView); } /** * 计算前布局摆放的位置 */ private Rect computeFrontViewRect(boolean isOpen) { int left = 0; if(isOpen){ left = -mRange; } return new Rect(left, 0, left + mWidth, 0 + mHeight); } /** * 根据前布局计算后布局的位置 */ private Rect computeBackViewViaFront(Rect frontRect) { int left = frontRect.right; return new Rect(left, 0, left + mRange, 0 + mHeight); } }
activity_main.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=".MainActivity" > <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/lv" > </ListView> </RelativeLayout>
listview的条目item_list布局文件如下,这里注意要使用自己定义的SwipeLayout控件作为根布局:
<?xml version="1.0" encoding="utf-8"?> <com.example.myoperation.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/sl" android:layout_width="match_parent" android:layout_height="60dp" android:minHeight="60dp" android:background="#44000000" > <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:id="@+id/tv_call" android:layout_width="60dp" android:layout_height="match_parent" android:background="#666666" android:gravity="center" android:text="Call" android:textColor="#fff" /> <TextView android:id="@+id/tv_del" android:layout_width="60dp" android:layout_height="match_parent" android:background="#ff0000" android:gravity="center" android:text="Delete" android:textColor="#ffffff" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#44ffffff" android:gravity="center_vertical" android:orientation="horizontal" > <ImageView android:id="@+id/iv_image" android:layout_width="40dp" android:layout_height="40dp" android:layout_marginLeft="15dp" android:src="@drawable/head_1" /> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:text="Name" /> </LinearLayout> </com.example.myoperation.SwipeLayout>
MainActivity.java布局文件如下:
package com.example.myoperation; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import com.example.myoperation.SwipeLayout.OnSwipeLayoutListener; import java.util.ArrayList; public class MainActivity extends Activity { MyAdapter myAdapter; private ArrayList<SwipeLayout> opendItems = new ArrayList<>(); private ArrayList<String> NAMES = new ArrayList<>(); private ListView mList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mList = (ListView) findViewById(R.id.lv); initData(); myAdapter = new MyAdapter(); mList.setAdapter(myAdapter); } private void initData() { for (int i = 0; i < 20; i++){ NAMES.add("name"+i); } } public class MyAdapter extends BaseAdapter { @Override public int getCount() { return NAMES.size(); } @Override public String getItem(int position) { return NAMES.get(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; if(convertView == null){ convertView = View.inflate(MainActivity.this, R.layout.item_list, null); holder = new ViewHolder(); holder.tv_name = (TextView)convertView.findViewById(R.id.tv_name); holder.tv_del = (TextView)convertView.findViewById(R.id.tv_del); convertView.setTag(holder); }else { holder = (ViewHolder)convertView.getTag(); } holder.tv_name.setText(getItem(position)); holder.tv_del.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { NAMES.remove(position); myAdapter.notifyDataSetChanged(); } }); /** * 设置listview滑动监听,当listview滑动时关闭已经开启的的Item */ mList.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { for (SwipeLayout layout : opendItems) { layout.close(); } opendItems.clear(); } }); /** * 设置SwipeLayout滑动监听,当新的Item开启时,关闭之前已经开启的Item */ SwipeLayout sl = (SwipeLayout)convertView; sl.setSwipeLayoutListener(new OnSwipeLayoutListener() { @Override public void onStartOpen(SwipeLayout mSwipeLayout) { for (SwipeLayout layout : opendItems) { layout.close(); } opendItems.clear(); } @Override public void onStartClose(SwipeLayout mSwipeLayout) { } @Override public void onOpen(SwipeLayout mSwipeLayout) { opendItems.add(mSwipeLayout); } @Override public void onDraging(SwipeLayout mSwipeLayout) { } @Override public void onClose(SwipeLayout mSwipeLayout) { opendItems.remove(mSwipeLayout); } }); return convertView; } } static class ViewHolder { TextView tv_name; TextView tv_del; } }
将上述布局文件和java代码布局好,编译运行就可以实现listview向右滑动点击删除按钮达到删除条目的效果了。代码中的一些注意点我都注释了,如果有不明白的地方可以留言哦,我尽量帮大家解决。
相关文章推荐
- 完美实现Android ListView中的TextView的跑马灯效果
- android上改变listView的选中颜色
- 麻雀虽小五脏俱全 Dojo自定义控件应用
- Delphi7中Listview的常用功能汇总
- Delphi控件ListView的属性及使用方法详解
- WinForm自定义控件应用实例
- android中ListView数据刷新时的同步方法
- Android提高之ListView实现自适应表格的方法
- Android中实现水平滑动(横向滑动)ListView示例
- C++ 自定义控件的移植问题
- C#实现用户自定义控件中嵌入自己的图标
- C#实现ListView选中项向上或向下移动的方法
- Listview加载的性能优化是如何实现的
- C#实现listview Group收缩扩展的方法
- C# listview添加combobox到单元格的实现代码
- ListView 百分比进度条(delphi版)
- Android listview多视图嵌套多视图
- ListView Adapter优化 实例
- Android用ListView显示SDCard文件列表的小例子
- Adapter实现ListView带多选框等状态的自定义控件的注意事项