一、在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博客_倒计时悬浮窗