通过ViewDragHelper实现ListView的Item的侧拉划出效果

来源:互联网 发布:恶魔的奶爸 知乎专栏 编辑:程序博客网 时间:2024/04/27 15:55

先来看看,今天要实现的自定义控件效果图:



关于ViewDragHelper的使用,大家可以先看这篇文章ViewDragHelper的使用介绍

实现该自定义控件的大体步骤如下:

1.ViewDragHelper使用的3部曲,初始化ViewDragHelper,传递触摸事件,实现ViewDragHelper.Callback抽象类.

2.需要创建2个直接的子View,分别是前景View和背景View,代表ListView每一项Item的布局的组成,如下所示:

未划出时显示的FrontView:


划出后的右边显示BackView:



以上2部分就是该自定义控件要包含的2个直接子View.

3.需要获取FrontView的宽高,宽度其实就是屏幕的宽度,高度就是ListView每一项Item的高度;还需获取BackView的宽度,因为这个宽度就是侧滑的最大范围.

4.需要确定FrontView和BackView的初始位置,在onLayout方法中确定,即默认情况下是只显示FrontView的.这个实现起来也很简单,FrontView的left=0,BackView的left=FrontView的right即可.

5.需要同步FrontView和BackView的滑动,即滑动FrontView的时候BackView也需要跟着划出,同样滑动BackView的时候也需要FrontView跟着滑动.

6.需要解决侧拉划出的效果是否有动画效果.平滑滑动的动画可以通过ViewDragHelper轻松实现.


好了,直接上自定义的SwipeLayout源码:

