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

发布于:2025-03-11 ⋅ 阅读:(16) ⋅ 点赞:(0)

在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的测量,布局中触发了窗口动画,并且使用了最初的坐标,大致是这样的思路。