首先感谢多位CSDN大神的指导!如果本文有任何不对的地方,欢迎指出。
个人查了很多资料,都没有相关的指导,大部分的教程都只是实现了悬浮窗,并没有涉及悬浮窗内容的更新问题。
我的这个demo实现了在一个Activity生成悬浮窗后,在其他任何Activity或者是Serivce或者是线程中,都可以对悬浮窗的内容进行更新。
实现思路
通过发送系统广播的方法,通知悬浮窗类对其自身的内容进行更新
实现过程
1,新建一个类,继承Service,在这个类中实现一个可拖动的悬浮窗,并注册一个广播接收器,在其中读取特定的广播,并依据广播内容更新悬浮窗。
2,在其他需要更新悬浮窗的地方,发送一个特定的广播。
demo说明
1,按下相应按钮可以打开/关闭悬浮窗。
2,按下测试,会新建一个线程,在线程中延时发送广播,更新悬浮窗内容。
3,实际测试在别的Activity中发送相应广播,也可以更新悬浮窗。
源代码
- 悬浮窗的实现类:FxService.java
package com.application.mac.floating_test4;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class FxService extends Service
{
static final String receive_show = "Float_window_show";
//注册广播
private MsgReceiver msgReceiver;
//定义浮动窗口布局
LinearLayout mFloatLayout;
WindowManager.LayoutParams wmParams;
//创建浮动窗口设置布局参数的对象
WindowManager mWindowManager;
Button mFloatView_Button;
TextView mFloatView_textView;
private static final String TAG = "FxService";
/**
* 广播接收器
*
*/
public class MsgReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//此处从广播中取出数据,并写入你想要的函数
String string = intent.getStringExtra(receive_show);
mFloatView_textView.setText(string);
}
}
@Override
public void onCreate()
{
// TODO Auto-generated method stub
super.onCreate();
Log.i(TAG, "on_create");
createFloatView();
//动态注册广播接收器
msgReceiver = new MsgReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.application.mac.RECEIVER");
registerReceiver(msgReceiver, intentFilter);
}
@Override
public IBinder onBind(Intent intent)
{
// TODO Auto-generated method stub
return null;
}
private void createFloatView()
{
wmParams = new WindowManager.LayoutParams();
//获取的是WindowManagerImpl.CompatModeWrapper
mWindowManager = (WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);
Log.i(TAG, "mWindowManager--->" + mWindowManager);
//设置window type
wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
//设置图片格式,效果为背景透明
wmParams.format = PixelFormat.RGBA_8888;
//设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//调整悬浮窗显示的停靠位置为左侧置顶
wmParams.gravity = Gravity.LEFT | Gravity.TOP;
// 以屏幕左上角为原点,设置x、y初始值,相对于gravity
wmParams.x = 0;
wmParams.y = 0;
//设置悬浮窗口长宽数据
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
/*// 设置悬浮窗口长宽数据
wmParams.width = 200;
wmParams.height = 80;*/
LayoutInflater inflater = LayoutInflater.from(getApplication());
//获取浮动窗口视图所在布局
mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);
//添加mFloatLayout
mWindowManager.addView(mFloatLayout, wmParams);
//浮动窗口按钮
mFloatView_Button = (Button)mFloatLayout.findViewById(R.id.float_id);
mFloatView_textView = (TextView)mFloatLayout.findViewById(R.id.float_text);
mFloatView_textView.setText("我是悬浮窗");
mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED), View.MeasureSpec
.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
Log.i(TAG, "Width/2--->" + mFloatView_Button.getMeasuredWidth()/2);
Log.i(TAG, "Height/2--->" + mFloatView_Button.getMeasuredHeight()/2);
//设置监听浮动窗口的触摸移动
mFloatView_Button.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
// TODO Auto-generated method stub
//getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标
wmParams.x = (int) event.getRawX() - mFloatView_Button.getMeasuredWidth()/2;
Log.i(TAG, "RawX" + event.getRawX());
Log.i(TAG, "X" + event.getX());
//减25为状态栏的高度
wmParams.y = (int) event.getRawY() - mFloatView_Button.getMeasuredHeight()/2 - 25;
Log.i(TAG, "RawY" + event.getRawY());
Log.i(TAG, "Y" + event.getY());
//刷新
mWindowManager.updateViewLayout(mFloatLayout, wmParams);
return false; //此处必须返回false,否则OnClickListener获取不到监听
}
});
mFloatView_Button.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
// TODO Auto-generated method stub
Toast.makeText(FxService.this, "onClick", Toast.LENGTH_SHORT).show();
mFloatView_textView.setText("我很丑\n但我很聪明");
}
});
}
@Override
public void onDestroy()
{
// TODO Auto-generated method stub
super.onDestroy();
//注销广播
unregisterReceiver(msgReceiver);
if(mFloatLayout != null)
{
//移除悬浮窗口
mWindowManager.removeView(mFloatLayout);
}
}
}
接下来是主页面的逻辑
- MainActivity.java
package com.application.mac.floating_test4;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import static com.application.mac.floating_test4.FxService.receive_show;
/**
* 本项目实现了悬浮窗的外部更新
* 原理:使用了安卓的广播系统,向FxService发送系统广播,从而实现
* 内容的更新
*/
public class MainActivity extends AppCompatActivity {
final static String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.text_floating).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
for(int i = 0;i < 60;i++){
Intent intent_Receiver = new Intent("com.application.mac.RECEIVER");
intent_Receiver.putExtra(receive_show,"现在数到" + i);
sendBroadcast(intent_Receiver);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
});
findViewById(R.id.open_Floating).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,FxService.class);
startService(intent);
}
});
findViewById(R.id.stop_Floating).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,FxService.class);
stopService(intent);
}
});
}
@Override
protected void onDestroy() {
Intent intent = new Intent(MainActivity.this,FxService.class);
stopService(intent);
super.onDestroy();
}
}
然后是悬浮窗的布局文件
- float_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:orientation="vertical"
android:layout_height="wrap_content">
<Button
android:id="@+id/float_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拖拽"/>
<TextView
android:text="default text"
android:id="@+id/float_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
还有主页面的布局文件
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/text_floating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="测试"/>
<Button
android:id="@+id/open_Floating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@+id/text_floating"
android:text="开悬浮窗"/>
<Button
android:id="@+id/stop_Floating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/open_Floating"
android:layout_alignParentBottom="true"
android:text="关悬浮窗" />
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
最后切记在AndroidManiFest.xml中声明相关权限
- AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.application.mac.floating_test4">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/>
<uses-permission android:name="android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".FxService" >
</service>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
注意:此demo没有写动态申请权限的逻辑,烦请各位按需要自行实现。