Android自定义View-SlideListView

移动开发 Android
在 android 中 ListView 可以说是使用最多的控件之一,ListView 有很多的用法和处理事件,比如 item 的点击和长按事件,在比较多的应用中,点击就是跳转,长按会弹出一些选择菜单等。

在 android 中 ListView 可以说是使用最多的控件之一,ListView 有很多的用法和处理事件,比如 item 的点击和长按事件,在比较多的应用中,点击就是跳转,长按会弹出一些选择菜单等。 这里我要介绍的是一个 ListView 侧滑出菜单的自定义控件

效果图如下:
正常状态

侧滑出菜单状态

 

分析

主要用到了 Scroller 这个滑动类,刚开始拦截触摸事件在 action ==MotionEvent.ACTION_DOWN的时候,根据出点获取我们点击的itemView 然后根据滑动模式(左滑动 or 右滑动)来自动获取左侧或者右侧的宽度;

在 action == MotionEvent.ACTION_MOVE 中根据移动判断是否可以侧滑,以及侧滑的方向,并使用 itemView.scrollTo(deltaX, 0); 来移动itemView ;

***在 ction == MotionEvent.ACTION_UP 中判断模式和移动的距离完成侧滑或者还原到初始状态。
实现

***步 初始化Scroller

  1. scroller = new Scroller(context);  
  2. mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 

第二步 action ==MotionEvent.ACTION_DOWN

  1. case MotionEvent.ACTION_DOWN:  
  2.                 if (this.mode == MOD_FORBID) {  
  3.                     return super.onTouchEvent(ev);  
  4.                 }  
  5.                 // 如果处于侧滑完成状态,侧滑回去,并直接返回  
  6.                 if (isSlided) {  
  7.                     scrollBack();  
  8.                     return false;  
  9.                 }  
  10.                 // 假如scroller滚动还没有结束,我们直接返回  
  11.                 if (!scroller.isFinished()) {  
  12.                     return false;  
  13.                 }  
  14.    
  15.                 downX = (int) ev.getX();  
  16.                 downY = (int) ev.getY();  
  17.    
  18.                 slidePosition = pointToPosition(downX, downY);  
  19.                 // 无效的position, 不做任何处理  
  20.                 if (slidePosition == AdapterView.INVALID_POSITION) {  
  21.                     return super.onTouchEvent(ev);  
  22.                 }  
  23.    
  24.                 // 获取我们点击的item view  
  25.                 itemView = getChildAt(slidePosition - getFirstVisiblePosition());  
  26.                 /*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/  
  27.                 if (this.mode == MOD_BOTH) {  
  28.                     this.leftLength = -itemView.getPaddingLeft();  
  29.                     this.rightLength = -itemView.getPaddingRight();  
  30.                 } else if (this.mode == MOD_LEFT) {  
  31.                     this.leftLength = -itemView.getPaddingLeft();  
  32.                 } else if (this.mode == MOD_RIGHT) {  
  33.                     this.rightLength = -itemView.getPaddingRight();  
  34.                 }  
  35.                 break; 

第三步 action == MotionEvent.ACTION_MOVE

  1. case MotionEvent.ACTION_MOVE:  
  2.                 if (!canMove  
  3.                         && slidePosition != AdapterView.INVALID_POSITION  
  4.                         && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev  
  5.                         .getY() - downY) < mTouchSlop)) {  
  6.                     if (mSwipeLayout != null)  
  7.                         mSwipeLayout.setEnabled(false);  
  8.                     int offsetX = downX - lastX;  
  9.                     if (offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) {  
  10.                         /*从右向左滑*/  
  11.                         canMove = true;  
  12.                     } else if (offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) {  
  13.                         /*从左向右滑*/  
  14.                         canMove = true;  
  15.                     } else {  
  16.                         canMove = false;  
  17.                     }  
  18.                     /*此段代码是为了避免我们在侧向滑动时同时触发ListView的OnItemClickListener时间*/  
  19.                     MotionEvent cancelEvent = MotionEvent.obtain(ev);  
  20.                     cancelEvent  
  21.                             .setAction(MotionEvent.ACTION_CANCEL  
  22.                                     | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));  
  23.                     onTouchEvent(cancelEvent);  
  24.                 }  
  25.                 if (canMove) {  
  26.                     /*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/  
  27.                     requestDisallowInterceptTouchEvent(true);  
  28.                     // 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚  
  29.                     int deltaX = downX - lastX;  
  30.                     if (deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) {  
  31.                         /*向左滑*/  
  32.                         itemView.scrollTo(deltaX, 0);  
  33.                     } else if (deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) {  
  34.                         /*向右滑*/  
  35.                         itemView.scrollTo(deltaX, 0);  
  36.                     } else {  
  37.                         itemView.scrollTo(0, 0);  
  38.                     }  
  39.                     return true;  
  40.                 }  
  41.                 break; 

