Android实现系统级悬浮按钮

作者:Tim0815 时间:2022-12-15 00:44:26 

本文实例为大家分享了Android系统级悬浮按钮的具体代码,供大家参考,具体内容如下

具体的需求

1、就是做一个系统级的悬浮按钮,就像iPhone 桌面的那个悬浮按钮效果一样,能随意拖动,并且手一放开,悬浮按钮就自动靠边。
2、可以点击并且可以随意拖动。
3、悬浮按钮自动靠边的时候,或者移动到边上的时候,自动隐藏半边。
4、横竖屏切换都兼容

1、就在WindowManager 里面添加View,这个View通过自定义控件来实现。
2、在onTouch里的MotionEvent.ACTION_MOVE事件里头,通过控制悬浮按钮的具体坐标来实现随意移动。
3、在onTouch里的MotionEvent.ACTION_UP事件里头,来控制悬浮按钮自动靠边,并且自动隐藏半边,不过在这里onTouch和onClick这两个事件是一起触发的,不过这也有解决办法,你可以在手放开的瞬间,通过移动的距离,来决定是否触发点击事件,,如果返回false,就会触发点击事件,如果返回true就会触发点击事件
4、通过自定义控件onLayout方法,来捕获横竖屏切换事件,
5、还有一个靠哪边停靠的问题,通过坐标来判读更靠近哪一边。就靠哪边停靠。
![以中间这个中心点为准,以更短的X轴画一个正方形]

Android实现系统级悬浮按钮

下面是具体实现代码:


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;

import com.iapppay.openid.channel.LoginResultCallback;
import com.iapppay.openid.channel.OpenIDApplication;
import com.iapppay.openid.channel.util.DisplayUtil;
import com.iapppay.openid.channel.util.LogUtil;
import com.iapppay.openid.channel.util.Res;

/**
* Created by HuangTiebing 2017/2/14.
*/

public class DragFloatActionButton extends ImageView implements View.OnTouchListener, View.OnClickListener {

public static String TAG = "DragFloatActionButton";
 private Context context;

float lastX, lastY;
 float originX, originY;
 int screenWidth;
 int screenHeight;
 private int originWidth;

private WindowManager windowManager;
 //  // 此windowManagerParams变量为获取的全局变量,用以保存悬浮窗口的属性
 private WindowManager.LayoutParams windowManagerParams;

private LoginResultCallback resultCallback; //悬浮按钮点击回调

public DragFloatActionButton(Context context, boolean isForceLogin, LoginResultCallback resultCallback) {
   this(context, null);
   OpenIDApplication.getInstance().setForceLogin(isForceLogin);
   this.resultCallback = resultCallback;
 }

public DragFloatActionButton(Context context, AttributeSet attrs) {
   this(context, attrs, 0);
 }

public DragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
   super(context, attrs, defStyleAttr);
   this.context = context;

Point screenSize = DisplayUtil.getScreenSize(context);
   screenWidth = screenSize.x;
   screenHeight = screenSize.y;
   setImageResource(Res.drawable(context, "ipay_float_btn_bg"));
   setOnTouchListener(this);
   setOnClickListener(this);

windowManager = (WindowManager) getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
 }

public int getOriginWidth() {
   return originWidth;
 }

public void setOriginWidth(int originWidth) {
   this.originWidth = originWidth;
 }

@Override
 public boolean onTouch(View v, MotionEvent event) {
   windowManagerParams = (WindowManager.LayoutParams) this.getLayoutParams();
   //获取到状态栏的高度
   Rect frame = new Rect();
   getWindowVisibleDisplayFrame(frame);
   int ea = event.getAction();
   switch (ea) {
     case MotionEvent.ACTION_DOWN:
       lastX = event.getRawX();// 获取触摸事件触摸位置的原始X坐标
       lastY = event.getRawY();
       originX = lastX;
       originY = lastY;
       break;
     case MotionEvent.ACTION_MOVE:
       float dx = event.getRawX() - lastX;
       float dy = event.getRawY() - lastY;
       windowManagerParams.x += dx;
       windowManagerParams.y += dy;
       LogUtil.d(TAG, "移动距离:dx=" + dx + ",dy=" + dy);
       showAllBtn();
       lastX = (int) event.getRawX();
       lastY = (int) event.getRawY();
       break;
     case MotionEvent.ACTION_UP:
       float lastMoveDx = Math.abs(event.getRawX() - originX);
       float lastMoveDy = Math.abs(event.getRawY() - originY);
       LogUtil.d(TAG, "松开时,移动距离:lastMoveDx=" + lastMoveDx + ", lastMoveDy=" + lastMoveDy);
       if (lastMoveDx < 10 && lastMoveDy < 10) { //移动距离太小,视为点击,
         return false;
       } else {
         updateViewLayout(event);
         isFirstClick = true;
         return true;
       }
   }
   return false;
 }

