因为赶项目本人停更两个月 从今天开始又可以更新了 今天说一下这个可随意拖动的view 简单说一下这个view效果 和 发展 一开始这种效果是使用在网页端的特别是购物类 例如某宝 某东 购物车和客服窗口 都有使用这个悬浮可拖动的设计效果 后来才发展到的移动端 还有手机桌面也是用到了这种效果 例如某族手机的消息中心 手机桌面的悬浮球 某讯 和 某荣耀手游 某吃鸡游戏 某视频软件等等也都是这种效果 这种方式的好处就是是可以随时随地的快速进去到你需要的页面 目前来说 有很多的大型app选择了这种交互方式 服务用户 ;
下面说一下android 端的使用及实现

先看一下效果图
Android 自定义View 之 可随意拖动的View-LMLPHP

编码思路

首先实现这种效果的方式有两种 一是自定义viewgroup 自定义容器 需要获取手机悬浮窗权限和处理各种事件 二 使用自定义view 第一种方式相对简单一些 但是使用效果不好 所以我也就放弃掉了
今天主要说一下通过自定义view 的方式实现

自定义的主要思路 : 以ImageView为例

创建FreeView 继承 ImageView

首先 获取最大高度和宽度 设置view 的活动范围 (主要考虑是否支持屏幕外 具体细节下面再说)

第二 通过onTouchEvent 触摸事件进行 位移计算 重置FreeView 的四角坐标

第三 就是完成显示

主要思路就这么多 我的重点是第一步和第二步

注意
第一步 获取最大高度和宽度 就是给定FreeView的活动范围 最大宽度很容易获取 需要注意的是最大高度 我们屏幕组成

window = statusbar + layout + navigationbar (从上倒下的顺序)

受android碎片化影响 andorid手机的高度具有很多种不确定性 所以要做好高度的适配

还有第二个要注意的地方 这里跟你的需求有关系 就是是否允许FreeView 出现在屏幕外 这种需求例如魅族手机的消息中心 颗拖到屏幕边缘 留下一个小尾巴 点击或者拖动时在展现给用户

第二步 这里主要是实现计算 和 实现方式 首先 onTouchEvent 通过触摸监听MotionEvent.ACTION_DOWN 我可以得到点击时的坐标 通过MotionEvent.ACTION_MOVE 可以得到离开时的坐标 然后通过给FreeView设置layout 实现 位置重置 在这里还要注意点击事件和拖动时间的冲突处理 代码中会有体现

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;

import com.nade.commenlib.util.UiUtils;

/**
 *Created by Nade on 2018/10/2.
 *随意拖动的view
 */

@SuppressLint("AppCompatCustomView")
public class FreeView extends ImageView {

    private int width; //  测量宽度 FreeView的宽度
    private int height; // 测量高度 FreeView的高度
    private int maxWidth; // 最大宽度 window 的宽度
    private int maxHeight; // 最大高度 window 的高度
    private Context context;
    private float downX; //点击时的x坐标
    private float downY;  // 点击时的y坐标
    //是否拖动标识
    private boolean isDrag=false;

    // 处理点击事件和滑动时间冲突时使用 返回是否拖动标识
    public boolean isDrag() {
        return isDrag;
    }

