1前言

由于项目需求,需要把项目的主界面采用GridView显示,并且需要根据模块优先级支持拖动图标(砍死产品狗)。为此,自定义了一个支持拖拽图标的GridView。效果如下:

具体效果如上图

2 可拖拽的GridView实现

要实现上面的效果有两个难点,第一就是如何创造一个可拖动的View在我们的Activity界面上。第二个就是如何实现两个View的交换

关于第一个:我们可以用WindowManager 来往我们的界面上添加View,这样我们再重写GridView的onTouchEvent()方法,根据移动的距离来更新的我们View的位置即可
关于第二个:可以这样实现,当我们拖动时,创建一个透明度低一点的镜像item
View。把要拖动的item对应的View先隐藏起来,此时Adapter的item先不交换,当我们把拖动的item移动到另一个item对应的范围内,我们再进行交换,先把这个item隐藏掉,然后在原来的位置显示出这个item。最后镜像item对应的view
再消失。

其实关于第二点,也有其他的交换策略,比如判断拖到镜像view到另一个item之上一段时间再进行交换等。

1 实现思路

好了,下面我们仔细总结了一下思路,有了思路我们就很好办了:

1.根据手指按下的X,Y坐标来获取我们在GridView上面点击的item,再获取对应的View

2.手指按下的时候使用Handler和Runnable来实现一个定时器,假如定时时间为1000毫秒,在1000毫秒内,如果手指抬起了就移除定时器,没有抬起并且手指点击在GridView的item所在的区域,则表示我们长按了GridView的item

3. 如果我们长按了item则隐藏item,然后使用WindowManager来添加一个item的镜像在屏幕用来代替刚刚隐藏的item

4.当我们手指在屏幕移动的时候,更新item镜像的位置,然后在根据我们移动的X,Y的坐标来获取移动到GridView的哪一个位置

5. 到GridView的item过多的时候,可能一屏幕显示不完,我们手指拖动item镜像到屏幕下方,要触发GridView想上滚动,同理,当我们手指拖动item镜像到屏幕上面,触发GridView向下滚动

6.GridView交换数据,刷新界面,移除item的镜像

2 实现代码

这里定义一个XGridView继承GridView来实现
代码如下,加了详细的注释,应该容易看懂

