zoukankan      html  css  js  c++  java
  • ListView列表拖拽排序

    ListView列表拖拽排序能够參考Android源代码下的Music播放列表,他是能够拖拽的,源代码在[packages/apps/Music下的TouchInterceptor.java下]。
    首先是搭建框架,此处的ListView列表相似于QQ消息列表,当然数据不过模拟,为了简单起见,没有把ListView的条目的所有的属性所有写上。首先是消息的实体类Msg.java:

    package me.chenfuduo.mymsgdrag;
    
    public class Msg {
    
        private int ivId;
    
        private String text;
    
        public Msg() {
    
        }
    
        public Msg(int ivId, String text) {
            this.ivId = ivId;
            this.text = text;
        }
    
        public int getIvId() {
            return ivId;
        }
    
    
        public String getText() {
            return text;
        }
    
    
    
    }

    然后是数据列表的每一个Item的布局item.xml:

    <?

    xml version="1.0" encoding="utf-8"?

    > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentLeft="true" android:layout_marginLeft="10dp" android:layout_centerInParent="true" android:id="@+id/imageView" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/textView" android:layout_toRightOf="@id/imageView" android:layout_centerInParent="true" android:layout_marginLeft="10dp" /> </RelativeLayout>

    如今能够新建一个MsgAdapter适配器类,让其继承自ArrayAdapter,并实现其构造方法和重写getView()方法。

    package me.chenfuduo.mymsgdrag;
    
    import java.util.List;
    
    
    import android.content.Context;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ArrayAdapter;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    public class MsgAdapter extends ArrayAdapter<Msg> {
    
        public MsgAdapter(Context context, List<Msg> msgList) {
            super(context, 0, msgList);
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view;
            ViewHolder viewHolder;
            if (convertView == null) {
                view = View.inflate(getContext(), R.layout.item, null);
                viewHolder = new ViewHolder();
                viewHolder.imageView = (ImageView) view
                        .findViewById(R.id.imageView);
                viewHolder.textView = (TextView) view.findViewById(R.id.textView);
                view.setTag(viewHolder);
            } else {
                view = convertView;
                viewHolder = (ViewHolder) view.getTag();
            }
    
            viewHolder.imageView.setImageResource(getItem(position).getIvId());
            viewHolder.textView.setText(getItem(position).getText());
            return view;
        }
    
        static class ViewHolder {
            ImageView imageView;
            TextView textView;
        }
    
    }

    主界面MainActivity设置适配器:

    package me.chenfuduo.mymsgdrag;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class MainActivity extends Activity {
    
        private MyDragListView list;
    
        private List<Msg> msgList;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            list = (MyDragListView) findViewById(R.id.list);
            msgList = new ArrayList<Msg>();
            initData();
            list.setAdapter(new MsgAdapter(this, msgList));
        }
    
        private void initData() {
            for (int i = 0; i < 30; i++) {
                msgList.add(new Msg(R.drawable.ic_launcher, "new item" + i));
            }
    
        }
    
    }

    这里提到的MyDragListView就是以下我们要着重介绍的自己定义的ListView,先无论。
    OK,如今运行,数据所有展示在ListView上了。以下開始新建一个类MyDragListView,并让其继承自ListView。提供三个构造方法。

    public MyDragListView(Context context) {
            this(context, null);
        }
    
        public MyDragListView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public MyDragListView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

    接下来。须要重写onInterceptTouchEvent()拦截事件的方法,为了能在子控件响应触摸事件的情况下此ListView也能监听到触摸事件,须要重写此方法,做一些初始化的工作,在这里捕获ACTION_DOWN事件。在ACTION_DOWN事件中,做一些拖动的准备工作。

    • 获取PV数据项,初始化一些变量(pointToPosition)
    • 推断是否是拖动还是不过点击
    • 假设是拖动,建立拖动影像()

    以上都是后面拖动的基础。
    那首先定义我们须要的一些变量:

        // 原始条目位置
        private int dragSrcPosition;
        // 目标条目位置
        private int dragDestPosition;
        //在当前数据项中的位置
        private int dragPoint;
        //拖动的时候,開始向上滚动的边界
        private int upScrollBounce;
        //拖动的时候,開始向下滚动的边界
        private int downScrollBounce;
        //窗口控制类
        private WindowManager windowManager;
        //用于控制拖拽项显示的參数
        private WindowManager.LayoutParams windowParams;
        //当前视图和屏幕的距离(这里只使用了y轴上的)
        private int dragOffset;
    
        // 推断滑动的一个距离,scroll的时候会用到
        private int scaledTouchSlop;
        // 被拖拽项的影像。事实上就是一个ImageView。在我们这里是"用户头像"
        private ImageView dragImageView;

    我们在构造器中获取滑动的距离:

    public MyDragListView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }

    注意获取这个系统所能识别出的被觉得是滑动的最小距离的方式。

    getScaledTouchSlop()是一个距离,表示滑动的时候,手的移动要大于这个距离才開始移动控件。

    假设小于这个距离就不触发移动控件。

    接下来,在onInterceptTouchEvent()捕获的ACTION_DOWN事件中,做处理。
    还是依照上面的来,第一部须要得到选中的数据项的位置,这里使用pointToPosition(x,y)就可以。假设想要測试这个api。也非常easy,以下是实例代码:

     mListView.setOnTouchListener(new OnTouchListener() {  
                @Override  
                public boolean onTouch(View v, MotionEvent event) {  
                    int item=mListView.pointToPosition((int) event.getX(), (int) event.getY());  
                    System.out.println("---> 如今点击了ListView中第"+(item+1)+"个Item");  
                    return true;  
                }  
            });  

    ok,在我们这里是这样:

    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    
                // 触点所在的条目的位置
    
                int x = (int) ev.getX();
                int y = (int) ev.getY();
                dragSrcPosition = dragDestPosition = pointToPosition(x, y);
    
                //假设是无效位置(超出边界,切割线等位置)。返回
                     if(dragDestPosition==AdapterView.INVALID_POSITION){
                            return super.onInterceptTouchEvent(ev);
                     }
    
    }

    如今我们要获取ListView的单个Item,由于获取了这个单个的Item,才干获取Item里面的”头像”(姑且这么叫),ok,代码例如以下:

                //这里假设不减去,会报空指针异常
                ViewGroup itemView = (ViewGroup) getChildAt(dragSrcPosition
                        - getFirstVisiblePosition());

    在这里我当时遇到一个NPE的问题。就是当ListView滚动到以下的时候,我选择以下的Item。报错了。归根究竟,还是没有理解好getChildAt(i)这种方法。

    这里。我參考了以下的资料去理解的。
    ListView中getChildAt(index)的使用注意事项
    通过getChildAt方法取得AdapterView中第n个Item
    stackover:ListView getChildAt returning null for visible children
    说究竟,getChildAt(i)是获取可见视图的。
    接下来,就能够获取”用户头像”了:

                // 图标
                View dragger = itemView.findViewById(R.id.imageView);

    以下须要推断手指的触点是不是在logo(”用户头像”)范围内:

            if (dragger != null && x < dragger.getRight() + 10) {
    
                    upScrollBounce = Math.min(y - scaledTouchSlop, getHeight() / 3);
                    downScrollBounce = Math.max(y + scaledTouchSlop,
                            getHeight() * 2 / 3);
    }

    这个非常好理解。


    接着便能够获取选中条目的图片了。

                    itemView.setDrawingCacheEnabled(true);
                    Bitmap bitmap = itemView.getDrawingCache();
                    startDrag(bitmap, y);
                    itemView.setDrawingCacheEnabled(false);

    这里。又学到一招,将View转化为Bitmap,相关的api:

    • setDrawingCacheEnabled(boolean)注意最后须要设置其为false
    • getDrawingCache()

    那么如今就能够拖动了。

    startDrag(bitmap, y);

    最后,我们返回false,让事件能够传递到子控件。
    总体的代码入下:

    @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
    
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    
                // 触点所在的条目的位置
    
                int x = (int) ev.getX();
                int y = (int) ev.getY();
                dragSrcPosition = dragDestPosition = pointToPosition(x, y);
    
                // 假设是无效位置(超出边界,切割线等位置),返回
                if (dragDestPosition == AdapterView.INVALID_POSITION) {
                    return super.onInterceptTouchEvent(ev);
                }
    
    
                //这里假设不减去
                ViewGroup itemView = (ViewGroup) getChildAt(dragSrcPosition
                        - getFirstVisiblePosition());
    
                // 手指在条目中的相对y坐标
    
                dragPoint = y - itemView.getTop();
    
                /*Log.e("Test", "dragPoint:" + dragPoint + "
    " + "y:"+ y
                        + "
    " + "itemView.getTop():" + itemView.getTop());*/
    
                dragOffset = (int) (ev.getRawY() - y);
    
                /*Log.e("Test", "dragOffset:" + dragPoint + "
    " + "y:"+ y
                        + "
    " + "ev.getRawY():" + ev.getRawY());*/
    
                // 图标
                View dragger = itemView.findViewById(R.id.imageView);
                // 推断触点是否在logo的区域
    
                if (dragger != null && x < dragger.getRight() + 10) {
    
                    upScrollBounce = Math.min(y - scaledTouchSlop, getHeight() / 3);
                    downScrollBounce = Math.max(y + scaledTouchSlop,
                            getHeight() * 2 / 3);
    
                    // 获取选中条目的图片
    
                    itemView.setDrawingCacheEnabled(true);
                    Bitmap bitmap = itemView.getDrawingCache();
                    itemView.setDrawingCacheEnabled(false);
                    startDrag(bitmap, y);
    
                }
    
                // 能够传递到子控件
                return false;
    
            }
    
            return super.onInterceptTouchEvent(ev);
        }

    在上面有获取坐标的getRawY()等等,假设不清楚。能够看下这个文章。


    android MotionEvent中getX()和getRawX()的差别
    接下来是拖拽的方法startDrag(bitmap, y);:

    private void startDrag(Bitmap bitmap, int y) {
    
            // 释放影像,在准备影像的时候。防止影像没释放,每次都运行一下
            stopDrag();
    
            windowManager = (WindowManager) getContext().getSystemService(
                    Context.WINDOW_SERVICE);
    
            // 窗口參数配置
    
            windowParams = new WindowManager.LayoutParams();
    
            windowParams.gravity = Gravity.TOP;
    
            windowParams.x = 0;
    
            // 图片在屏幕上的绝对坐标
    
            windowParams.y = y - dragPoint + dragOffset;
    
            /*Log.e("Test", "windowParams.y:" + windowParams.y + "
    " + "y:"+ y
                    + "
    " + "dragPoint:" + dragPoint + 
                    "
    " + "dragOffset" + 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(bitmap);
            windowManager = (WindowManager) getContext().getSystemService("window");
            windowManager.addView(imageView, windowParams);
            // 把影像ImageView引用到变量drawImageView,用于兴许操作(拖动,释放等等)
    
            dragImageView = imageView;
        }

    假设做过自己定义Toast,对上面的代码不会陌生。
    不做解释,接着重写boolean onTouchEvent(MotionEvent ev):

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            // 假设dragImageView为空。说明拦截事件中已经判定不过点击,不是拖动,返回
            // 假设点击的是无效位置,返回,须要又一次推断
            if (dragImageView != null && dragDestPosition != INVALID_POSITION) {
                int action = ev.getAction();
                switch (action) {
                case MotionEvent.ACTION_UP:
                    int upY = (int) ev.getY();
                    // 释放拖动影像
                    stopDrag();
                    // 放下后。推断位置,实现对应的位置删除和插入
                    onDrop(upY);
                    break;
                case MotionEvent.ACTION_MOVE:
                    int moveY = (int) ev.getY();
                    // 拖动影像
                    onDrag(moveY);
                    break;
                default:
                    break;
                }
                return true;
            }
            // 这个返回值能够实现selected的选中效果,假设返回true则无选中效果
            return super.onTouchEvent(ev);
        }

    首先得推断是点击还是拖动,直接通过dragImageView就可以推断。假设dragImageView为空,说明拦截事件中已经判定不过点击,不是拖动。返回。接着分析拖动的方法onDrag(moveY);:
    拖动的时候,当前拖动的条目的透明度让其有所变化,然后是位置在不断更新,其次须要推断位置是否合法。最后是滚动。

    private void onDrag(int y) {
            if (dragImageView != null) {
                windowParams.alpha = 0.8f;
                windowParams.y = y - dragPoint + dragOffset;
                windowManager.updateViewLayout(dragImageView, windowParams);
            }
            // 为了避免滑动到切割线的时候,返回-1的问题
            int tempPosition = pointToPosition(0, y);
            if (tempPosition != INVALID_POSITION) {
                dragDestPosition = tempPosition;
            }
    
            // 滚动
            int scrollHeight = 0;
            if (y < upScrollBounce) {
                scrollHeight = 8;// 定义向上滚动8个像素,假设能够向上滚动的话
            } else if (y > downScrollBounce) {
                scrollHeight = -8;// 定义向下滚动8个像素,。假设能够向上滚动的话
            }
    
            if (scrollHeight != 0) {
                // 真正滚动的方法setSelectionFromTop()
                setSelectionFromTop(dragDestPosition,
                        getChildAt(dragDestPosition - getFirstVisiblePosition())
                                .getTop() + scrollHeight);
            }
        }

    这里的ViewManager.updateViewLayout(View arg0, LayoutParams arg1)会使得view所引用的实例使用params又一次绘制自己。
    接下来介绍下ListView的setSelectionFromTop(...)setSelection(...)方法。
    看一下setSelectionFromTop()的详细实现,代码例如以下:

    /** 
     * Sets the selected item and positions the selection y pixels from the top edge 
     * of the ListView. (If in touch mode, the item will not be selected but it will 
     * still be positioned appropriately.) 
     * 
     * @param position Index (starting at 0) of the data item to be selected. 
     * @param y The distance from the top edge of the ListView (plus padding) that the 
     *        item will be positioned. 
     */  
    public void setSelectionFromTop(int position, int y) {  
        if (mAdapter == null) {  
            return;  
        }  
    
        if (!isInTouchMode()) {  
            position = lookForSelectablePosition(position, true);  
            if (position >= 0) {  
                setNextSelectedPositionInt(position);  
            }  
        } else {  
            mResurrectToPosition = position;  
        }  
    
        if (position >= 0) {  
            mLayoutMode = LAYOUT_SPECIFIC;  
            mSpecificTop = mListPadding.top + y;  
    
            if (mNeedSync) {  
                mSyncPosition = position;  
                mSyncRowId = mAdapter.getItemId(position);  
            }  
    
            requestLayout();  
        }  
    }  

    从上面的代码能够得知,setSelectionFromTop()的作用是设置ListView选中的位置。同一时候在Y轴设置一个偏移量(padding值)。
    ListView另一个方法叫setSelection(),传入一个index整型数值,就能够让ListView定位到指定Item的位置。


    这两个方法有什么差别呢?看一下setSelection()的详细实现。代码例如以下:

    /** 
     * Sets the currently selected item. If in touch mode, the item will not be selected 
     * but it will still be positioned appropriately. If the specified selection position 
     * is less than 0, then the item at position 0 will be selected. 
     * 
     * @param position Index (starting at 0) of the data item to be selected. 
     */  
    @Override  
    public void setSelection(int position) {  
        setSelectionFromTop(position, 0);  
    }  

    原来。setSelection()内部就是调用了setSelectionFromTop(),只不过是Y轴的偏移量是0而已。
    Ok,当手指抬起来的时候。须要停止拖动:

        private void stopDrag() {
            if (dragImageView != null) {
                windowManager.removeView(dragImageView);
                dragImageView = null;
            }
    
        }

    最后得将Item放到正确的位置:

    private void onDrop(int y) {
            // 获取放下位置在数据集合中position
            // 定义暂时位置变量为了避免滑动到切割线的时候,返回-1的问题,假设为-1,则不改动dragPosition的值,急需运行。达到跳过无效位置的效果
            int tempPosition = pointToPosition(0, y);
            if (tempPosition != INVALID_POSITION) {
                dragDestPosition = tempPosition;
            }
    
            // 超出边界处理
            if (y < getChildAt(1).getTop()) {
                // 超出上边界,设为最小值位置0
                dragDestPosition = 0;
            } else if (y > getChildAt(getChildCount() - 1).getTop()) {
                // 超出下边界。设为最大值位置,注意哦,假设大于可视界面中最大的View的底部则是越下界,所以推断中用getChildCount()方法
                // 可是最后一项在数据集合中的position是getAdapter().getCount()-1。这点要区分清除
                dragDestPosition = getAdapter().getCount() - 1;
            }
    
            // 数据更新
            if (dragDestPosition >= 0 && dragDestPosition < getAdapter().getCount()) {
                MsgAdapter adapter = (MsgAdapter) getAdapter();
                Msg dragItem = adapter.getItem(dragSrcPosition);
    
                // 删除原位置数据项
                adapter.remove(dragItem);
                // 在新位置插入拖动项
                adapter.insert(dragItem, dragDestPosition);
            }
        }
  • 相关阅读:
    使用Aspose.Cell控件实现Excel高难度报表的生成(三)
    使用Aspose.Cell控件实现Excel高难度报表的生成(二)
    使用Aspose.Cell控件实现Excel高难度报表的生成(一)
    利用Aspose.Word控件和Aspose.Cell控件,实现Word文档和Excel文档的模板化导出
    新中新身份证阅读器不显示图片
    spring security 3 自定义认证,授权示例
    Spring Security教程
    Spring Security3实现,权限动态获取
    mybatis 做 insert操作的时候返回插入的那条数据的id
    如何在spring中获取request对象
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5245040.html
Copyright © 2011-2022 走看看