/**
  * 显示整个图标
  */
 public void showAllBtn() {
   windowManagerParams.width = originWidth;
   windowManagerParams.height = originWidth;
   setImageResource(Res.drawable(context, "ipay_float_btn_bg"));
   windowManager.updateViewLayout(this, windowManagerParams); // 刷新显示
 }

/**
  * 悬浮按钮显示在左边
  */
 private void showInLeft() {
   windowManagerParams.x = 0;
   windowManagerParams.width = originWidth / 2;
   windowManagerParams.height = originWidth;
   setImageResource(Res.drawable(context, "ipay_float_btn_left_hidden"));
   windowManager.updateViewLayout(this, windowManagerParams); // 刷新显示
 }

/**
  * 悬浮按钮显示在右边
  */
 private void showInRight() {
   windowManagerParams.width = originWidth / 2;
   windowManagerParams.height = originWidth;
   windowManagerParams.x = screenWidth - windowManagerParams.width;
   setImageResource(Res.drawable(context, "ipay_float_btn_right_hidden"));
   windowManager.updateViewLayout(this, windowManagerParams); // 刷新显示
 }

/**
  * 悬浮按钮显示在上面
  */
 private void showInTop() {
   windowManagerParams.y = 0;
   windowManagerParams.width = originWidth;
   windowManagerParams.height = originWidth / 2;
   setImageResource(Res.drawable(context, "ipay_float_btn_top_hidden"));
   windowManager.updateViewLayout(this, windowManagerParams); // 刷新显示
 }

/**
  * 悬浮按钮显示在下面
  */
 private void showInBottom() {
   windowManagerParams.width = originWidth;
   windowManagerParams.height = originWidth / 2;
   windowManagerParams.y = screenHeight - windowManagerParams.width;
   setImageResource(Res.drawable(context, "ipay_float_btn_bottom_hidden"));
   windowManager.updateViewLayout(this, windowManagerParams); // 刷新显示
 }

/**
  * 更新悬浮图标
  *
  * @param event 手动移动事件
  */
 public void updateViewLayout(MotionEvent event) {
   Point center = new Point(screenWidth / 2, screenHeight / 2); //屏幕中心点
   float xOffset, yOffset;//以屏幕中心点为原点,X轴和Y轴上的偏移量
   if (event != null) {//手动移动的
     xOffset = event.getRawX() - center.x;
     yOffset = event.getRawY() - center.y;
   } else {//自动隐藏
     xOffset = lastX - center.x;
     yOffset = lastY - center.y;
   }
   if (Math.abs(xOffset) >= Math.abs(yOffset)) {//向左或向右缩进隐藏
     if (xOffset <= 0) { //向左缩进
       showInLeft();
     } else {
       showInRight();
     }
   } else {//向上或向下缩进隐藏
     if (yOffset <= 0) {//向上缩进
       showInTop();
     } else {
       showInBottom();
     }
   }
 }

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 }

@Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   super.onLayout(changed, left, top, right, bottom);
   Point screenSize = DisplayUtil.getScreenSize(context);
   if (screenWidth != screenSize.x) {//屏幕旋转切换
     screenWidth = screenSize.x;
     screenHeight = screenSize.y;
     lastY = windowManagerParams.x;
     lastX = windowManagerParams.y;
     windowManagerParams.x = (int) lastX;
     windowManagerParams.y = (int) lastY;
     updateViewLayout(null);
   }
 }

private boolean isFirstClick = true;

@Override
 protected void onDraw(Canvas canvas) {
   super.onDraw(canvas);
 }

