|
| 1 | +package com.tianxia.lib.baseworld.widget; |
| 2 | + |
| 3 | +import android.content.Context; |
| 4 | + |
| 5 | +import android.graphics.Bitmap; |
| 6 | +import android.graphics.PixelFormat; |
| 7 | + |
| 8 | +import android.util.AttributeSet; |
| 9 | + |
| 10 | +import android.view.Gravity; |
| 11 | +import android.view.LayoutInflater; |
| 12 | +import android.view.MotionEvent; |
| 13 | +import android.view.View; |
| 14 | +import android.view.ViewConfiguration; |
| 15 | +import android.view.ViewGroup; |
| 16 | +import android.view.WindowManager; |
| 17 | + |
| 18 | +import android.widget.Adapter; |
| 19 | +import android.widget.AdapterView; |
| 20 | +import android.widget.ArrayAdapter; |
| 21 | +import android.widget.ImageView; |
| 22 | +import android.widget.ListView; |
| 23 | +import android.widget.SimpleAdapter; |
| 24 | +import android.widget.TextView; |
| 25 | + |
| 26 | +import com.tianxia.lib.baseworld.R; |
| 27 | + |
| 28 | +import java.util.List; |
| 29 | + |
| 30 | +/** |
| 31 | + * 拖拽ListView |
| 32 | + */ |
| 33 | +public class DragListView extends ListView { |
| 34 | + |
| 35 | + private int mScaledTouchSlop;//判断滑动的一个距离,scroll的时候会用到 |
| 36 | + |
| 37 | + private ImageView mDragImageView;//被拖拽项的影像,其实就是一个ImageView |
| 38 | + private int mDragSrcPosition;//手指拖动项原始在列表中的位置 |
| 39 | + private int mDragPosition;//手指拖动的时候,当前拖动项在列表中的位置 |
| 40 | + |
| 41 | + private int mDragPoint;//在当前数据项中的位置 |
| 42 | + private int mDragOffset;//当前视图和屏幕的距离(这里只使用了y方向上) |
| 43 | + |
| 44 | + private WindowManager mWindowManager;//windows窗口控制类 |
| 45 | + private WindowManager.LayoutParams mWindowParams;//用于控制拖拽项的显示的参数 |
| 46 | + |
| 47 | + private int mUpScrollBounce;//拖动的时候,开始向上滚动的边界 |
| 48 | + private int mDownScrollBounce;//拖动的时候,开始向下滚动的边界 |
| 49 | + |
| 50 | + public DragListView(Context context, AttributeSet attrs) { |
| 51 | + super(context, attrs); |
| 52 | + mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); |
| 53 | + } |
| 54 | + |
| 55 | + @Override |
| 56 | + public boolean onInterceptTouchEvent(MotionEvent ev) { |
| 57 | + //捕获down事件 |
| 58 | + if (ev.getAction() == MotionEvent.ACTION_DOWN) { |
| 59 | + int x = (int)ev.getX(); |
| 60 | + int y = (int)ev.getY(); |
| 61 | + |
| 62 | + //选中的数据项位置,使用ListView自带的pointToPosition(x, y)方法 |
| 63 | + mDragSrcPosition = mDragPosition = pointToPosition(x, y); |
| 64 | + //如果是无效位置(超出边界,分割线等位置),返回 |
| 65 | + if (mDragPosition == AdapterView.INVALID_POSITION) { |
| 66 | + return super.onInterceptTouchEvent(ev); |
| 67 | + } |
| 68 | + |
| 69 | + //获取选中项View |
| 70 | + //getChildAt(int position)显示display在界面的position位置的View |
| 71 | + //getFirstVisiblePosition()返回第一个display在界面的view在adapter的位置position,可能是0,也可能是4 |
| 72 | + ViewGroup itemView = (ViewGroup) getChildAt(mDragPosition-getFirstVisiblePosition()); |
| 73 | + |
| 74 | + //mDragPoint点击位置在点击View内的相对位置 |
| 75 | + //mDragOffset屏幕位置和当前ListView位置的偏移量,这里只用到y坐标上的值 |
| 76 | + //这两个参数用于后面拖动的开始位置和移动位置的计算 |
| 77 | + mDragPoint = y - itemView.getTop(); |
| 78 | + mDragOffset = (int) (ev.getRawY() - y); |
| 79 | + |
| 80 | + //获取右边的拖动图标,这个对后面分组拖拽有妙用 |
| 81 | + View dragger = itemView.findViewById(R.id.drag_list_item_image); |
| 82 | + //如果在右边位置(拖拽图片左边的20px的右边区域) |
| 83 | + if (dragger != null && x > dragger.getLeft()-20) { |
| 84 | + //准备拖动 |
| 85 | + //初始化拖动时滚动变量 |
| 86 | + //scaledTouchSlop定义了拖动的偏差位(一般+-10) |
| 87 | + //mUpScrollBounce当在屏幕的上部(上面1/3区域)或者更上的区域,执行拖动的边界,mDownScrollBounce同理定义 |
| 88 | + mUpScrollBounce = Math.min(y - mScaledTouchSlop, getHeight()/3); |
| 89 | + mDownScrollBounce = Math.max(y + mScaledTouchSlop, getHeight()*2/3); |
| 90 | + |
| 91 | + //设置Drawingcache为true,获得选中项的影像bm,就是后面我们拖动的哪个头像 |
| 92 | + itemView.setDrawingCacheEnabled(true); |
| 93 | + Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache()); |
| 94 | + |
| 95 | + //准备拖动影像(把影像加入到当前窗口,并没有拖动,拖动操作我们放在onTouchEvent()的move中执行) |
| 96 | + startDrag(bm, y); |
| 97 | + } |
| 98 | + return false; |
| 99 | + } |
| 100 | + return super.onInterceptTouchEvent(ev); |
| 101 | + } |
| 102 | + |
| 103 | + /* |
| 104 | + * 触摸事件 |
| 105 | + * */ |
| 106 | + @Override |
| 107 | + public boolean onTouchEvent(MotionEvent ev) { |
| 108 | + //如果dragmageView为空,说明拦截事件中已经判定仅仅是点击,不是拖动,返回 |
| 109 | + //如果点击的是无效位置,返回,需要重新判断 |
| 110 | + if (mDragImageView != null && mDragPosition != INVALID_POSITION) { |
| 111 | + int action = ev.getAction(); |
| 112 | + switch(action){ |
| 113 | + case MotionEvent.ACTION_UP: |
| 114 | + int upY = (int)ev.getY(); |
| 115 | + //释放拖动影像 |
| 116 | + stopDrag(); |
| 117 | + //放下后,判断位置,实现相应的位置删除和插入 |
| 118 | + onDrop(upY); |
| 119 | + break; |
| 120 | + case MotionEvent.ACTION_MOVE: |
| 121 | + int moveY = (int)ev.getY(); |
| 122 | + //拖动影像 |
| 123 | + onDrag(moveY); |
| 124 | + break; |
| 125 | + default:break; |
| 126 | + } |
| 127 | + return true; |
| 128 | + } |
| 129 | + //这个返回值能够实现selected的选中效果,如果返回true则无选中效果 |
| 130 | + return super.onTouchEvent(ev); |
| 131 | + } |
| 132 | + |
| 133 | + /** |
| 134 | + * * 准备拖动,初始化拖动项的图像 |
| 135 | + * * @param bm |
| 136 | + * * @param y |
| 137 | + * */ |
| 138 | + public void startDrag(Bitmap bm ,int y){ |
| 139 | + //释放影像,在准备影像的时候,防止影像没释放,每次都执行一下 |
| 140 | + stopDrag(); |
| 141 | + |
| 142 | + mWindowParams = new WindowManager.LayoutParams(); |
| 143 | + //从上到下计算y方向上的相对位置, |
| 144 | + mWindowParams.gravity = Gravity.TOP; |
| 145 | + mWindowParams.x = 0; |
| 146 | + mWindowParams.y = y - mDragPoint + mDragOffset; |
| 147 | + mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; |
| 148 | + mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; |
| 149 | + //下面这些参数能够帮助准确定位到选中项点击位置,照抄即可 |
| 150 | + mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| 151 | + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
| 152 | + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
| 153 | + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; |
| 154 | + mWindowParams.format = PixelFormat.TRANSLUCENT; |
| 155 | + mWindowParams.windowAnimations = 0; |
| 156 | + |
| 157 | + //把影像ImagView添加到当前视图中 |
| 158 | + ImageView imageView = new ImageView(getContext()); |
| 159 | + imageView.setImageBitmap(bm); |
| 160 | + mWindowManager = (WindowManager)getContext().getSystemService("window"); |
| 161 | + mWindowManager.addView(imageView, mWindowParams); |
| 162 | + //把影像ImageView引用到变量drawImageView,用于后续操作(拖动,释放等等) |
| 163 | + mDragImageView = imageView; |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * * 停止拖动,去除拖动项的头像 |
| 168 | + * */ |
| 169 | + public void stopDrag(){ |
| 170 | + if (mDragImageView != null) { |
| 171 | + mWindowManager.removeView(mDragImageView); |
| 172 | + mDragImageView = null; |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + /** |
| 177 | + * * 拖动执行,在Move方法中执行 |
| 178 | + * * @param y |
| 179 | + * */ |
| 180 | + public void onDrag(int y){ |
| 181 | + if (mDragImageView != null) { |
| 182 | + mWindowParams.alpha = 0.8f; |
| 183 | + mWindowParams.y = y - mDragPoint + mDragOffset; |
| 184 | + mWindowManager.updateViewLayout(mDragImageView, mWindowParams); |
| 185 | + } |
| 186 | + //为了避免滑动到分割线的时候,返回-1的问题 |
| 187 | + int tempPosition = pointToPosition(0, y); |
| 188 | + if (tempPosition != INVALID_POSITION) { |
| 189 | + mDragPosition = tempPosition; |
| 190 | + } |
| 191 | + |
| 192 | + //滚动 |
| 193 | + int scrollHeight = 0; |
| 194 | + if (y < mUpScrollBounce) { |
| 195 | + scrollHeight = 8;//定义向上滚动8个像素,如果可以向上滚动的话 |
| 196 | + } else if (y > mDownScrollBounce){ |
| 197 | + scrollHeight = -8;//定义向下滚动8个像素,,如果可以向上滚动的话 |
| 198 | + } |
| 199 | + |
| 200 | + if (scrollHeight != 0) { |
| 201 | + //真正滚动的方法setSelectionFromTop() |
| 202 | + setSelectionFromTop(mDragPosition, getChildAt(mDragPosition-getFirstVisiblePosition()).getTop()+scrollHeight); |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + /* |
| 207 | + * 拖动放下的时候 |
| 208 | + * @param y |
| 209 | + * */ |
| 210 | + public void onDrop(int y){ |
| 211 | + //获取放下位置在数据集合中position |
| 212 | + //定义临时位置变量为了避免滑动到分割线的时候,返回-1的问题,如果为-1,则不修改mDragPosition的值,急需执行,达到跳过无效位置的效果 |
| 213 | + int tempPosition = pointToPosition(0, y); |
| 214 | + if (tempPosition != INVALID_POSITION) { |
| 215 | + mDragPosition = tempPosition; |
| 216 | + } |
| 217 | + |
| 218 | + //超出边界处理 |
| 219 | + if (y < getChildAt(0).getTop()) { |
| 220 | + //超出上边界,设为最小值位置0 |
| 221 | + mDragPosition = 0; |
| 222 | + } else if (y > getChildAt(getChildCount()-1).getBottom()) { |
| 223 | + //超出下边界,设为最大值位置,注意哦,如果大于可视界面中最大的View的底部则是越下界,所以判断中用getChildCount()方法 |
| 224 | + //但是最后一项在数据集合中的position是getAdapter().getCount()-1,这点要区分清除 |
| 225 | + mDragPosition = getAdapter().getCount() - 1; |
| 226 | + } |
| 227 | + |
| 228 | + //数据更新 |
| 229 | + if (mDragPosition >= 0 && mDragPosition < getAdapter().getCount()) { |
| 230 | + if (mDropListener != null) { |
| 231 | + mDropListener.onDrop(mDragSrcPosition, mDragPosition); |
| 232 | + } |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + //set the drop listener |
| 237 | + public interface OnDropListener { |
| 238 | + void onDrop(int srcPosition, int destPosition); |
| 239 | + } |
| 240 | + |
| 241 | + private OnDropListener mDropListener = null; |
| 242 | + public void setDropListener (OnDropListener listener) { |
| 243 | + mDropListener = listener; |
| 244 | + } |
| 245 | +} |
0 commit comments