简介:Android悬浮窗(悬浮Activity)是一种常见的需求,尤其用于显示悬浮歌词或控制面板。本源码项目提供了可拖动悬浮窗的实现方案,涉及WindowManager服务、自定义布局、触摸事件处理和权限管理等知识点。通过 FloatingActivity
和 DraggableLayout
类,开发者可以轻松创建可拖动的悬浮窗,在音乐播放器或视频应用中实现浮动歌词或控制面板功能。
1. Android悬浮窗基础
悬浮窗是一种在其他应用程序之上显示的窗口,通常用于提供额外的功能或信息。在Android中,悬浮窗由WindowManager服务管理,该服务提供创建、管理和销毁悬浮窗的API。
悬浮窗通常用于显示通知、控制媒体播放或提供快速访问常用功能。它们可以根据需要进行定位和调整大小,并可以包含各种UI元素,如按钮、文本和图像。
2.1 WindowManager服务的介绍
WindowManager服务概述
WindowManager服务是Android系统中负责管理窗口和显示的系统服务。它提供了创建、管理和销毁窗口的API,并负责将窗口内容绘制到屏幕上。WindowManager服务由 android.view.WindowManager
类表示,它提供了以下主要功能:
- 创建和管理窗口:WindowManager服务可以创建和管理各种类型的窗口,包括应用程序窗口、系统窗口和悬浮窗口。
- 控制窗口属性:WindowManager服务允许应用程序设置窗口的属性,例如大小、位置、透明度和焦点。
- 处理窗口事件:WindowManager服务负责处理窗口事件,例如触摸事件、键盘事件和焦点事件。
- 绘制窗口内容:WindowManager服务将窗口内容绘制到屏幕上。它使用SurfaceFlinger服务来实际执行绘制操作。
WindowManager服务的架构
WindowManager服务由以下主要组件组成:
- WindowManagerPolicy :负责管理窗口的整体策略,例如窗口的优先级、显示顺序和焦点分配。
- WindowManagerService :负责创建、管理和销毁窗口,并处理窗口事件。
- SurfaceFlinger :负责将窗口内容绘制到屏幕上。
获取WindowManager服务
要获取WindowManager服务,可以使用以下代码:
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager服务的使用
WindowManager服务提供了多种API用于创建、管理和销毁窗口。常用的方法包括:
- addView() :将一个View添加到窗口中。
- removeView() :从窗口中移除一个View。
- updateViewLayout() :更新窗口中View的布局。
- setAttributes() :设置窗口的属性。
- requestFocus() :请求窗口获取焦点。
代码示例
以下代码示例演示了如何使用WindowManager服务创建和显示一个简单的悬浮窗口:
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
View view = new TextView(this);
view.setText("悬浮窗口");
windowManager.addView(view, params);
在上面的代码中,我们首先获取WindowManager服务,然后创建了一个WindowManager.LayoutParams对象来指定窗口的属性。接下来,我们创建一个TextView并将其添加到窗口中。最后,我们将窗口添加到WindowManager服务中。
3. 悬浮窗交互与事件处理
3.1 触摸事件处理
悬浮窗的触摸事件处理与普通视图的触摸事件处理基本一致,可以通过 onTouchEvent()
方法来处理触摸事件。 onTouchEvent()
方法的签名如下:
public boolean onTouchEvent(MotionEvent event)
其中, MotionEvent
对象包含了触摸事件的详细信息,如触摸点的位置、触摸动作类型等。
在 onTouchEvent()
方法中,可以通过 event.getAction()
方法获取触摸动作类型,然后根据不同的触摸动作类型进行相应的处理。常见的触摸动作类型有:
-
MotionEvent.ACTION_DOWN
:手指按下 -
MotionEvent.ACTION_MOVE
:手指移动 -
MotionEvent.ACTION_UP
:手指抬起 -
MotionEvent.ACTION_CANCEL
:触摸事件被取消
例如,以下代码展示了如何处理手指按下的事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// 手指按下时,记录按下时的坐标
mDownX = event.getRawX();
mDownY = event.getRawY();
}
return super.onTouchEvent(event);
}
3.2 拖动实现
为了实现悬浮窗的拖动,需要在 onTouchEvent()
方法中处理手指移动事件。当手指移动时,可以通过计算手指移动的距离来更新悬浮窗的位置。
以下代码展示了如何实现悬浮窗的拖动:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
// 手指移动时,计算手指移动的距离
float moveX = event.getRawX() - mDownX;
float moveY = event.getRawY() - mDownY;
// 更新悬浮窗的位置
mWindowManager.updateViewLayout(m悬浮窗View, m悬浮窗LayoutParams);
}
return super.onTouchEvent(event);
}
3.3 权限管理
悬浮窗需要使用 SYSTEM_ALERT_WINDOW
权限才能显示。该权限是一个危险权限,需要在清单文件中声明。
以下代码展示了如何在清单文件中声明 SYSTEM_ALERT_WINDOW
权限:
<manifest ...>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
...
</manifest>
在运行时,还可以通过 ContextCompat.checkSelfPermission()
方法检查是否拥有 SYSTEM_ALERT_WINDOW
权限。如果未拥有该权限,可以通过 ActivityCompat.requestPermissions()
方法请求该权限。
以下代码展示了如何在运行时请求 SYSTEM_ALERT_WINDOW
权限:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SYSTEM_ALERT_WINDOW}, REQUEST_CODE_SYSTEM_ALERT_WINDOW);
}
4. 悬浮窗高级应用
4.1 悬浮窗显示/隐藏控制
悬浮窗的显示和隐藏控制是悬浮窗应用中常见的功能。可以通过以下步骤实现悬浮窗的显示和隐藏控制:
- 获取WindowManager服务 :首先需要获取WindowManager服务,可以通过以下代码获取:
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
-
创建悬浮窗布局 :创建悬浮窗布局,包括悬浮窗的大小、位置、背景等属性。
-
添加悬浮窗 :将悬浮窗布局添加到WindowManager服务中,可以通过以下代码添加悬浮窗:
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.gravity = Gravity.CENTER;
windowManager.addView(悬浮窗布局, layoutParams);
- 移除悬浮窗 :当需要隐藏悬浮窗时,可以通过以下代码移除悬浮窗:
windowManager.removeView(悬浮窗布局);
4.2 用户交互设计
悬浮窗的用户交互设计至关重要,需要考虑以下因素:
-
悬浮窗大小和位置 :悬浮窗的大小和位置应根据实际使用场景进行设计,既要保证用户操作方便,又不能遮挡其他重要内容。
-
触摸事件处理 :悬浮窗需要处理触摸事件,包括点击、拖动、长按等操作。可以通过覆写悬浮窗布局的onTouchEvent()方法来处理触摸事件。
-
拖动实现 :悬浮窗可以通过拖动来改变位置,可以通过覆写悬浮窗布局的onTouchEvent()方法来实现拖动功能。
-
权限管理 :悬浮窗需要申请权限才能正常显示,可以通过以下代码申请权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION);
}
}
5. 悬浮窗实战应用
5.1 悬浮歌词应用实现
需求分析
悬浮歌词应用需要实现以下功能:
- 在视频播放器上方显示歌词
- 同步播放器进度,实时更新歌词
- 支持歌词搜索和收藏
技术选型
- 悬浮窗技术:WindowManager
- 歌词解析:正则表达式
- 播放器控制:MediaController
代码实现
public class LyricWindowService extends Service {
private WindowManager windowManager;
private LyricsView lyricsView;
@Override
public void onCreate() {
super.onCreate();
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
lyricsView = new LyricsView(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 获取播放器信息
MediaController mediaController = intent.getParcelableExtra("mediaController");
// 同步播放器进度
mediaController.addOnTimelineChangeListener(new TimelineChangeListener() {
@Override
public void onTimelineChange(Timeline timeline, Object reason) {
lyricsView.updateLyrics(timeline.getCurrentPosition());
}
});
// 显示悬浮窗
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.CENTER;
windowManager.addView(lyricsView, params);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
windowManager.removeView(lyricsView);
}
}
歌词解析
public class LyricsParser {
private static final Pattern LYRICS_PATTERN = Pattern.compile("(?<time>[0-9:.]+)\t(?<lyrics>.+)");
public static List<Lyric> parseLyrics(String lyrics) {
List<Lyric> lyricsList = new ArrayList<>();
Matcher matcher = LYRICS_PATTERN.matcher(lyrics);
while (matcher.find()) {
String time = matcher.group("time");
String lyricsText = matcher.group("lyrics");
Lyric lyric = new Lyric(time, lyricsText);
lyricsList.add(lyric);
}
return lyricsList;
}
}
歌词显示
public class LyricsView extends View {
private List<Lyric> lyricsList;
private int currentPosition;
public LyricsView(Context context) {
super(context);
}
public void setLyrics(List<Lyric> lyricsList) {
this.lyricsList = lyricsList;
}
public void updateLyrics(long position) {
currentPosition = position;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 查找当前时间对应的歌词
Lyric currentLyric = null;
for (Lyric lyric : lyricsList) {
if (lyric.getTime() <= currentPosition) {
currentLyric = lyric;
}
}
// 绘制歌词
if (currentLyric != null) {
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(30);
canvas.drawText(currentLyric.getLyrics(), 0, 30, paint);
}
}
}
简介:Android悬浮窗(悬浮Activity)是一种常见的需求,尤其用于显示悬浮歌词或控制面板。本源码项目提供了可拖动悬浮窗的实现方案,涉及WindowManager服务、自定义布局、触摸事件处理和权限管理等知识点。通过 FloatingActivity
和 DraggableLayout
类,开发者可以轻松创建可拖动的悬浮窗,在音乐播放器或视频应用中实现浮动歌词或控制面板功能。