foreword
Floating windows are a relatively common requirement. For example, the video call interface is reduced into a floating window, and then users can handle things on other interfaces.
This article will explain the floating window implementation steps, principles, example codes, etc.
Realization principle
1. WindowManager interface
The interface drawing of Android is realized through the service WindowMananger
of . So, since we want to implement a floating window that can be on an interface other than our own application, we need to use WindowManager
to achieve it.
(frameworks/base/core/java/android/view/WindowMananger.java)
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
...
}
WindowManager
It implements ViewManager
the interface and can WINDOW_SERVICE
be obtained by obtaining the system service. ViewManager
The interface has addView
a method , and we use this method to add the floating window control to the screen.
2. LayoutParam setting
What needs to be emphasized here LayoutParam
is type
the variable in . This variable is used to specify the window type.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
Implementation
Implement a floating window for automatic carousel.
1. Rendering
2. Declaration and application authority
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
@RequiresApi(api = Build.VERSION_CODES.M)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show();
startService(new Intent(MainActivity.this, FloatingImageDisplayService.class));
}
}
}
public void startFloatingImageDisplayService(View view) {
if (FloatingImageDisplayService.isStarted) {
return;
}
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT);
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 1);
} else {
startService(new Intent(MainActivity.this, FloatingImageDisplayService.class));
}
}
}
3. Build the controls needed for the floating window
image_display.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/image_display_imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
4. Add the control to theWindowManager
@RequiresApi(api = Build.VERSION_CODES.M)
public class FloatingImageDisplayService extends Service {
public static boolean isStarted = false;
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
private View displayView;
private int[] images;
private int imageIndex = 0;
private Handler changeImageHandler;
@Override
public void onCreate() {
super.onCreate();
isStarted = true;
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
layoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.width = 500;
layoutParams.height = 500;
layoutParams.x = 300;
layoutParams.y = 300;
images = new int[] {
R.mipmap.image_01,
R.mipmap.image_02,
R.mipmap.image_03,
R.mipmap.image_04,
R.mipmap.image_05
};
changeImageHandler = new Handler(this.getMainLooper(), changeImageCallback);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
showFloatingWindow();
return super.onStartCommand(intent, flags, startId);
}
private void showFloatingWindow() {
if (Settings.canDrawOverlays(this)) {
LayoutInflater layoutInflater = LayoutInflater.from(this);
displayView = layoutInflater.inflate(R.layout.image_display, null);
displayView.setOnTouchListener(new FloatingOnTouchListener());
ImageView imageView = displayView.findViewById(R.id.image_display_imageview);
imageView.setImageResource(images[imageIndex]);
windowManager.addView(displayView, layoutParams);
changeImageHandler.sendEmptyMessageDelayed(0, 2000);
}
}
private Handler.Callback changeImageCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 0) {
imageIndex++;
if (imageIndex >= 5) {
imageIndex = 0;
}
if (displayView != null) {
((ImageView) displayView.findViewById(R.id.image_display_imageview)).setImageResource(images[imageIndex]);
}
changeImageHandler.sendEmptyMessageDelayed(0, 2000);
}
return false;
}
};
private class FloatingOnTouchListener implements View.OnTouchListener {
private int x;
private int y;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
int nowY = (int) event.getRawY();
int movedX = nowX - x;
int movedY = nowY - y;
x = nowX;
y = nowY;
layoutParams.x = layoutParams.x + movedX;
layoutParams.y = layoutParams.y + movedY;
windowManager.updateViewLayout(view, layoutParams);
break;
default:
break;
}
return false;
}
}
}