    // 初始化属性
    public FreeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;


    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取屏宽高 和 可是适用范围 (我的需求是可在屏幕内拖动 不超出范围 也不需要隐藏)
        width=getMeasuredWidth();
        height=getMeasuredHeight();
        maxWidth = UiUtils.getMaxWidth(context);
        //   maxHeight = UiUtils.getMaxHeight(context)-getStatusBarHeight();// 此时减去状态栏高度 注意如果有状态栏 要减去状态栏 如下行 得到的是可活动的高度
       maxHeight = UiUtils.getMaxHeight(context)-getStatusBarHeight() - getNavigationBarHeight();
    }
    // 获取状态栏高度
    public int getStatusBarHeight(){
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        return getResources().getDimensionPixelSize(resourceId);
    }
    // 获取导航栏高度
    public int getNavigationBarHeight() {
        int rid = getResources().getIdentifier("config_showNavigationBar", "bool", "android");
        if (rid!=0){
            int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
            return context.getResources().getDimensionPixelSize(resourceId);
        }else
            return 0;

    }


    /**
     * 处理事件分发
     * @param event
     * @return
     */

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        if (this.isEnabled()) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: // 点击动作处理 每次点击时将拖动状态改为 false 并且记录下点击时的坐标 downX downY
                    isDrag=false;
                    downX = event.getX(); // 点击触屏时的x坐标 用于离开屏幕时的x坐标作计算
                    downY = event.getY(); // 点击触屏时的y坐标 用于离开屏幕时的y坐标作计算
                    break;
                case MotionEvent.ACTION_MOVE: // 滑动动作处理 记录离开屏幕时的 moveX  moveY 用于计算距离 和 判断滑动事件和点击事件 并作出响应
                    final float moveX = event.getX() - downX;
                    final float moveY = event.getY() - downY;
                    int l,r,t,b; // 上下左右四点移动后的偏移量
                    //计算偏移量 设置偏移量 = 3 时 为判断点击事件和滑动事件的峰值
                    if (Math.abs(moveX) > 3 ||Math.abs(moveY) > 3) { // 偏移量的绝对值大于 3 为 滑动时间 并根据偏移量计算四点移动后的位置
                        l = (int) (getLeft() + moveX);
                        r = l+width;
                        t = (int) (getTop() + moveY);
                        b = t+height;
                        //不划出边界判断,最大值为边界值
                        // 如果你的需求是可以划出边界 此时你要计算可以划出边界的偏移量 最大不能超过自身宽度或者是高度  如果超过自身的宽度和高度 view 划出边界后 就无法再拖动到界面内了 注意
                        if(l<0){ // left 小于 0 就是滑出边界 赋值为 0 ; right 右边的坐标就是自身宽度 如果可以划出边界 left right top bottom 最小值的绝对值 不能大于自身的宽高
                            l=0;
                            r=l+width;
                        }else if(r> maxWidth){ // 判断 right 并赋值
                            r= maxWidth;
                            l=r-width;
                        }
                        if(t<0){ // top
                            t=0;
                            b=t+height;
                        }else if(b> maxHeight){ // bottom
                            b= maxHeight;
                            t=b-height;
                        }
                        this.layout(l, t, r, b); // 重置view在layout 中位置
                        isDrag=true;  // 重置 拖动为 true
                    }else {
                        isDrag=false; // 小于峰值3时 为点击事件
                    }
                    break;
                case MotionEvent.ACTION_UP: // 不处理
                    setPressed(false);
                    break;
                case MotionEvent.ACTION_CANCEL: // 不处理
                    setPressed(false);
                    break;
            }
            return true;
        }
        return false;
    }
}

注释写的很清晰了 不懂得可以看代码 我说一下点击事件的处理 我暴漏了一个方法
public boolean isDrag() {
return isDrag;
}
这个方法返回一个boolean 适用于标识是否滑动 我们利用这个值进行区分点击事件和滑动事件
好 看一下使用

freeview = findViewById(R.id.main_freeview);
freeview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!freeview.isDrag()) {
Toast.makeText(MainActivity.this, “点击了freeview”, Toast.LENGTH_SHORT).show();
}
}
});

此时只要处理点击事件即可
好了 本文到此完结 如有疑问欢迎私信和留言 源码在这里就不上传了 有需要可以加我的群 675234574 或加我微信

下面是工具类

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class UiUtil {
// 获取最大宽度
public static int getMaxWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService( Context.WINDOW_SERVICE );
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics( dm );
return dm.widthPixels;
}
// 获取最大高度
public static int getMaxHeight(Context context) {
WindowManager wm = (WindowManager) context.getSystemService( Context.WINDOW_SERVICE );
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics( dm );
return dm.heightPixels;
}
}
10-04 13:56