package com.qiyei.javatest;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.GridView;
import android.widget.ImageView; /**
* Created by qiyei2015 on 2017/1/5.
*/
public class XGridView extends GridView { //拖拽响应的时间 默认为1s
private long mDragResponseMs = 1000;
//是否支持拖拽,默认不支持
private boolean isDrag = false;
//振动器,用于提示替换
private Vibrator mVibrator;
//拖拽的item的position
private int mDragPosition;
//拖拽的item对应的View
private View mDragView; //窗口管理器,用于为Activity上添加拖拽的View
private WindowManager mWindowManager;
//item镜像的布局参数
private WindowManager.LayoutParams mLayoutParams; //item镜像的 显示镜像,这里用ImageView显示
private ImageView mDragMirrorView;
//item镜像的bitmap
private Bitmap mDragBitmap; //按下的点到所在item的左边缘距离
private int mPoint2ItemLeft;
private int mPoint2ItemTop; //DragView到上边缘的距离
private int mOffset2Top;
private int mOffset2Left; //按下时x,y
private int mDownX;
private int mDownY;
//移动的时x.y
private int mMoveX;
private int mMoveY; //状态栏高度
private int mStatusHeight; //XGridView向下滚动的边界值
private int mDownScrollBorder;
//XGridView向上滚动的边界值
private int mUpScrollBorder;
//滚动的速度
private int mSpeed = 20; //item发生变化的回调接口
private OnItemChangeListener changeListener; private Handler mHandler; /**
* 长按的Runnable
*/
private Runnable mLongClickRunable = new Runnable() {
@Override
public void run() { isDrag = true;
mVibrator.vibrate(200);
//隐藏该item
mDragView.setVisibility(INVISIBLE); //在点击的地方创建并显示item镜像
createDragView(mDragBitmap,mDownX,mDownY);
}
}; /**
* 当moveY的值大于向上滚动的边界值,触发GridView自动向上滚动
* 当moveY的值小于向下滚动的边界值,触犯GridView自动向下滚动
* 否则不进行滚动
*/
private Runnable mScrollRunbale = new Runnable() {
@Override
public void run() {
int scrollY = 0;
if (mMoveY > mUpScrollBorder){
scrollY = mSpeed;
mHandler.postDelayed(mScrollRunbale,25);
}else if (mMoveY < mDownScrollBorder){
scrollY = -mSpeed;
mHandler.postDelayed(mScrollRunbale,25);
}else {
scrollY = 0;
mHandler.removeCallbacks(mScrollRunbale);
}
smoothScrollBy(scrollY,10);
}
}; public XGridView(Context context) {
this(context,null);
} public XGridView(Context context, AttributeSet attrs) {
this(context, attrs,0);
} public XGridView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mHandler = new Handler();
mStatusHeight = getStatusHeight(context);
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
mDownX = (int)ev.getX();
mDownY = (int)ev.getY(); //获取按下的position
mDragPosition = pointToPosition(mDownX,mDownY);
if (mDragPosition == INVALID_POSITION){ //无效就返回
return super.dispatchTouchEvent(ev);
} //延时长按执行mLongClickRunable
mHandler.postDelayed(mLongClickRunable,mDragResponseMs);
//获取按下的item对应的View 由于存在复用机制,所以需要 处理FirstVisiblePosition
mDragView = getChildAt(mDragPosition - getFirstVisiblePosition());
if (mDragView == null){
return super.dispatchTouchEvent(ev);
} //计算按下的点到所在item的left top 距离
mPoint2ItemLeft = mDownX - mDragView.getLeft();
mPoint2ItemTop = mDownY - mDragView.getTop();
//计算GridView的left top 偏移量:原始距离 - 相对距离就是偏移量
mOffset2Left = (int)ev.getRawX() - mDownX;
mOffset2Top = (int)ev.getRawY() - mDownY; //获取GridView自动向下滚动的偏移量,小于这个值,DragGridView向下滚动
mDownScrollBorder = getHeight() /4;
//获取GridView自动向上滚动的偏移量,大于这个值,DragGridView向上滚动
mUpScrollBorder = getHeight() * 3/4; //开启视图缓存
mDragView.setDrawingCacheEnabled(true);
//获取缓存的中的bitmap镜像 包含了item中的ImageView和TextView
mDragBitmap = Bitmap.createBitmap(mDragView.getDrawingCache());
//释放视图缓存 避免出现重复的镜像
mDragView.destroyDrawingCache(); break;
case MotionEvent.ACTION_MOVE: mMoveX = (int)ev.getX();
mMoveY = (int)ev.getY(); //如果只在按下的item上移动,未超过边界,就不移除mLongClickRunable
if (!isTouchInItem(mDragView,mMoveX,mMoveY)){
mHandler.removeCallbacks(mLongClickRunable);
} break;
case MotionEvent.ACTION_UP:
mHandler.removeCallbacks(mLongClickRunable);
mHandler.removeCallbacks(mScrollRunbale);
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent ev) {
if (isDrag && mDragMirrorView != null){
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
mMoveX = (int)ev.getX();
mMoveY = (int)ev.getY();
onDragItem(mMoveX,mMoveY);
break;
case MotionEvent.ACTION_UP:
onStopDrag();
isDrag = false;
break;
default:
break;
}
return true;
}
return super.onTouchEvent(ev);
} /************************对外提供的接口***************************************/ public boolean isDrag() {
return isDrag;
} public void setDrag(boolean drag) {
isDrag = drag;
} public long getDragResponseMs() {
return mDragResponseMs;
} public void setDragResponseMs(long mDragResponseMs) {
this.mDragResponseMs = mDragResponseMs;
} public void setOnItemChangeListener(OnItemChangeListener changeListener) {
this.changeListener = changeListener;
}
/******************************************************************************/ /**
* 点是否在该View上面
* @param view
* @param x
* @param y
* @return
*/
private boolean isTouchInItem(View view, int x, int y) {
if (view == null){
return false;
}
if (view.getLeft() < x && x < view.getRight()
&& view.getTop() < y && y < view.getBottom()){
return true;
}else {
return false;
}
} /**
* 获取状态栏的高度
* @param context
* @return
*/
private static int getStatusHeight(Context context){
int statusHeight = 0;
Rect localRect = new Rect();
((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect);
statusHeight = localRect.top;
if (0 == statusHeight){
Class<?> localClass;
try {
localClass = Class.forName("com.android.internal.R$dimen");
Object localObject = localClass.newInstance();
int height = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
statusHeight = context.getResources().getDimensionPixelSize(height);
} catch (Exception e) {
e.printStackTrace();
}
}
return statusHeight;
} /**
* 停止拖动
*/
private void onStopDrag() {
View view = getChildAt(mDragPosition - getFirstVisiblePosition());
if (view != null){
view.setVisibility(VISIBLE);
}
removeDragImage();
} /**
* WindowManager 移除镜像
*/
private void removeDragImage() {
if (mDragMirrorView != null){
mWindowManager.removeView(mDragMirrorView);
mDragMirrorView = null;
}
} /**
* 拖动item到指定位置
* @param x
* @param y
*/
private void onDragItem(int x, int y) {
mLayoutParams.x = x - mPoint2ItemLeft + mOffset2Left;
mLayoutParams.y = y - mPoint2ItemTop + mOffset2Top - mStatusHeight;
//更新镜像位置
mWindowManager.updateViewLayout(mDragMirrorView,mLayoutParams); onSwapItem(x,y); mHandler.post(mScrollRunbale);
} /**
* 交换 item 并且控制 item之间的显示与隐藏
* @param x
* @param y
*/
private void onSwapItem(int x, int y) {
//获取我们手指移动到那个item
int tmpPosition = pointToPosition(x,y);
if (tmpPosition != INVALID_POSITION && tmpPosition != mDragPosition){
if (changeListener != null){
changeListener.onChange(mDragPosition,tmpPosition);
}
//隐藏tmpPosition
getChildAt(tmpPosition - getFirstVisiblePosition()).setVisibility(INVISIBLE);
//显示之前的item
getChildAt(mDragPosition - getFirstVisiblePosition()).setVisibility(VISIBLE); mDragPosition = tmpPosition;
} } /**
* 创建拖动的镜像
* @param bitmap
* @param downX
* @param downY
*/
private void createDragView(Bitmap bitmap, int downX, int downY) {
mLayoutParams = new WindowManager.LayoutParams();
mLayoutParams.format = PixelFormat.TRANSLUCENT; //图片之外其他地方透明
mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; //左 上
//指定位置 其实就是 该 item 对应的 rawX rawY 因为Window 添加View是需要知道 raw x ,y的
mLayoutParams.x = mOffset2Left + (downX - mPoint2ItemLeft);
mLayoutParams.y = mOffset2Top + (downY - mPoint2ItemTop) + mStatusHeight;
//指定布局大小
mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
//透明度
mLayoutParams.alpha = 0.4f;
//指定标志 不能获取焦点和触摸
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mDragMirrorView = new ImageView(getContext());
mDragMirrorView.setImageBitmap(bitmap);
//添加View到窗口中
mWindowManager.addView(mDragMirrorView,mLayoutParams);
} /**
* item 交换时的回调接口
*/
public interface OnItemChangeListener{
void onChange(int from,int to);
}

android 自定义View开发实战(六) 可拖动的GridView的更多相关文章

  1. Android自定义View实战(SlideTab-可滑动的选择器)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/52178553 本文出自:[openXu的博客] 目录: 初步分析重写onDraw绘制 重写o ...

  2. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  3. android 自定义view 前的基础知识

    本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了 ...

  4. 【朝花夕拾】Android自定义View篇之(八)多点触控(上)MotionEvent简介

    前言 在前面的文章中,介绍了不少触摸相关的知识,但都是基于单点触控的,即一次只用一根手指.但是在实际使用App中,常常是多根手指同时操作,这就需要用到多点触控相关的知识了.多点触控是在Android2 ...

  5. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  6. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  7. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  8. Android 自定义View及其在布局文件中的使用示例(二)

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3530213.html From crash_coder linguowu linguowu0622@gami ...

  9. Android自定义View

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View ...

随机推荐

  1. python操作剪贴板错误提示:pywintypes.error: (1418, 'GetClipboardData',线程没有打开的剪贴板)

    问题现象:通过打断点,一步步调试可以正常复制和粘贴剪贴板数据.但是直接运行会报错pywintypes.error: (1418, 'GetClipboardData',线程没有打开的剪贴板) 问题原因 ...

  2. 对于2-sat问题的求解

    一.O(n+m) 暴力不多说 二.O(m) 1.构图 2.求图的极大强连通子图 3.把每个子图收缩成单个节点,根据原图关系构造一个有向无环图 4.判断是否有解,无解则输出(退出) 5.对新图进行拓扑排 ...

  3. asp.net 引发类型为“System.OutOfMemoryException”的异常

    asp.net 引发类型为“System.OutOfMemoryException”的异常通常发生在IIS进程获取不到内存时. 临时解决方法是: 回收IIS的应用程序池. 如果要比较好的解决办法是: ...

  4. 最小生成树求法 Prim + Kruskal

    prim算法的思路 和dijkstra是一样的 每次选取一个最近的点 然后去向新的节点扩张 注意这里的扩张 不再是 以前求最短路时候的到新的节点的最短距离 而是因为要生成一棵树 所以是要连一根最短的连 ...

  5. POJ3233:Matrix Power Series

    对n<=30(其实可以100)大小的矩阵A求A^1+A^2+……+A^K,K<=1e9,A中的数%m. 从K的二进制位入手.K分解二进制,比如10110,令F[i]=A^1+A^2+……+ ...

  6. ajax请求数据之后在已经有的数据前面打对勾的方法

    今天遇到这么一个需求: 选择一部分人,在点击确定的时候添加到对应的div中,也就是添加到对应的表单下面,当再次查询的时候需要在已经选过的人的复选框前面打伤对勾.

  7. 库操作&表操作

    系统数据库 ps:系统数据库: mysql 授权库,主要存储系统用户的 权限信息 test MySQL数据库系统自动创建的 测试数据库 ination_schema 虚拟库,不占用磁盘空间,存储的是数 ...

  8. CodeForces 582A【multiset使用样例】

    题意: 给一些无序的数字,求解一个矩阵,使得矩阵的每一个元素都是行和列标志数的gcd,输出行标志数. 首先对数字进行排序.复杂度n*log(n^2). 这题的证明有官方的英文题解==在这直接贴英文题解 ...

  9. Win7 SP1 安装SQL Server 2012时提示“此计算机上的操作系统不符合 SQL Server 2012的最低要求”

  10. java的反射机制和javassist、asm

    1.java的反射机制,可以帮助我们在运行的时候获取我们引用的java类相关的信息,包括类的名字.所包含的方法名字.方法参数等等 2.javassit这个jar包,大概看了下,更厉害,它可以直接操作字 ...