2022-10-10 Android 在其他应用上的悬浮窗View

一、在samsung android8.0 真机运行效果图如下


二、废话不多说,直接上代码

    1、代码架构

    2、java/com/example/suspendedwindow/MainActivity.java

package com.example.suspendedwindow;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Timer;
import java.util.TimerTask;

import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

/**
 * 倒计时60s悬浮窗
 *
 * @author zy
 *
 */
public class MainActivity extends Activity {
    protected static final String TAG = "CountDownActivity";
    protected static final int TIME = 1;

    private Context context = MainActivity.this;
    private TextView tv_time;
    private Button cancle;
    private static Timer countDown = null;
    private int mValue = 60;
    private int statusBarHeight;// 状态栏高度
    WindowManager wm;
    WindowManager.LayoutParams params ;
    View countDownView;
    private boolean viewAdded = false;// 透明窗体是否已经显示
    //private WindowManager.LayoutParams layoutParams ;
    Handler post = new Handler();
    LinearLayout    commonCardContainer;
    LinearLayout.LayoutParams params_window;
    int width = 10;
    boolean lock = false ;
    int remeberx = 0;
    int remebery = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    /**
     * 点击显示悬浮窗
     *
     * @param
     */
    @SuppressLint("WrongConstant")
    public void show(View v) {
        wm = (WindowManager) getApplicationContext().getSystemService(
                WINDOW_SERVICE); // 注意:这里必须是全局的context
        // 判断UI控件是否存在,存在则移除,确保开启任意次应用都只有一个悬浮窗
        if (countDownView != null) {
            wm.removeView(countDownView);
        }

/*
        params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
*/
        params = new WindowManager.LayoutParams();
        params.width  = WindowManager.LayoutParams.WRAP_CONTENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

        // 系统级别的窗口
        params.type =  WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;


                //| WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
      //  params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

        // 居中显示
       // params.gravity = Gravity.CENTER;
        //params.gravity = Gravity.RIGHT|Gravity.BOTTOM; //悬浮窗开始在右下角显示
        params.gravity = Gravity.LEFT | Gravity.TOP;

        // 设置背景透明
        params.format = PixelFormat.TRANSPARENT;

        countDownView = new View(getApplicationContext()); // 不依赖activity的生命周期
        countDownView = View.inflate(getApplicationContext(),
                R.layout.countdown_weight, null);

        cancle = (Button) countDownView.findViewById(R.id.cancle);
        tv_time = (TextView) countDownView.findViewById(R.id.tv_time);
        tv_time.setText("60");
        wm.addView(countDownView, params);
        viewAdded = true;

        /**
         * 监听窗体移动事件
         */
        countDownView.setOnTouchListener(new View.OnTouchListener() {
            float[] temp = new float[] { 0f, 0f };

            public boolean onTouch(View v, MotionEvent event) {
                Log.i(TAG, "temp[0]:" + temp[0]+" temp[1]:" + temp[1] + " X:" + event.getX()+
                        " Y:"+event.getY() + " RawX:"+event.getRawX() + " RawY:"+event.getRawY());
                if(lock)
                    return true;
                int eventaction = event.getAction();
                switch (eventaction) {
                    case MotionEvent.ACTION_DOWN: // 按下事件,记录按下时手指在悬浮窗的XY坐标值
                        temp[0] = event.getX();
                        temp[1] = event.getY();
                        break;

                    case MotionEvent.ACTION_MOVE:
                        refreshView((int) (event.getRawX() - temp[0]),
                                (int) (event.getRawY() - temp[1]));
                        break;
                }
                return true;
            }
        });

                // 设置“取消”倒计时按钮的监听
        cancle.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View view) {
               /*
                Log.e(TAG, "取消倒计时");
                wm.removeView(countDownView);
                countDownView = null;
                countDown.cancel();
                mValue = 999;
                */
/*
                ImageView twenty_four_hour = (ImageView) countDownView.findViewById(R.id.img);
                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) twenty_four_hour.getLayoutParams();
                lp.width = ++lp.width;
                twenty_four_hour.setLayoutParams(lp);
                Log.i(TAG, "lp.width:"+lp.width);
*/

                lock = !lock;
                if(lock)
                {
                    Toast.makeText(MainActivity.this, "Locked", Toast.LENGTH_SHORT).show();
                    params.flags = 0 ;
                    refresh();
                }
                else {
                    Toast.makeText(MainActivity.this, "Unocked", Toast.LENGTH_SHORT).show();
                    params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
                    refresh();
                }

            }
        });

        // 添加倒计时功能
        countDown = new Timer();
        countDown.schedule(new TimerTask() {
            @Override
            public void run() {
                mValue--;
                post.post(drawCount);
                if (mValue == 0) {
                    // 执行关机操作(这里可以使任意其他操作,根据自己的需求)
                    Log.e(TAG, "关机");
                    wm.removeView(countDownView);
                    countDownView = null;
                    // 取消定时
                    countDown.cancel();
                    finish();
                }
            }
        }, 0, 1000);

       // refreshView(295, 949);
        finish();
    }
    private void refreshView(int x, int y) {
        // 状态栏高度不能立即取,不然得到的值是0
        if (statusBarHeight == 0) {
            View rootView = countDownView.getRootView();
            Rect r = new Rect();
            rootView.getWindowVisibleDisplayFrame(r);
            statusBarHeight = r.top;
        }

        params.x = x;
        // y轴减去状态栏的高度,因为状态栏不是用户可以绘制的区域,不然拖动的时候会有跳动
        params.y = y - statusBarHeight;// STATUS_HEIGHT;

        Log.i(TAG, " x:"+x+" y:"+y+" params.x:"+params.x+" params.y:" +params.y +" statusBarHeight:"+statusBarHeight);
   //     params.x = 1080- params.x;
     //   params.y = 1920- params.y;

        //params.x = 311 ;
        //params.y = 1114 ;
        remeberx = params.x;
        remebery = params.y;

        refresh();
/*
        ImageView twenty_four_hour = (ImageView) countDownView.findViewById(R.id.img);
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) twenty_four_hour.getLayoutParams();
        twenty_four_hour.setLayoutParams(lp);
        Log.i(TAG, "lp.width:"+lp.width);
 */
    }

    /**
     * 添加悬浮窗或者更新悬浮窗 如果悬浮窗还没添加则添加 如果已经添加则更新其位置
     */
    private void refresh() {
        // 如果已经添加了就只更新view
        if (viewAdded) {
            wm.updateViewLayout(countDownView, params);
        } else {
            wm.addView(countDownView, params);
            viewAdded = true;
        }
    }

    /**
     * 模拟其他操作
     * @param view
     */
    public void other(View view) {
        Toast.makeText(context, "别的操作", Toast.LENGTH_SHORT).show();
        ImageView twenty_four_hour = (ImageView) findViewById(R.id.img);
        RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) twenty_four_hour.getLayoutParams();
        lp.width = lp.width + 1;
        twenty_four_hour.setLayoutParams(lp);
        Log.i(TAG, "lp.width:"+lp.width+ " topMargin:"+lp.topMargin);
        // startActivity(new Intent(context, NewActivity.class));
    }

    Runnable drawCount = new Runnable() {
        @Override
        public void run() {
            tv_time.setText(Integer.toString(mValue));
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "倒计时结束");
    };
}

    3、res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    tools:context=".MainActivity" >
    <ImageView
        android:id="@+id/img"
        android:layout_width="152px"
        android:layout_height="wrap_content"
        android:layout_marginTop="126px"
        android:text="60"
        android:textColor="#ff0000"
        android:background="@drawable/background"
        android:textSize="25dp" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="show"
        android:text="点击显示弹窗" />

    <Button
        android:layout_alignParentBottom="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="other"
        android:text="别的操作" />

