Android 计时器悬浮窗实现

在 Android 应用程序中,悬浮窗是一种非常实用的功能,它可以在其他应用上方显示重要的信息和小工具。而计时器悬浮窗则是其中一个常见的应用场景,适用于需要定时提醒或跟踪时间的应用。本文将探讨如何在 Android 中实现计时器悬浮窗,并提供一个代码示例,帮助开发者快速上手这一特性。

什么是悬浮窗?

悬浮窗,是指在 Android 界面上显示的可以自由浮动的小窗口。用户可以通过手势来移动这个小窗口,它可以显示信息、工具或任何其他功能。悬浮窗常用于聊天应用中的悬浮聊天气泡、快速工具箱等场景。

计时器悬浮窗的应用场景

计时器悬浮窗可以用于多种场景,例如:

  • 倒计时(如烹饪、赛事)
  • 闹钟提醒
  • 长时间任务跟踪(如学习、工作等)

如何实现计时器悬浮窗

在实现计时器悬浮窗时,我们需要使用 Service 来创建悬浮窗,同时使用 WindowManager 来管理窗体的显示。接下来,我们将一步步实现这一功能。

1. 添加权限

首先,在 AndroidManifest.xml 文件中添加悬浮窗所需的权限:

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

2. 创建悬浮窗服务

接下来,我们需要创建一个 Service,用于管理我们的悬浮窗。

import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;

public class TimerService extends Service {
    private WindowManager windowManager;
    private View floatingView;
    private CountDownTimer countDownTimer;
    private long timeRemaining = 60000; // 1 minute

    @Override
    public void onCreate() {
        super.onCreate();
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        createFloatingView();
    }

    private void createFloatingView() {
        floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_view, null);
        
        final TextView timerText = floatingView.findViewById(R.id.timerText);
        Button startButton = floatingView.findViewById(R.id.startButton);

        // Update timer text
        updateTimerText(timerText, timeRemaining);

        startButton.setOnClickListener(v -> startCountDown(timerText)); 

        // Set up layout parameters
        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);

        params.gravity = Gravity.TOP | Gravity.LEFT; 
        params.x = 0;
        params.y = 100;

        windowManager.addView(floatingView, params);

        // Set touch listener for moving the window
        floatingView.setOnTouchListener(new View.OnTouchListener() {
            private int initialX, initialY;
            private float initialTouchX, initialTouchY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        initialX = params.x;
                        initialY = params.y;
                        initialTouchX = event.getRawX();
                        initialTouchY = event.getRawY();
                        return true;
                    case MotionEvent.ACTION_MOVE:
                        params.x = initialX + (int) (event.getRawX() - initialTouchX);
                        params.y = initialY + (int) (event.getRawY() - initialTouchY);
                        windowManager.updateViewLayout(floatingView, params);
                        return true;
                }
                return false;
            }
        });
    }

    private void startCountDown(TextView timerText) {
        countDownTimer = new CountDownTimer(timeRemaining, 1000) {
            public void onTick(long millisUntilFinished) {
                timeRemaining = millisUntilFinished;
                updateTimerText(timerText, timeRemaining);
            }

            public void onFinish() {
                timerText.setText("时间到!");
            }
        }.start();
    }

    private void updateTimerText(TextView timerText, long timeRemaining) {
        int seconds = (int) (timeRemaining / 1000) % 60;
        timerText.setText(String.format("%02d", seconds));
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

3. 布局文件

需要创建一个布局文件 layout_floating_view.xml,用于悬浮窗的显示:

<LinearLayout xmlns:android="
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp"
    android:background="#AAFFFFFF">

    <TextView
        android:id="@+id/timerText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="60"
        android:textSize="30sp" />

    <Button
        android:id="@+id/startButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始" />
</LinearLayout>

4. 启动服务

为了在应用中启动悬浮窗服务,只需在合适的地方调用:

Intent serviceIntent = new Intent(this, TimerService.class);
startService(serviceIntent);

5. 悬浮窗关系图

在实现计时器悬浮窗的过程中,可以使用以下 ER 图展示各组件之间的关系:

erDiagram
    SERVICE {
        string name
        string purpose
    }

    VIEW {
        string layout
        string components
    }

    TIMER {
        string duration
        string remainingTime
    }

    SERVICE ||--o| VIEW : "creates"
    VIEW ||--o| TIMER : "controls"

结论

通过以上内容,我们详细介绍了如何在 Android 中实现计时器悬浮窗的功能。利用 ServiceWindowManager,我们能够创建一个能够在其它应用上方显示的小窗口,并实现简单的倒计时功能。这种悬浮窗设计可以为用户提供更便捷的操作体验,适用于多种应用场景。希望本文的介绍和示例代码能够帮助你在项目中快速实现这一功能。