zoukankan      html  css  js  c++  java
  • Android学习系列--App列表之拖拽ListView(上)

    研究了很久的拖拽ListView的实现,受益良多,特此与尔共飨。
          鉴于这部分内容网上的资料少而简陋,而具体的实现过程或许对大家才有帮助,为了详尽而不失真,我们一步一步分析,分成两篇文章。 

    一、准备。

    1.需求问题
          初步:实现列表的拖拽效果(可参考Android源码下packages/apps/Music中的播放列表TouchInterceptor.java源码)。
                   (提前说明一下,本文不是完全按照Music中实现的,代码实现方式做了一些调整,去掉来很多无关的东西,方便大家理解,效果上也修改成了另外一种 个人认为是更简单更高效的一套。)
          拓展:借鉴上一篇文章Android学习系列(9)--App列表之分组ListView,实现分组列表的拖拽效果。
          下面以初步实现为例子,逐步展开实现步骤。

    2.搭建主界面DragListActivity.java和主布局drag_list_activity.xml。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class DragListActivity extends Activity {
         
        //数据列表
        private List<String> list = null;
         
        //数据适配器
        private DragListAdapter adapter = null;
         
        //存放分组标签
        public static List<String> groupKey= new ArrayList<String>();
        //分组一
        private List<String> navList = new ArrayList<String>();
        //分组二
        private List<String> moreList = new ArrayList<String>();
         
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.drag_list_activity);
             
            //初始化样本数据
            initData();
             
            //后面会介绍DragListView
            DragListView dragListView = (DragListView)findViewById(R.id.drag_list);
            adapter = new DragListAdapter(this, list);
            dragListView.setAdapter(adapter);
        }
    }

    3.列表项的布局drag_list_item.xml。 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="utf-8"?>
    <!-- 强调一点,使用相对布局 -->
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <TextView
           android:id="@+id/drag_list_item_text" 
           android:layout_width="wrap_content" 
           android:layout_height="@dimen/drag_item_normal_height"
           android:paddingLeft="5dip"
           android:layout_alignParentLeft="true"
           android:layout_centerVertical="true"
           android:gravity="center_vertical"/>
        <ImageView android:id="@+id/drag_list_item_image"
           android:src="@drawable/list_icon"
           android:layout_alignParentRight="true"
           android:layout_centerVertical="true"
           android:layout_width="wrap_content"
           android:layout_height="@dimen/drag_item_normal_height"/>
    </RelativeLayout>

    4.准备样本数据。
          我已经准备好了两组数据,在前面提到的initData()方法中执行初始化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public void initData(){
        //数据结果
        list = new ArrayList<String>();
         
        //groupKey存放的是分组标签
        groupKey.add("A组");
        groupKey.add("B组");
         
        for(int i=0; i<5; i++){
            navList.add("A选项"+i);
        }
        list.add("A组");
        list.addAll(navList);
         
        for(int i=0; i<8; i++){
            moreList.add("B选项"+i);
        }
        list.add("B组");
        list.addAll(moreList);
    }

          这里定义了分组标签集合groupKey后面分组的时候会用到。

    5.自定义适配器类DragListAdapter。
          接着我们搭建数据适配器,负责把list的数据填充到ListView中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static class DragListAdapter extends ArrayAdapter<String>{
        public DragListAdapter(Context context, List<String> objects) {
            super(context, 0, objects);
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
            if(view==null){
                //加载列表项模板
                view = LayoutInflater.from(getContext()).inflate(R.layout.drag_list_item, null);
            }            
            TextView textView = (TextView)view.findViewById(R.id.drag_list_item_text);
            textView.setText(getItem(position));
            return view;
        }
    }

           注意getItem(position)会取得数组适配器中position位置的T(这里是字符串),比较好用的一个方法。

           至此,我们准备了一个正常的数据列表,效果如下:
     

    二、实现

          上面部分是我们的一个 准备工作,接下来我们通过自定义ListView,重写ListView中onInterceptTouchEvent(),onTouchEvent()方法来响应触控事件做相应的界面调整(选中,拖动,数据更改后刷新界面)等等。

    6.自定义视图类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //自定义ListView,准备改造成自己想要的ListView
    //这样的好处是我们不仅可以直接使用ListView很多现成的稳定的方法,而且可以重写方法改写ListView的行为(利用的是java面向对象的继承特性,本人喜欢在任何代码中分析面向对象的特性、原则和模式)
    public class DragListView extends ListView {
     
        private int scaledTouchSlop;//判断滑动的一个距离,scroll的时候会用到
        public DragListView(Context context, AttributeSet attrs) {
            super(context, attrs);
            scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }
     
    }

    7.重写触控拦截事件方法onInterceptTouchEvent()。
           为了能在子控件响应触摸事件的情况下此ListView也能监听到触摸事件,我们把重写这个方法,做一些初始化工作。我们在这里捕获down事件,在down事件中,我们做一些拖动的准备工作:
          1)获取点击数据项,初始化一些变量;
          2)判断是否是拖动还是仅仅点击;
          3)如果是拖动,建立拖动影像;
           这些工作是我们后面拖动的一个执行基础,非常重要。 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    //下面定义要使用的所有变量
    private ImageView dragImageView;//被拖拽项的影像,其实就是一个ImageView
    private int dragSrcPosition;//手指拖动项原始在列表中的位置
    private int dragPosition;//手指拖动的时候,当前拖动项在列表中的位置
     
    private int dragPoint;//在当前数据项中的位置
    private int dragOffset;//当前视图和屏幕的距离(这里只使用了y方向上)
     
    private WindowManager windowManager;//windows窗口控制类
    private WindowManager.LayoutParams windowParams;//用于控制拖拽项的显示的参数
     
    private int scaledTouchSlop;//判断滑动的一个距离
    private int upScrollBounce;//拖动的时候,开始向上滚动的边界
    private int downScrollBounce;//拖动的时候,开始向下滚动的边界
     
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
            //捕获down事件
            if(ev.getAction()==MotionEvent.ACTION_DOWN){
            int x = (int)ev.getX();
            int y = (int)ev.getY();
             
            //选中的数据项位置,使用ListView自带的pointToPosition(x, y)方法
            dragSrcPosition = dragPosition = pointToPosition(x, y);
            //如果是无效位置(超出边界,分割线等位置),返回
            if(dragPosition==AdapterView.INVALID_POSITION){
                return super.onInterceptTouchEvent(ev);
            }
     
            //获取选中项View
            //getChildAt(int position)显示display在界面的position位置的View
            //getFirstVisiblePosition()返回第一个display在界面的view在adapter的位置position,可能是0,也可能是4
            ViewGroup itemView = (ViewGroup) getChildAt(dragPosition-getFirstVisiblePosition());
             
            //dragPoint点击位置在点击View内的相对位置
            //dragOffset屏幕位置和当前ListView位置的偏移量,这里只用到y坐标上的值
            //这两个参数用于后面拖动的开始位置和移动位置的计算
            dragPoint = y - itemView.getTop();
            dragOffset = (int) (ev.getRawY() - y);
             
            //获取右边的拖动图标,这个对后面分组拖拽有妙用
            View dragger = itemView.findViewById(R.id.drag_list_item_image);
            //如果在右边位置(拖拽图片左边的20px的右边区域)
            if(dragger!=null&&x>dragger.getLeft()-20){
                //准备拖动
                //初始化拖动时滚动变量
                //scaledTouchSlop定义了拖动的偏差位(一般+-10)
                //upScrollBounce当在屏幕的上部(上面1/3区域)或者更上的区域,执行拖动的边界,downScrollBounce同理定义
                upScrollBounce = Math.min(y-scaledTouchSlop, getHeight()/3);
                downScrollBounce = Math.max(y+scaledTouchSlop, getHeight()*2/3);
                 
                //设置Drawingcache为true,获得选中项的影像bm,就是后面我们拖动的哪个头像
                itemView.setDrawingCacheEnabled(true);
                Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());
                 
                //准备拖动影像(把影像加入到当前窗口,并没有拖动,拖动操作我们放在onTouchEvent()的move中执行)
                startDrag(bm, y);
            }
            return false;
         }
         return super.onInterceptTouchEvent(ev);
    }

          看到上面的一大堆变量和操作,你可能有些眼花缭乱,在后面使用的时候回头再去理解也可。
          开始拖动影像startDrag()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
     * 准备拖动,初始化拖动项的图像
     * @param bm
     * @param y
     */
    public void startDrag(Bitmap bm ,int y){
       //释放影像,在准备影像的时候,防止影像没释放,每次都执行一下 
       stopDrag();
         
        windowParams = new WindowManager.LayoutParams();
        //从上到下计算y方向上的相对位置,
        windowParams.gravity = Gravity.TOP;
        windowParams.x = 0;
        windowParams.y = y - dragPoint + dragOffset;
        windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        //下面这些参数能够帮助准确定位到选中项点击位置,照抄即可
        windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        windowParams.format = PixelFormat.TRANSLUCENT;
        windowParams.windowAnimations = 0;
     
        //把影像ImagView添加到当前视图中
        ImageView imageView = new ImageView(getContext());
        imageView.setImageBitmap(bm);
        windowManager = (WindowManager)getContext().getSystemService("window");
        windowManager.addView(imageView, windowParams);
        //把影像ImageView引用到变量drawImageView,用于后续操作(拖动,释放等等)
        dragImageView = imageView;
    }

           stopDrag()方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
     * 停止拖动,去除拖动项的头像
     */
    public void stopDrag(){
        if(dragImageView!=null){
            windowManager.removeView(dragImageView);
            dragImageView = null;
        }
    }

            运行看看,我们点击一项的时候没有什么反应,但是细心观察的话,其实点击项上有一层淡淡的重影,这就是我们定义的点击项的影像,后面要做的就是拖动这个影像,放下影像以及放下位置的数据项插入和原数据项位置的删除,这部分内容以及后续扩展的内容我们放到下篇中继续分析并实现。
            未完待续... 

  • 相关阅读:
    每日一水 POJ8道水题
    编译和使用 MySQL C++ Connector
    j2ee model1模型完成分页逻辑的实现 详解!
    DB查询分析器访问EXCEL时,要在表名前后加上中括弧或双引号
    指向结构体变量的指针
    EOSS V3.0 企业运营支撑系统(基于RBAC原理的权限管理)
    MybatisGen1.0 Mybatis JavaBean Mapper生成工具
    The table name must be enclosed in double quotation marks or sqare bracket while accessing EXCEL by
    资源-Android:Android
    软件-开发软件:Android Studio
  • 原文地址:https://www.cnblogs.com/vijozsoft/p/4836338.html
Copyright © 2011-2022 走看看