@Override
 public void onClick(View v) {
   LogUtil.d(TAG, "执行点击事件");
   if (!isFirstClick) {
     OpenIDApplication.getInstance().floatBtnClick(context, OpenIDApplication.getInstance().isForceLogin(), resultCallback);
   } else {//半隐藏状态,点击显示全部
     isFirstClick = false;
     showAllBtn();
   }
 }

}

调用实现代码,这里注意有个问题,弹出系统级的悬浮窗,需要配置权限:

并且Android 6.0以上的手机,还要弹出对话框问用户是否运行,如果这个用户拒绝了,就不能弹出系统级的悬浮窗了,还有个别手机厂商修改了android源码,还需要进系统设置里去允许这个应用弹出悬浮窗。这样的话就体验感非常不好,不过这里有个小技巧,按下面方式设置为toast类型就完全解决,既不用配置权限,也不弹出窗来向用户获取权限,完全解决问题。


WindowManager.LayoutParams windowManagerParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_TOAST,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);

具体实现代码如下:


DragFloatActionButton floatBtn = new DragFloatActionButton(context, isForceLogin, mResultCallback);

WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  // 设置LayoutParams(全局变量)相关参数
  WindowManager.LayoutParams windowManagerParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_TOAST,
    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
    PixelFormat.TRANSLUCENT);
  /**
   * 注意,flag的值可以为:
   * 下面的flags属性的效果形同“锁定”。
   * 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
   * LayoutParams.FLAG_NOT_TOUCH_MODAL 不影响后面的事件
   * LayoutParams.FLAG_NOT_FOCUSABLE 不可聚焦
   * LayoutParams.FLAG_NOT_TOUCHABLE 不可触摸
   */
  // 调整悬浮窗口至左上角,便于调整坐标
  windowManagerParams.gravity = Gravity.LEFT | Gravity.TOP;
  // 以屏幕左上角为原点,设置x、y初始值
  windowManagerParams.x = 0;
  windowManagerParams.y = 0;
  // 设置悬浮窗口长宽数据
  floatBtn.measure(0, 0);
  floatBtn.setOriginWidth(floatBtn.getMeasuredWidth() - 50);
  windowManagerParams.width = floatBtn.getOriginWidth();
  windowManagerParams.height = windowManagerParams.width;
  // 显示myFloatView图像
  windowManager.addView(floatBtn, windowManagerParams);

标签:Android,悬浮按钮
0
投稿

猜你喜欢

  • JAVA算法起步之插入排序实例

    2021-05-30 15:52:30
  • Java输入输出流实例详解

    2023-05-28 15:54:35
  • MyBatis动态SQL标签用法实例详解

    2023-11-26 01:04:27
  • Android短信验证码自动填充功能

    2023-11-25 00:04:20
  • SpringBoot多种自定义错误页面方式小结

    2021-12-12 00:56:03
  • Java 爬虫工具Jsoup详解

    2022-04-11 03:46:16
  • 简单谈谈Struts动态表单(DynamicForm)

    2022-10-07 07:24:29
  • 浅析C# 结构体struct

    2021-08-08 08:21:19
  • Java应用层协议WebSocket实现消息推送

    2022-05-09 07:26:29
  • java 一个类实现两个接口的案例

    2023-08-09 12:24:35
  • JAVA代码实现MongoDB动态条件之分页查询

    2022-05-21 12:44:11
  • C++中静态成员函数与静态成员变量(static )

    2021-11-08 07:31:38
  • 详解C#枚举中使用Flags特性

    2023-05-19 04:44:31
  • JavaSE实现猜拳游戏

    2023-12-22 07:21:19
  • Java GC 机制与内存分配策略详解

    2022-06-12 05:36:44
  • Java中ThreadLocal避免内存泄漏的方法详解

    2023-04-02 12:51:42
  • Java实现动态数字时钟

    2022-05-07 08:56:56
  • Android实现水波纹效果

    2022-12-11 01:47:16
  • 在Spring Boot中从类路径加载文件的示例

    2023-11-11 21:53:44
  • C#提取网页中超链接link和text部分的方法

    2023-02-24 02:04:14
  • asp之家 软件编程 m.aspxhome.com