Android 自定义View 之 可随意拖动的View

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

先看一下效果图

freeview.gif
交流

编码思路

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

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

创建FreeView 继承 ImageView

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

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

第三 就是完成显示

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

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

win.png

受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;

/**
*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 = UiUtil.getMaxWidth(context);
    maxHeight = UiUtil.getMaxHeight(context)-getStatusBarHeight();// 此时减去状态栏高度 注意如果有状态栏 要减去状态栏 如下行 得到的是可活动的高度
   //maxHeight = UiUtil.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;
}
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,451评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,691评论 2 59
  • 先说一下这篇文章里面的内容:TCP 客户端, 自定义对话框, 自定义按钮, ProgressBar竖直显示, 重力...
    杨奉武阅读 3,271评论 0 3
  • 看到以前日记本当中的一段 ——摘自Jean《十五岁的日记本》 他不羁的脸像天色将晚他洗过的发像心中火焰 我现在在北...
    Jeansunsun阅读 421评论 3 3
  • 28岁的若拉不是那种第一眼美女。“你长得又普通,家庭又普通,再不努力学习将来怎么办?”妈妈从若拉记事起就不停在她耳...
    滴滴桃阅读 253评论 0 0