[java] view plain copy
  1. /** 
  2.  * Created by mChenys on 2015/12/26. 
  3.  */  
  4. public class SwipeLayout extends FrameLayout {  
  5.   
  6.     private ViewDragHelper.Callback mCallback;  
  7.     private ViewDragHelper mDragHelper;  
  8.     private View mBackView; //item的侧边布局  
  9.     private View mFrontView;//当前显示的item布局  
  10.     private int mWidth; //屏幕的宽度,mFrontView的宽度  
  11.     private int mHeight; //mFrontView的高度  
  12.     private int mRange;//mFrontView侧拉时向左移动的最大距离,即mBackView的宽度  
  13.   
  14.     public SwipeLayout(Context context) {  
  15.         this(context, null);  
  16.     }  
  17.   
  18.     public SwipeLayout(Context context, AttributeSet attrs) {  
  19.         this(context, attrs, 0);  
  20.     }  
  21.   
  22.     public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) {  
  23.         super(context, attrs, defStyleAttr);  
  24.         init();  
  25.     }  
  26.   
  27.     //1.初始ViewDragHelper  
  28.     private void init() {  
  29.         mCallback = new ViewDragHelper.Callback() {  
  30.             //3.在回调方法中处理触摸事件  
  31.             @Override  
  32.             public boolean tryCaptureView(View child, int pointerId) {  
  33.                 return true//允许所有子控件的滑动  
  34.             }  
  35.   
  36.             //设定滑动的边界值  
  37.             @Override  
  38.             public int clampViewPositionHorizontal(View child, int left, int dx) {  
  39.                 if (child == mFrontView) {  
  40.                     //前景View的滑动范围是(0~ -mRange)  
  41.                     if (left > 0) {  
  42.                         left = 0;  
  43.                     } else if (left < -mRange) {  
  44.                         left = -mRange;  
  45.                     }  
  46.                 }  
  47.                 if (child == mBackView) {  
  48.                     //背景View的滑动范围是(mWidth - mRange ~ mWidth)  
  49.                     if (left > mWidth) {  
  50.                         left = mWidth;  
  51.                     } else if (left < (mWidth - mRange)) {  
  52.                         left = mWidth - mRange;  
  53.                     }  
  54.                 }  
  55.                 //返回修正过的建议值  
  56.                 return left;  
  57.             }  
  58.   
  59.             //监听View的滑动位置的改变,同步前景View和背景View的滑动事件  
  60.             @Override  
  61.             public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {  
  62.                 if (changedView == mFrontView) {  
  63.                     //当滑动前景View时,也需要滑动背景View  
  64.                     mBackView.offsetLeftAndRight(dx);  
  65.                 } else if (changedView == mBackView) {  
  66.                     //当滑动背景View时,也需要滑动前景View  
  67.                     mFrontView.offsetLeftAndRight(dx);  
  68.                 }  
  69.                 // 兼容老版本  
  70.                 invalidate();  
  71.             }  
  72.   
  73.             //处理释放后的开启和关闭动作  
  74.             @Override  
  75.             public void onViewReleased(View releasedChild, float xvel, float yvel) {  
  76.                 if (xvel < 0) {  
  77.                     //有向左滑动的速度,则打开  
  78.                     open();  
  79.                 } else if (xvel == 0 && mFrontView.getLeft() < -mRange / 2.0f) {  
  80.                     //前景View向左滑动的left小于背景View宽度一半的负值时,打开  
  81.                     open();  
  82.                 } else {  
  83.                     //其他情况为关闭  
  84.                     close();  
  85.                 }  
  86.             }  
  87.         };  
  88.         mDragHelper = ViewDragHelper.create(this, mCallback);  
  89.     }  
  90.   
  91.     //2.传递触摸事件  
  92.     @Override  
  93.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  94.         return mDragHelper.shouldInterceptTouchEvent(ev);  
  95.     }  
  96.   
  97.     @Override  
  98.     public boolean onTouchEvent(MotionEvent event) {  
  99.         try {  
  100.             mDragHelper.processTouchEvent(event);  
  101.         } catch (Exception e) {  
  102.             e.printStackTrace();  
  103.         }  
  104.         return true;  
  105.     }  
  106.   
  107.     //获取子控件的引用  
  108.     @Override  
  109.     protected void onFinishInflate() {  
  110.         super.onFinishInflate();  
  111.         mBackView = getChildAt(0); //获取背景View,即展示数据的Item的右边隐藏的侧滑布局  
  112.         mFrontView = getChildAt(1);//获取前景View,即展示数据的Item  
  113.     }  
  114.   
  115.     //获取子控件的相关宽高信息  
  116.     @Override  
  117.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  118.         super.onSizeChanged(w, h, oldw, oldh);  
  119.         mWidth = mFrontView.getMeasuredWidth();  
  120.         mHeight = mFrontView.getMeasuredHeight();  
  121.         mRange = mBackView.getMeasuredWidth();  
  122.     }  
  123.   
  124.     //确定子控件的初始位置  
  125.     @Override  
  126.     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
  127.         super.onLayout(changed, left, top, right, bottom);  
  128.         layoutChildView(false);  
  129.     }  
  130.   
  131.     /** 
  132.      * 放置子控件的位置 
  133.      * 
  134.      * @param isOpen 是否是打开前景View,true打开,false关闭 
  135.      */  
  136.     private void layoutChildView(boolean isOpen) {  
  137.         //计算前景View的位置,将坐标信息封装到矩形中  
  138.         Rect fontRect = computerFontViewRect(isOpen);  
  139.         //摆放前景View  
  140.         mFrontView.layout(fontRect.left, fontRect.top, fontRect.right, fontRect.bottom);  
  141.   
  142.         //摆放背景View,left坐标是前景View的right坐标  
  143.         int left = fontRect.right;  
  144.         mBackView.layout(left, 0, left + mRange, mHeight);  
  145.   
  146.         //由于上面是后摆放背景View,所以会覆盖前景View,因此需要通过下面的方式将前景View显示在前面  
  147.         bringChildToFront(mFrontView);  
  148.     }  
  149.   
  150.     /** 
  151.      * 计算前景View的坐标 
  152.      * 
  153.      * @param isOpen 是否是打开前景View 
  154.      * @return 
  155.      */  
  156.     private Rect computerFontViewRect(boolean isOpen) {  
  157.         int left = isOpen ? -mRange : 0;  
  158.         return new Rect(left, 0, left + mWidth, mHeight);  
  159.     }  
  160.   
  161.     /** 
  162.      * 打开侧边栏mBackView,默认平滑打开 
  163.      */  
  164.     public void open() {  
  165.         open(true);  
  166.     }  
  167.   
  168.     /** 
  169.      * 打开侧边栏mBackView 
  170.      * 
  171.      * @param isSmooth 是否平滑打开 
  172.      */  
  173.     public void open(boolean isSmooth) {  
  174.         if (isSmooth) {  
  175.             if (mDragHelper.smoothSlideViewTo(mFrontView, -mRange, 0)) {  
  176.                 //动画在继续  
  177.                 ViewCompat.postInvalidateOnAnimation(this);  
  178.             }  
  179.         } else {  
  180.             layoutChildView(true);  
  181.         }  
  182.     }  
  183.   
  184.     /** 
  185.      * 关闭侧边栏mBackView,默认平滑关闭 
  186.      */  
  187.     public void close() {  
  188.         close(true);  
  189.     }  
  190.   
  191.     /** 
  192.      * 关闭侧边栏mBackView 
  193.      * 
  194.      * @param isSmooth 是否平滑关闭 
  195.      */  
  196.     public void close(boolean isSmooth) {  
  197.         if (isSmooth) {  
  198.             if (mDragHelper.smoothSlideViewTo(mBackView, mWidth, 0)) {  
  199.                 //动画在继续  
  200.                 ViewCompat.postInvalidateOnAnimation(this);  
  201.             }  
  202.         } else {  
  203.             layoutChildView(false);  
  204.         }  
  205.     }  
  206.   
  207.     @Override  
  208.     public void computeScroll() {  
  209.         super.computeScroll();  
  210.         if (mDragHelper.continueSettling(true)) {  
  211.             //动画还在继续  
  212.             ViewCompat.postInvalidateOnAnimation(this);  
  213.         }  
  214.     }  
  215. }  


