DragGridView.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. package com.xunao.effectdemo;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.graphics.Bitmap;
  5. import android.graphics.PixelFormat;
  6. import android.graphics.Rect;
  7. import android.os.Handler;
  8. import android.util.AttributeSet;
  9. import android.view.Gravity;
  10. import android.view.MotionEvent;
  11. import android.view.View;
  12. import android.view.WindowManager;
  13. import android.widget.GridView;
  14. import android.widget.ImageView;
  15. /**
  16. * @创建者 Demon
  17. * @创建时间 2018/5/2 16:28
  18. * @描述 ${TODO}
  19. */
  20. public class DragGridView extends GridView {
  21. //拖拽响应的时间 默认为1s
  22. private long mDragResponseMs = 1000;
  23. //是否支持拖拽,默认不支持
  24. private boolean isDrag = true;
  25. //振动器,用于提示替换
  26. // private Vibrator mVibrator;
  27. //拖拽的item的position
  28. private int mDragPosition;
  29. //拖拽的item对应的View
  30. private View mDragView;
  31. //窗口管理器,用于为Activity上添加拖拽的View
  32. private WindowManager mWindowManager;
  33. //item镜像的布局参数
  34. private WindowManager.LayoutParams mLayoutParams;
  35. //item镜像的 显示镜像,这里用ImageView显示
  36. private ImageView mDragMirrorView;
  37. //item镜像的bitmap
  38. private Bitmap mDragBitmap;
  39. //按下的点到所在item的左边缘距离
  40. private int mPoint2ItemLeft;
  41. private int mPoint2ItemTop;
  42. //DragView到上边缘的距离
  43. private int mOffset2Top;
  44. private int mOffset2Left;
  45. //按下时x,y
  46. private int mDownX;
  47. private int mDownY;
  48. //移动的时x.y
  49. private int mMoveX;
  50. private int mMoveY;
  51. //状态栏高度
  52. private int mStatusHeight;
  53. //XGridView向下滚动的边界值
  54. private int mDownScrollBorder;
  55. //XGridView向上滚动的边界值
  56. private int mUpScrollBorder;
  57. //滚动的速度
  58. private int mSpeed = 20;
  59. //item发生变化的回调接口
  60. private OnItemChangeListener changeListener;
  61. private Handler mHandler;
  62. private Context mContext;
  63. /**
  64. * 长按的Runnable
  65. */
  66. private Runnable mLongClickRunable = new Runnable() {
  67. @Override
  68. public void run() {
  69. isDrag = true;
  70. // mVibrator.vibrate(200);
  71. //隐藏该item
  72. mDragView.setVisibility(INVISIBLE);
  73. //在点击的地方创建并显示item镜像
  74. createDragView(mDragBitmap, mDownX, mDownY);
  75. }
  76. };
  77. /**
  78. * 当moveY的值大于向上滚动的边界值,触发GridView自动向上滚动
  79. * 当moveY的值小于向下滚动的边界值,触犯GridView自动向下滚动
  80. * 否则不进行滚动
  81. */
  82. private Runnable mScrollRunbale = new Runnable() {
  83. @Override
  84. public void run() {
  85. int scrollY = 0;
  86. if (mMoveY > mUpScrollBorder){
  87. scrollY = mSpeed;
  88. mHandler.postDelayed(mScrollRunbale,25);
  89. }else if (mMoveY < mDownScrollBorder){
  90. scrollY = -mSpeed;
  91. mHandler.postDelayed(mScrollRunbale,25);
  92. }else {
  93. scrollY = 0;
  94. mHandler.removeCallbacks(mScrollRunbale);
  95. }
  96. smoothScrollBy(scrollY,10);
  97. }
  98. };
  99. public DragGridView(Context context) {
  100. this(context,null);
  101. }
  102. public DragGridView(Context context, AttributeSet attrs) {
  103. this(context, attrs,0);
  104. }
  105. public DragGridView(Context context, AttributeSet attrs, int defStyleAttr) {
  106. super(context, attrs, defStyleAttr);
  107. mContext = context;
  108. // mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
  109. mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  110. mHandler = new Handler();
  111. mStatusHeight = getStatusHeight(context);
  112. }
  113. @Override
  114. public boolean dispatchTouchEvent(MotionEvent ev) {
  115. switch (ev.getAction()){
  116. case MotionEvent.ACTION_DOWN:
  117. mDownX = (int)ev.getX();
  118. mDownY = (int)ev.getY();
  119. //获取按下的position
  120. mDragPosition = pointToPosition(mDownX, mDownY);
  121. if (mDragPosition == INVALID_POSITION){ //无效就返回
  122. return super.dispatchTouchEvent(ev);
  123. }
  124. //延时长按执行mLongClickRunable
  125. mHandler.postDelayed(mLongClickRunable, mDragResponseMs);
  126. //获取按下的item对应的View 由于存在复用机制,所以需要 处理FirstVisiblePosition
  127. mDragView = getChildAt(mDragPosition - getFirstVisiblePosition());
  128. if (mDragView == null){
  129. return super.dispatchTouchEvent(ev);
  130. }
  131. //计算按下的点到所在item的left top 距离
  132. mPoint2ItemLeft = mDownX - mDragView.getLeft();
  133. mPoint2ItemTop = mDownY - mDragView.getTop();
  134. //计算GridView的left top 偏移量:原始距离 - 相对距离就是偏移量
  135. mOffset2Left = (int)ev.getRawX() - mDownX;
  136. mOffset2Top = (int)ev.getRawY() - mDownY;
  137. //获取GridView自动向下滚动的偏移量,小于这个值,DragGridView向下滚动
  138. mDownScrollBorder = getHeight() / 4;
  139. //获取GridView自动向上滚动的偏移量,大于这个值,DragGridView向上滚动
  140. mUpScrollBorder = getHeight() * 3 / 4;
  141. //开启视图缓存
  142. mDragView.setDrawingCacheEnabled(true);
  143. //获取缓存的中的bitmap镜像 包含了item中的ImageView和TextView
  144. mDragBitmap = Bitmap.createBitmap(mDragView.getDrawingCache());
  145. //释放视图缓存 避免出现重复的镜像
  146. mDragView.destroyDrawingCache();
  147. break;
  148. case MotionEvent.ACTION_MOVE:
  149. mMoveX = (int)ev.getX();
  150. mMoveY = (int)ev.getY();
  151. //如果只在按下的item上移动,未超过边界,就不移除mLongClickRunable
  152. if (!isTouchInItem(mDragView, mMoveX, mMoveY)) {
  153. mHandler.removeCallbacks(mLongClickRunable);
  154. }
  155. break;
  156. case MotionEvent.ACTION_UP:
  157. mHandler.removeCallbacks(mLongClickRunable);
  158. mHandler.removeCallbacks(mScrollRunbale);
  159. break;
  160. default:
  161. break;
  162. }
  163. return super.dispatchTouchEvent(ev);
  164. }
  165. @Override
  166. public boolean onTouchEvent(MotionEvent ev) {
  167. if (isDrag && mDragMirrorView != null){
  168. switch (ev.getAction()){
  169. case MotionEvent.ACTION_DOWN:
  170. break;
  171. case MotionEvent.ACTION_MOVE:
  172. mMoveX = (int)ev.getX();
  173. mMoveY = (int)ev.getY();
  174. onDragItem(mMoveX, mMoveY);
  175. break;
  176. case MotionEvent.ACTION_UP:
  177. onStopDrag();
  178. isDrag = false;
  179. break;
  180. default:
  181. break;
  182. }
  183. return true;
  184. }
  185. return super.onTouchEvent(ev);
  186. }
  187. /************************对外提供的接口***************************************/
  188. public boolean isDrag() {
  189. return isDrag;
  190. }
  191. //设置是否可拖拽
  192. public void setDrag(boolean drag) {
  193. isDrag = drag;
  194. }
  195. public long getDragResponseMs() {
  196. return mDragResponseMs;
  197. }
  198. //设置长按响应时长
  199. public void setDragResponseMs(long mDragResponseMs) {
  200. this.mDragResponseMs = mDragResponseMs;
  201. }
  202. public void setOnItemChangeListener(OnItemChangeListener changeListener) {
  203. this.changeListener = changeListener;
  204. }
  205. /******************************************************************************/
  206. /**
  207. * 点是否在该View上面
  208. * @param view
  209. * @param x
  210. * @param y
  211. * @return
  212. */
  213. private boolean isTouchInItem(View view, int x, int y) {
  214. if (view == null){
  215. return false;
  216. }
  217. if (view.getLeft() < x && x < view.getRight()
  218. && view.getTop() < y && y < view.getBottom()){
  219. return true;
  220. }else {
  221. return false;
  222. }
  223. }
  224. /**
  225. * 获取状态栏的高度
  226. * @param context
  227. * @return
  228. */
  229. private static int getStatusHeight(Context context){
  230. int statusHeight = 0;
  231. Rect localRect = new Rect();
  232. ((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect);
  233. statusHeight = localRect.top;
  234. if (0 == statusHeight){
  235. Class<?> localClass;
  236. try {
  237. localClass = Class.forName("com.android.internal.R$dimen");
  238. Object localObject = localClass.newInstance();
  239. int height = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
  240. statusHeight = context.getResources().getDimensionPixelSize(height);
  241. } catch (Exception e) {
  242. e.printStackTrace();
  243. }
  244. }
  245. return statusHeight;
  246. }
  247. /**
  248. * 停止拖动
  249. */
  250. private void onStopDrag() {
  251. View view = getChildAt(mDragPosition - getFirstVisiblePosition());
  252. if (view != null){
  253. view.setVisibility(VISIBLE);
  254. }
  255. changeListener.onStop();
  256. removeDragImage();
  257. }
  258. /**
  259. * WindowManager 移除镜像
  260. */
  261. private void removeDragImage() {
  262. if (mDragMirrorView != null){
  263. mWindowManager.removeView(mDragMirrorView);
  264. mDragMirrorView = null;
  265. }
  266. }
  267. /**
  268. * 拖动item到指定位置
  269. * @param x
  270. * @param y
  271. */
  272. private void onDragItem(int x, int y) {
  273. mLayoutParams.x = x - mPoint2ItemLeft + mOffset2Left;
  274. mLayoutParams.y = y - mPoint2ItemTop + mOffset2Top - mStatusHeight;
  275. //更新镜像位置
  276. mWindowManager.updateViewLayout(mDragMirrorView,mLayoutParams);
  277. onSwapItem(x,y);
  278. mHandler.post(mScrollRunbale);
  279. }
  280. /**
  281. * 交换 item 并且控制 item之间的显示与隐藏
  282. * @param x
  283. * @param y
  284. */
  285. private void onSwapItem(int x, int y) {
  286. //获取我们手指移动到那个item
  287. int tmpPosition = pointToPosition(x, y);
  288. if (tmpPosition != INVALID_POSITION && tmpPosition != mDragPosition){
  289. if (changeListener != null){
  290. changeListener.onChange(mDragPosition, tmpPosition);
  291. }
  292. //隐藏tmpPosition
  293. getChildAt(tmpPosition - getFirstVisiblePosition()).setVisibility(INVISIBLE);
  294. //显示之前的item
  295. getChildAt(mDragPosition - getFirstVisiblePosition()).setVisibility(VISIBLE);
  296. mDragPosition = tmpPosition;
  297. }
  298. }
  299. /**
  300. * 创建拖动的镜像
  301. * @param bitmap
  302. * @param downX
  303. * @param downY
  304. */
  305. private void createDragView(Bitmap bitmap, int downX, int downY) {
  306. mLayoutParams = new WindowManager.LayoutParams();
  307. mLayoutParams.format = PixelFormat.TRANSLUCENT; //图片之外其他地方透明
  308. mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; //左 上
  309. //指定位置 其实就是 该 item 对应的 rawX rawY 因为Window 添加View是需要知道 raw x ,y的
  310. mLayoutParams.x = mOffset2Left + (downX - mPoint2ItemLeft);
  311. mLayoutParams.y = downY - mPoint2ItemTop + mOffset2Top - mStatusHeight;
  312. //指定布局大小
  313. mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
  314. mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
  315. //透明度
  316. mLayoutParams.alpha = 0.4f;
  317. //指定标志 不能获取焦点和触摸
  318. mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  319. | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  320. mDragMirrorView = new ImageView(getContext());
  321. mDragMirrorView.setImageBitmap(bitmap);
  322. //添加View到窗口中
  323. mWindowManager.addView(mDragMirrorView,mLayoutParams);
  324. }
  325. /**
  326. * item 交换时的回调接口
  327. */
  328. public interface OnItemChangeListener{
  329. void onChange(int from, int to);
  330. void onStop();
  331. }
  332. }