</RelativeLayout>

    4、res/layout/countdown_weight.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/twenty_four_hour"
        android:layout_gravity="center"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:padding="10dp" >

        <ImageView
            android:id="@+id/img"
            android:layout_width="433px"
            android:layout_height="290px"
            android:text="60"
            android:textColor="#ff0000"
            android:background="@drawable/background"
            android:textSize="25dp" />
        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="60"
            android:textColor="#ff0000"
            android:textSize="25dp" />

        <Button
            android:id="@+id/cancle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消"
            android:textSize="25dp"
            android:textColor="#000" />
        <!-- android:background="@drawable/xml_sel_button_bg" -->
    </LinearLayout>

</FrameLayout>

    5、申请权限

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

    6、遇到的一个坑

三、参考文章

(664条消息) Android悬浮窗看这篇就够了_AndroidLMY的博客-CSDN博客_android 悬浮窗

(664条消息) Android悬浮窗的坑_android_cai_niao的博客-CSDN博客_android studio 悬浮窗

(664条消息) Android常用控件之悬浮窗_dztai的博客-CSDN博客_安卓悬浮窗

(664条消息) Android悬浮窗的实现--可以置顶,可以设置优先级的view_兮谁与歌的博客-CSDN博客_倒计时悬浮窗

猜你喜欢

转载自blog.csdn.net/qq_37858386/article/details/127241074
今日推荐