如何使用呢?

使用该控件,必须要让其有2个直接的子控件,如下布局所示:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <mchenys.net.csdn.blog.myswipelayout.view.SwipeLayout   
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:id="@+id/sl"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="60dp"  
  7.     android:minHeight="60dp"  
  8.     android:background="#44000000" >  
  9.     <!--后置布局-->  
  10.     <LinearLayout  
  11.         android:layout_width="wrap_content"  
  12.         android:layout_height="match_parent"  
  13.         android:orientation="horizontal" >  
  14.   
  15.         <TextView  
  16.             android:id="@+id/tv_call"  
  17.             android:layout_width="60dp"  
  18.             android:layout_height="match_parent"  
  19.             android:background="#666666"  
  20.             android:gravity="center"  
  21.             android:text="Edit"  
  22.             android:textColor="#ffffff" />  
  23.   
  24.         <TextView  
  25.             android:id="@+id/tv_del"  
  26.             android:layout_width="60dp"  
  27.             android:layout_height="match_parent"  
  28.             android:background="#ff0000"  
  29.             android:gravity="center"  
  30.             android:text="Delete"  
  31.             android:textColor="#ffffff" />  
  32.     </LinearLayout>  
  33.     <!--前景布局-->  
  34.     <LinearLayout  
  35.         android:layout_width="match_parent"  
  36.         android:layout_height="match_parent"  
  37.         android:background="#44ffffff"  
  38.         android:gravity="center_vertical"  
  39.         android:orientation="horizontal" >  
  40.   
  41.         <ImageView  
  42.             android:id="@+id/iv_image"  
  43.             android:layout_width="40dp"  
  44.             android:layout_height="40dp"  
  45.             android:layout_marginLeft="15dp"  
  46.             android:src="@drawable/head_1" />  
  47.   
  48.         <TextView  
  49.             android:id="@+id/tv_name"  
  50.             android:layout_width="wrap_content"  
  51.             android:layout_height="wrap_content"  
  52.             android:layout_marginLeft="15dp"  
  53.             android:text="Name" />  
  54.     </LinearLayout>  
  55.   
  56. </mchenys.net.csdn.blog.myswipelayout.view.SwipeLayout>  

就是这么简单,跑起来就可以用了.不过这个只是定义出了SwipeLayout控件,如果要集成到ListView中,还需要做进一步的处理.

例如实现如下效果:



需要考虑2点:

1.在自定义SwipeLayout控件内需要处理3种状态,打开,关闭,拖拽.

2.需要添加一个侧滑监听接口,用于对外暴露当前SwipeLayout的打开,关闭,拖拽,将要打开,将要关闭这5种情况.接口定义如下所示:

[java] view plain copy
  1. /** 
  2.  * 侧拉SwipeLayout的监听 
  3.  * Created by mChenys on 2015/12/26. 
  4.  */  
  5. public interface SwipeViewListener {  
  6.     //关闭  
  7.     void onClose(SwipeLayout mSwipeLayout);  
  8.   
  9.     //打开  
  10.     void onOpen(SwipeLayout mSwipeLayout);  
  11.   
  12.     //正在侧拉  
  13.     void onDraging(SwipeLayout mSwipeLayout);  
  14.   
  15.     //开始要去关闭  
  16.     void onStartClose(SwipeLayout mSwipeLayout);  
  17.   
  18.     //开始要去开启  
  19.     void onStartOpen(SwipeLayout mSwipeLayout);  
  20. }  


SwipeLayout的3种状态,用enum表示即定义接收获取SwipeViewListener监听器的方法1

[java] view plain copy
  1. //以下是定义SwipeLayout的打开,关闭,滑动的3种状态  
  2.     public enum Status {  
  3.         CLOSE, OPEN, DRAGING;  
  4.     }  
  5.     //默认关闭  
  6.     private Status mStatus = Status.CLOSE;  
  7.     //滑动的监听器  
  8.     private SwipeViewListener mSwipeViewListener;  
  9.   
  10.     //设置监听器  
  11.     public void setSwipeViewListener(SwipeViewListener swipeViewListener) {  
  12.         mSwipeViewListener = swipeViewListener;  
  13.     }  


在onViewPositionChanged方法内添加多一个方法,用于处理拖拽的监听.

[java] view plain copy
  1. /** 
  2.      * 处理滑动,打开,关闭的3种情况 
  3.      * 在onViewPositionChanged 调用 
  4.      */  
  5.     private void dispatchSwipeEvent() {  
  6.         if (mSwipeViewListener != null) {  
  7.             mSwipeViewListener.onDraging(this);  
  8.         }  
  9.         //记录上一次的状态  
  10.         Status preStatus = mStatus;  
  11.         //获取当前的状态  
  12.         mStatus = getCurrStatus();  
  13.         if (preStatus != mStatus && null != mSwipeViewListener) {  
  14.             //说明有状态发生变化  
  15.             if (mStatus == Status.CLOSE) {  
  16.                 //关闭  
  17.                 mSwipeViewListener.onClose(this);  
  18.             } else if (mStatus == Status.OPEN) {  
  19.                 //打开  
  20.                 mSwipeViewListener.onOpen(this);  
  21.             } else if (mStatus == Status.DRAGING) {  
  22.                 //这里有2中情况,要么要打开,要么要关闭  
  23.                 if (preStatus == Status.CLOSE) {  
  24.                     //如果之前是关闭的,那么就是要打开  
  25.                     mSwipeViewListener.onStartOpen(this);  
  26.                 } else if (preStatus == Status.OPEN) {  
  27.                     //如果之前是打开,那么就是要关闭  
  28.                     mSwipeViewListener.onStartClose(this);  
  29.                 }  
  30.             }  
  31.         }  
  32.     }  
  33.   
  34.     /** 
  35.      * 获取当前的状态 
  36.      * 
  37.      * @return 
  38.      */  
  39.     private Status getCurrStatus() {  
  40.         int left = mFrontView.getLeft();  
  41.         if (left == 0) {  
  42.             return Status.CLOSE;  
  43.         } else if (left == -mRange) {  
  44.             return Status.OPEN;  
  45.         }  
  46.         return Status.DRAGING;  
  47.     }  

最后来看看MainActivity的测试:

[java] view plain copy
  1. public class MainActivity extends AppCompatActivity {  
  2.     private List<String> mData = new ArrayList<>();//数据集合  
  3.   
  4.     @Override  
  5.     protected void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         //获取数据,注意:Arrays.asList返回的并不是一个java.util.ArrayList,而是一个Arrays类的内部类,该List实现是不能进行增删操作的  
  8.         //因此必须再包装一下  
  9.         mData = new ArrayList<>(Arrays.asList(Constant.NAME));  
  10.         ListView listView = new ListView(this);  
  11.         listView.setAdapter(mAdapter);  
  12.         setContentView(listView);  
  13.     }  
  14.   
  15.     //自定义适配器  
  16.     private BaseAdapter mAdapter = new BaseAdapter() {  
  17.         //标记当前打开的SwipeLayout的集合  
  18.         private List<SwipeLayout> mOpenItem = new ArrayList<>();  
  19.   
  20.         @Override  
  21.         public int getCount() {  
  22.             return mData.size();  
  23.         }  
  24.   
  25.         @Override  
  26.         public String getItem(int position) {  
  27.             return mData.get(position);  
  28.         }  
  29.   
  30.         @Override  
  31.         public long getItemId(int position) {  
  32.             return position;  
  33.         }  
  34.   
  35.         @Override  
  36.         public View getView(final int position, View convertView, ViewGroup parent) {  
  37.             ViewHolder holder = null;  
  38.             if (null == convertView) {  
  39.                 holder = new ViewHolder();  
  40.                 convertView = View.inflate(MainActivity.this, R.layout.item_list, null);  
  41.                 holder.mSwipeLayout = (SwipeLayout) convertView;  
  42.                 holder.tvName = (TextView) convertView.findViewById(R.id.tv_name);  
  43.                 holder.tvDel = (TextView) convertView.findViewById(R.id.tv_del);  
  44.                 holder.tvEdit = (TextView) convertView.findViewById(R.id.tv_edit);  
  45.                 convertView.setTag(holder);  
  46.             } else {  
  47.                 holder = (ViewHolder) convertView.getTag();  
  48.             }  
  49.             //设置侧拉监听  
  50.             holder.mSwipeLayout.setSwipeViewListener(getSwipeViewListener());  
  51.             holder.tvName.setText(getItem(position));  
  52.             holder.tvDel.setOnClickListener(new View.OnClickListener() {  
  53.                 @Override  
  54.                 public void onClick(View v) {  
  55.                     //删除  
  56.                     mData.remove(position);  
  57.                     mAdapter.notifyDataSetChanged();  
  58.                 }  
  59.             });  
  60.             holder.tvEdit.setOnClickListener(new View.OnClickListener() {  
  61.                 @Override  
  62.                 public void onClick(View v) {  
  63.                     ToastUtils.showToast(MainActivity.this,"编辑");  
  64.                 }  
  65.             });  
  66.             return convertView;  
  67.         }  
  68.   
  69.         class ViewHolder {  
  70.             TextView tvName, tvDel, tvEdit;  
  71.             SwipeLayout mSwipeLayout;  
  72.         }  
  73.   
  74.         //获取滑动监听器  
  75.         private SwipeViewListener getSwipeViewListener() {  
  76.             return new SwipeViewListener() {  
  77.                 @Override  
  78.                 public void onClose(SwipeLayout mSwipeLayout) {  
  79.                     //关闭是移除  
  80.                     mOpenItem.remove(mSwipeLayout);  
  81.                     ToastUtils.showToast(MainActivity.this"关闭");  
  82.                 }  
  83.   
  84.                 @Override  
  85.                 public void onOpen(SwipeLayout mSwipeLayout) {  
  86.                     //打开时添加  
  87.                     mOpenItem.add(mSwipeLayout);  
  88.                     ToastUtils.showToast(MainActivity.this"打开");  
  89.                 }  
  90.   
  91.                 @Override  
  92.                 public void onDraging(SwipeLayout mSwipeLayout) {  
  93.   
  94.                 }  
  95.   
  96.                 @Override  
  97.                 public void onStartClose(SwipeLayout mSwipeLayout) {  
  98.                     ToastUtils.showToast(MainActivity.this"开始关闭");  
  99.                 }  
  100.                 @Override  
  101.                 public void onStartOpen(SwipeLayout mSwipeLayout) {  
  102.                     //将要打开时,需要将集合中的之前打开的SwipeLayout统统关闭  
  103.                     for (SwipeLayout swipeLayout : mOpenItem) {  
  104.                         swipeLayout.close();  
  105.                     }  
  106.                     mOpenItem.clear();//清空集合  
  107.                     ToastUtils.showToast(MainActivity.this"开始打开");  
  108.                 }  
  109.             };  
  110.         }  
  111.     };  
  112. }  


至此就Ok了.


文章转自:http://blog.csdn.net/mChenys/article/details/50410554

阅读全文
0 0
原创粉丝点击