当前位置: 首页 > news >正文

【Android】setText调用导致的悬浮窗抖动问题

在Android13中,有这么一个bug,写一个可以拖到的悬浮窗,这个悬浮窗里有TextView,在拖到某个位置后,再调用TextView的setText方法,会发现出现了一个窗口动画,悬浮窗跳到了起始位置,从开始的位置又滑动到当前位置,看起来就是出现了一个跳动。

试验例子

package com.example.testfloatview;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

public class FloatWindowService extends Service {

    private WindowManager windowManager;
    private View floatView;
    private TextView textView;

    // 记录手指按下时的初始位置
    private int initialX, initialY;
    // 记录悬浮窗的初始位置
    private int initialWindowX, initialWindowY;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // 初始化 WindowManager
        windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

        // 加载布局文件
        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        floatView = inflater.inflate(R.layout.float_window_layout, null);

        // 获取TextView控件
        textView = floatView.findViewById(R.id.text_view);

        // 设置初始文本
        textView.setText("hello, this is a test");

        // 创建 WindowManager.LayoutParams
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
                        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
                        WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT
        );

        // 设置悬浮窗位置
        params.gravity = Gravity.TOP | Gravity.START;
        params.x = 0;
        params.y = 100;
        params.windowAnimations = 0;

        // 添加悬浮窗到窗口管理器
        windowManager.addView(floatView, params);

        // 设置触摸事件监听器
        floatView.setOnTouchListener(new View.OnTouchListener() {
            private int initialTouchX, initialTouchY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        // 记录初始位置
                        initialX = params.x;
                        initialY = params.y;
                        initialTouchX = (int) event.getRawX();
                        initialTouchY = (int) event.getRawY();
                        break;

                    case MotionEvent.ACTION_MOVE:
                        // 计算新的位置
                        params.x = initialX + (int) (event.getRawX() - initialTouchX);
                        params.y = initialY + (int) (event.getRawY() - initialTouchY);

                        // 更新悬浮窗位置
                        windowManager.updateViewLayout(floatView, params);
                        break;
                    case MotionEvent.ACTION_UP:
                        floatView.invalidate();


                        textView.setText("hello");
                 //       textView.setText("hello, this is a test");
                        break;

                    default:
                        break;
                }
                return true; // 消费触摸事件
            }
        });

        // 使用Handler延迟5秒后更新文本
        new Handler().postDelayed(() -> {
       //     textView.setText("hello world");
       //     textView.setText("hello, this is a tttt");
            textView.setText("hello");
        }, 5000);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        // 移除悬浮窗
        if (floatView != null && windowManager != null) {
            windowManager.removeView(floatView);
        }
    }
}

package com.example.testfloatview;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 检查是否已经有悬浮窗权限
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (!Settings.canDrawOverlays(MainActivity.this)) {
                        // 如果没有权限,跳转到系统设置页面申请权限
                        Toast.makeText(MainActivity.this, "需要悬浮窗权限!", Toast.LENGTH_SHORT).show();
                        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                                Uri.parse("package:" + getPackageName()));
                        startActivityForResult(intent, 1);
                    } else {
                        // 已经有权限,启动悬浮窗服务
                        startService(new Intent(MainActivity.this, FloatWindowService.class));
                        finish(); // 关闭主Activity
                    }
                } else {
                    // 启动悬浮窗服务(低版本设备不需要权限)
                    startService(new Intent(MainActivity.this, FloatWindowService.class));
                    finish(); // 关闭主Activity
                }
            }
        });


    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (Settings.canDrawOverlays(this)) {
                    // 用户授予了权限,启动悬浮窗服务
                    startService(new Intent(this, FloatWindowService.class));
                    finish(); // 关闭主Activity
                } else {
                    Toast.makeText(this, "未获得悬浮窗权限!", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
}

在开发者选项里,关闭掉 窗口动画缩放,就没有这个跳动问题了,所以这应该是窗口动画的bug,在调用setText的过程中,view的测量,布局中触发了窗口动画,并且使用了最初的坐标,大致是这样的思路。

相关文章:

  • 天翼云Gpu主机安装Dify手册
  • 强化学习: 继续看 Q-Learning + FrozenLake, 解决更大的地图 8x8, 10x10
  • 【CUDA】Reduce归约求和(下)
  • 谈谈 HTTP 中的重定向,如何处理301和302重定向?
  • 信息安全技术
  • 国自然青年基金|基于机器学习的胃癌辅助化疗疗效预测模型建立及实证研究|基金申请·25-03-05
  • 数据类设计_图片类设计之2_无规则图类设计(前端架构基础)
  • Python教程(一):基本语法、流程控制、数据容器
  • ESP8266UDP透传
  • c++ 中的 friend 关键字
  • 假设检验与置信区间在机器学习中的应用
  • 动态内存管理的了解及使用
  • Flink-DataStreamAPI-执行模式
  • C# Enumerable类 之 数据排序
  • 【项目实战】Spring AI集成DeepSeek实战指南(硅基流动平台版)
  • JSAR 基础 1.2.1 基础概念_空间小程序
  • cdn取消接口缓存
  • 2025-03-08 学习记录--C/C++-C 语言 判断一个数是否是完全平方数
  • [网络爬虫] 动态网页抓取 — 概念引入
  • 基于opencv的hsv色块检测
  • 美国与卡塔尔签署超2435亿美元经济及军事合作协议
  • 一个多月来上海交大接连“牵手”三区,在这些方面进行区校合作
  • 美凯龙:董事兼总经理车建兴被立案调查并留置
  • 中美是否计划讨论美方以芬太尼为由对华征收的特别关税?外交部回应
  • 演员黄晓明、金世佳进入上海戏剧学院2025年博士研究生复试名单
  • 城市轨道交通安全、内河港区布局规划、扎实做好防汛工作……今天的上海市政府常务会议研究了这些重要事项