第四步 action == MotionEvent.ACTION_UP

  1. case MotionEvent.ACTION_UP:  
  2.                 if (mSwipeLayout != null)  
  3.                     mSwipeLayout.setEnabled(true);  
  4.                 //requestDisallowInterceptTouchEvent(false);  
  5.                 if (canMove){  
  6.                     canMove = false;  
  7.                     scrollByDistanceX();  
  8.                 }  
  9.                 break; 

完整代码

以下是完整代码

  1. package com.jwenfeng.fastdev.view;  
  2.    
  3. import android.content.Context;  
  4. import android.support.v4.widget.SwipeRefreshLayout;  
  5. import android.util.AttributeSet;  
  6. import android.view.MotionEvent;  
  7. import android.view.View;  
  8. import android.view.ViewConfiguration;  
  9. import android.widget.AdapterView;  
  10. import android.widget.ListView;  
  11. import android.widget.Scroller;  
  12.    
  13. /**  
  14.  * 当前类注释: ListView 侧滑出菜单  
  15.  * 项目名:fastdev  
  16.  * 包名:com.jwenfeng.fastdev.view  
  17.  * 作者:jinwenfeng on 16/4/11 10:55  
  18.  * 邮箱:823546371@qq.com  
  19.  * QQ: 823546371  
  20.  * 公司:南京穆尊信息科技有限公司  
  21.  * © 2016 jinwenfeng  
  22.  * ©版权所有,未经允许不得传播  
  23.  */  
  24. public class SlideListView extends ListView {  
  25.    
  26.     /**下拉刷新view*/  
  27.     private SwipeRefreshLayout mSwipeLayout;  
  28.     /**  
  29.      * 禁止侧滑模式  
  30.      */  
  31.     public static int MOD_FORBID = 0;  
  32.     /**  
  33.      * 从左向右滑出菜单模式  
  34.      */  
  35.     public static int MOD_LEFT = 1;  
  36.     /**  
  37.      * 从右向左滑出菜单模式  
  38.      */  
  39.     public static int MOD_RIGHT = 2;  
  40.     /**  
  41.      * 左右均可以滑出菜单模式  
  42.      */  
  43.     public static int MOD_BOTH = 3;  
  44.     /**  
  45.      * 当前的模式  
  46.      */  
  47.     private int mode = MOD_FORBID;  
  48.     /**  
  49.      * 左侧菜单的长度  
  50.      */  
  51.     private int leftLength = 0;  
  52.     /**  
  53.      * 右侧菜单的长度  
  54.      */  
  55.     private int rightLength = 0;  
  56.    
  57.     /**  
  58.      * 当前滑动的ListView position  
  59.      */  
  60.     private int slidePosition;  
  61.     /**  
  62.      * 手指按下X的坐标  
  63.      */  
  64.     private int downY;  
  65.     /**  
  66.      * 手指按下Y的坐标  
  67.      */  
  68.     private int downX;  
  69.     /**  
  70.      * ListView的item  
  71.      */  
  72.     private View itemView;  
  73.     /**  
  74.      * 滑动类  
  75.      */  
  76.     private Scroller scroller;  
  77.     /**  
  78.      * 认为是用户滑动的最小距离  
  79.      */  
  80.     private int mTouchSlop;  
  81.    
  82.     /**  
  83.      * 判断是否可以侧向滑动  
  84.      */  
  85.     private boolean canMove = false;  
  86.     /**  
  87.      * 标示是否完成侧滑  
  88.      */  
  89.     private boolean isSlided = false;  
  90.    
  91.     public SlideListView(Context context) {  
  92.         this(context, null);  
  93.     }  
  94.    
  95.     public SlideListView(Context context, AttributeSet attrs) {  
  96.         this(context, attrs,0);  
  97.     }  
  98.    
  99.     public SlideListView(Context context, AttributeSet attrs, int defStyleAttr) {  
  100.         super(context, attrs, defStyleAttr);  
  101.         scroller = new Scroller(context);  
  102.         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();  
  103.     }  
  104.    
  105.     /**  
  106.      * 初始化菜单的滑出模式  
  107.      *  
  108.      * @param mode  
  109.      */  
  110.     public void initSlideMode(int mode) {  
  111.         this.mode = mode;  
  112.     }  
  113.    
  114.     /**  
  115.      * 处理我们拖动ListView item的逻辑  
  116.      */  
  117.     @Override  
  118.     public boolean onTouchEvent(MotionEvent ev) {  
  119.         final int action = ev.getAction();  
  120.         int lastX = (int) ev.getX();  
  121.    
  122.         switch (action) {  
  123.             case MotionEvent.ACTION_DOWN:  
  124.                 if (this.mode == MOD_FORBID) {  
  125.                     return super.onTouchEvent(ev);  
  126.                 }  
  127.                 // 如果处于侧滑完成状态,侧滑回去,并直接返回  
  128.                 if (isSlided) {  
  129.                     scrollBack();  
  130.                     return false;  
  131.                 }  
  132.                 // 假如scroller滚动还没有结束,我们直接返回  
  133.                 if (!scroller.isFinished()) {  
  134.                     return false;  
  135.                 }  
  136.    
  137.                 downX = (int) ev.getX();  
  138.                 downY = (int) ev.getY();  
  139.    
  140.                 slidePosition = pointToPosition(downX, downY);  
  141.                 // 无效的position, 不做任何处理  
  142.                 if (slidePosition == AdapterView.INVALID_POSITION) {  
  143.                     return super.onTouchEvent(ev);  
  144.                 }  
  145.    
  146.                 // 获取我们点击的item view  
  147.                 itemView = getChildAt(slidePosition - getFirstVisiblePosition());  
  148.                 /*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/  
  149.                 if (this.mode == MOD_BOTH) {  
  150.                     this.leftLength = -itemView.getPaddingLeft();  
  151.                     this.rightLength = -itemView.getPaddingRight();  
  152.                 } else if (this.mode == MOD_LEFT) {  
  153.                     this.leftLength = -itemView.getPaddingLeft();  
  154.                 } else if (this.mode == MOD_RIGHT) {  
  155.                     this.rightLength = -itemView.getPaddingRight();  
  156.                 }  
  157.                 break;  
  158.             case MotionEvent.ACTION_MOVE:  
  159.                 if (!canMove  
  160.                         && slidePosition != AdapterView.INVALID_POSITION  
  161.                         && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev  
  162.                         .getY() - downY) < mTouchSlop)) {  
  163.                     if (mSwipeLayout != null)  
  164.                         mSwipeLayout.setEnabled(false);  
  165.                     int offsetX = downX - lastX;  
  166.                     if (offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) {  
  167.                         /*从右向左滑*/  
  168.                         canMove = true;  
  169.                     } else if (offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) {  
  170.                         /*从左向右滑*/  
  171.                         canMove = true;  
  172.                     } else {  
  173.                         canMove = false;  
  174.                     }  
  175.                     /*此段代码是为了避免我们在侧向滑动时同时触发ListView的OnItemClickListener时间*/  
  176.                     MotionEvent cancelEvent = MotionEvent.obtain(ev);  
  177.                     cancelEvent  
  178.                             .setAction(MotionEvent.ACTION_CANCEL  
  179.                                     | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));  
  180.                     onTouchEvent(cancelEvent);  
  181.                 }  
  182.                 if (canMove) {  
  183.                     /*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/  
  184.                     requestDisallowInterceptTouchEvent(true);  
  185.                     // 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚  
  186.                     int deltaX = downX - lastX;  
  187.                     if (deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)) {  
  188.                         /*向左滑*/  
  189.                         itemView.scrollTo(deltaX, 0);  
  190.                     } else if (deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)) {  
  191.                         /*向右滑*/  
  192.                         itemView.scrollTo(deltaX, 0);  
  193.                     } else {  
  194.                         itemView.scrollTo(0, 0);  
  195.                     }  
  196.                     return true;  
  197.                 }  
  198.                 break;  
  199.    
  200.             case MotionEvent.ACTION_UP:  
  201.                 if (mSwipeLayout != null)  
  202.                     mSwipeLayout.setEnabled(true);  
  203.                 //requestDisallowInterceptTouchEvent(false);  
  204.                 if (canMove){  
  205.                     canMove = false;  
  206.                     scrollByDistanceX();  
  207.                 }  
  208.                 break;  
  209.         }  
  210.    
  211.         return super.onTouchEvent(ev);  
  212.     }  
  213.    
  214.     private void scrollByDistanceX() {  
  215.         if(this.mode == MOD_FORBID){  
  216.             return;  
  217.         }  
  218.         if(itemView.getScrollX() > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){  
  219.             /*从右向左滑*/  
  220.             if (itemView.getScrollX() >= rightLength / 2) {  
  221.                 scrollLeft();  
  222.             }  else {  
  223.                 // 滚回到原始位置  
  224.                 scrollBack();  
  225.             }  
  226.         }else if(itemView.getScrollX() < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){  
  227.             /*从左向右滑*/  
  228.             if (itemView.getScrollX() <= -leftLength / 2) {  
  229.                 scrollRight();  
  230.             } else {  
  231.                 // 滚回到原始位置  
  232.                 scrollBack();  
  233.             }  
  234.         }else{  
  235.             // 滚回到原始位置  
  236.             scrollBack();  
  237.         }  
  238.     }  
  239.    
  240.     /**  
  241.      * 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值  
  242.      */  
  243.     private void scrollRight() {  
  244.         isSlided = true;  
  245.         final int delta = (leftLength + itemView.getScrollX());  
  246.         // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item  
  247.         scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,  
  248.                 Math.abs(delta));  
  249.         postInvalidate(); // 刷新itemView  
  250.     }  
  251.    
  252.     /**  
  253.      * 向左滑动,根据上面我们知道向左滑动为正值  
  254.      */  
  255.     private void scrollLeft() {  
  256.         isSlided = true;  
  257.         final int delta = (rightLength - itemView.getScrollX());  
  258.         // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item  
  259.         scroller.startScroll(itemView.getScrollX(), 0, delta, 0,  
  260.                 Math.abs(delta));  
  261.         postInvalidate(); // 刷新itemView  
  262.    
  263.     }  
  264.    
  265.     private void scrollBack() {  
  266.         isSlided = false;  
  267.         scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(),  
  268.                 0, Math.abs(itemView.getScrollX()));  
  269.         postInvalidate(); // 刷新itemView  
  270.     }  
  271.    
  272.     @Override  
  273.     public void computeScroll() {  
  274.         // 调用startScroll的时候scroller.computeScrollOffset()返回true,  
  275.         if (scroller.computeScrollOffset()) {  
  276.             // 让ListView item根据当前的滚动偏移量进行滚动  
  277.             itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());  
  278.             postInvalidate();  
  279.         }  
  280.     }  
  281.    
  282.     /**  
  283.      * 提供给外部调用,用以将侧滑出来的滑回去  
  284.      */  
  285.     public void slideBack() {  
  286.         this.scrollBack();  
  287.     }  
  288.    
  289.     public void setSwipeLayout(SwipeRefreshLayout mSwipeLayout) {  
  290.         this.mSwipeLayout = mSwipeLayout;  
  291.     }  
责任编辑:陈琳 来源: 晓风残月的博客
相关推荐

2016-11-16 21:55:55

源码分析自定义view androi

2016-12-26 15:25:59

Android自定义View

2017-03-02 13:33:19

Android自定义View

2012-05-18 10:52:20

TitaniumAndroid模块自定义View模块

2013-05-20 17:33:44

Android游戏开发自定义View

2013-01-06 10:43:54

Android开发View特效

2017-03-14 15:09:18

AndroidView圆形进度条

2011-08-02 11:17:13

iOS开发 View

2021-10-26 10:07:02

鸿蒙HarmonyOS应用

2013-04-01 14:35:10

Android开发Android自定义x

2010-02-07 14:02:16

Android 界面

2017-05-18 12:36:16

android万能适配器列表视图

2017-05-19 10:03:31

AndroidBaseAdapter实践

2015-02-12 15:33:43

微信SDK

2015-02-11 17:49:35

Android源码自定义控件

2013-05-02 14:08:18

2014-12-10 10:37:45

Android自定义布局

2011-08-18 17:32:55

iPhone开发Table Cell

2013-01-09 17:22:38

Android开发Camera

2015-02-12 15:38:26

微信SDK
点赞
收藏

51CTO技术栈公众号