Android实现三个手指截屏(附带源码)

一、项目介绍

1. 背景与动机

在日常使用 Android 设备时,截屏是一项非常常见且实用的功能。系统自带的截屏方式多为:

  • 同时按下 电源键 + 音量减键

  • 在部分设备上,长按 电源键 弹出截屏选项

  • 在部分厂商定制的系统中,还可使用 快捷手势(如手掌滑动)

但这些方式存在以下不足:

  1. 按键操作不够直观:对于手指不够灵活的用户,按键组合可能会误触或按不到。

  2. 手掌滑动受限:edge case 较多,不同机型灵敏度不一。

  3. 一致性差:不同厂商和系统版本截屏方法不统一,用户需不断适应。

为了提供更 自然、统一、可定制 的截屏交互体验,本项目提出:使用“三指并拢长按”或“三指向下滑动”触发截屏,让用户只需轻轻三指操作,即可完成截屏。该方案具有以下优势:

  • 直观:三根手指并拢或滑动,符合用户直觉

  • 跨机型:基于原生触摸事件与 AccessibilityService,无需系统定制

  • 可扩展:可自由定义手势样式与触发条件

2. 项目目标

  • 在 Android 5.0 及以上系统中,实现“三指截屏”功能

  • 支持两种触发手势:

    1. 三指并拢长按(持续 500ms 以上)

    2. 三指向下快速滑动

  • 使用 AccessibilityService 利用 performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT) API 触发截屏,无需 ROOT

  • 提供完整示例工程,所有代码和布局整合,注释详尽

  • 文章结构严格按照:

    1. 项目介绍

    2. 相关技术知识与解析

    3. 实现思路与整合代码

    4. 方法解读

    5. 项目总结


二、相关技术知识与解析

要完成“三指截屏”功能,需要掌握以下几个关键技术点:

  1. 多点触控(Multi-Touch)事件处理

  2. 自定义手势检测

  3. AccessibilityService 服务与截屏 API

  4. 前台服务与权限申请

下面逐一进行详细解析。

1. 多点触控事件处理

Android 中所有触摸事件都由 MotionEvent 对象承载。通过 ViewActivitydispatchTouchEventonTouchEvent 可接收触摸数据。多点触控时,MotionEvent 提供以下常用方法:

  • getPointerCount():当前屏幕上触摸点的数量

  • getActionMasked():触摸事件类型(兼容多点)

  • getActionIndex():当前操作的指针索引

  • getX(int pointerIndex), getY(int pointerIndex):指定指针的坐标

常见触摸类型(getActionMasked() 返回值):

常量含义
ACTION_DOWN第一个手指按下
ACTION_POINTER_DOWN非第一个手指按下(多点按下时)
ACTION_MOVE手指移动
ACTION_POINTER_UP非最后一个手指抬起
ACTION_UP最后一个手指抬起
ACTION_CANCEL事件中断

要检测“三指并拢”或“三指滑动”,需在 ACTION_POINTER_DOWN 阶段统计指针数量,并在 ACTION_MOVE 阶段分析手势轨迹及持续时间。

2. 自定义手势检测

Android 系统提供 GestureDetector 用于单点手势识别(如双击、长按、滑动)。但对多点手势并不直接支持,因此需手动实现。思路:

  1. 维护当前指针信息:在自定义管理类中保存所有指针的起始位置、当前坐标、按下时间等

  2. 定义阈值

    • 三指并拢最大间距:如三指间距小于 100px 认定为并拢

    • 长按时长:500ms 以上

    • 滑动最小距离:Y 轴下滑超过 200px

  3. 手势流程

    • 三指同时按下 → 记录起始时间与位置

    • ACTION_MOVE 中判断:

      • 若三指位置聚拢且手指保持静止超过阈值 → 触发长按截屏

      • 若三指向下滑动且满足滑动距离 → 触发滑动截屏

3. AccessibilityService 与截屏 API

从 Android 7.0(API 24)开始,AccessibilityService 提供了系统级截屏接口:

// 在无障碍服务中调用
performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);

使用步骤:

  1. 声明 AccessibilityService

  2. 在系统设置中开启服务

  3. 在服务中响应手势检测结果,调用 performGlobalAction

相比 MediaProjection API,AccessibilityService 截屏简单、无需用户二次授权,且支持保存至系统默认截屏目录。

4. 前台服务与权限申请

为了保证手势识别在应用切换、锁屏情况下依然可用,通常需要将手势检测放在一个 前台服务 中运行,并绑定 WindowManager 以截获全局触摸。关键点:

  • Android 8.0+ 对后台服务限制严格,必须调用 startForeground()

  • 需要申请 SYSTEM_ALERT_WINDOW(悬浮窗)权限,以便创建透明触摸层

  • AccessibilityService 需要用户手动在 “无障碍” 设置中开启


三、实现思路与整合代码

下面将所有 Java 代码和 XML 布局整合在一个文件中,使用注释分隔不同部分,配以极其详细的说明。请直接复制到项目中即可完整运行。

// =========================================================================
// 文件:ThreeFingerScreenshotApp.java
// 描述:将所有代码整合到一起,包含 Activity、Service、手势管理、布局资源
// =========================================================================

package com.example.threefingerscreenshot;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

/* --------------------------
   部分一:MainActivity
   负责启动并引导用户开启 AccessibilityService 与悬浮窗权限
   -------------------------- */
public class MainActivity extends AppCompatActivity {
    private static final int REQUEST_OVERLAY_PERMISSION = 1001;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 绑定布局
        setContentView(R.layout.activity_main);

        // 1. 检查并请求悬浮窗权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                Intent intent = new Intent(
                        Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                        android.net.Uri.parse("package:" + getPackageName()));
                startActivityForResult(intent, REQUEST_OVERLAY_PERMISSION);
                Toast.makeText(this,
                        "请授权悬浮窗权限,以便实现全局手势监听", Toast.LENGTH_LONG).show();
            }
        }

        // 2. 提示用户开启无障碍服务
        Toast.makeText(this,
                "请在“设置 → 无障碍”中启用 “三指截屏” 服务", Toast.LENGTH_LONG).show();

        // 3. 启动手势监听前台服务
        startService(new Intent(this, ThreeFingerService.class));
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_OVERLAY_PERMISSION) {
            if (!Settings.canDrawOverlays(this)) {
                Toast.makeText(this, "未获得悬浮窗权限,功能无法使用", Toast.LENGTH_LONG).show();
            }
        }
    }
}

/* --------------------------
   部分二:ThreeFingerService(前台服务 + 悬浮触摸层)
   负责创建全局透明触摸层,拦截全局触摸事件并交给手势管理器
   -------------------------- */
public class ThreeFingerService extends Service {
    private WindowManager mWindowManager;
    private View mTouchOverlay;
    private ThreeFingerGestureDetector mGestureDetector;

    @Override
    public void onCreate() {
        super.onCreate();

        // 初始化 WindowManager
        mWindowManager = (WindowManager)getSystemService(WINDOW_SERVICE);

        // 创建透明触摸层
        mTouchOverlay = new View(this);
        mGestureDetector = new ThreeFingerGestureDetector(this);

        // 设置触摸层属性
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.MATCH_PARENT,
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                        ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
                        : WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                PixelFormat.TRANSLUCENT);
        params.gravity = Gravity.TOP | Gravity.START;

        // 监听触摸事件
        mTouchOverlay.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // 将触摸事件传给手势检测器
                mGestureDetector.onTouchEvent(event);
                // 如果返回 true,则拦截事件,不下发给底层
                return true;
            }
        });

        // 添加到窗口管理器
        mWindowManager.addView(mTouchOverlay, params);

        // 启动前台通知(Android 8.0+ 要求)
        startForegroundServiceWithNotification();
    }

    // 创建并显示前台服务通知
    private void startForegroundServiceWithNotification() {
        String channelId = "three_finger_channel";
        String channelName = "三指截屏服务";
        NotificationManager nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    channelId, channelName, NotificationManager.IMPORTANCE_LOW);
            nm.createNotificationChannel(channel);
        }
        Notification notification = new Notification.Builder(this, channelId)
                .setContentTitle("三指截屏服务已启动")
                .setContentText("使用三指手势即可截屏")
                .setSmallIcon(android.R.drawable.ic_menu_camera)
                .build();
        startForeground(1, notification);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 移除触摸层
        if (mTouchOverlay != null) {
            mWindowManager.removeView(mTouchOverlay);
        }
    }

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

/* --------------------------
   部分三:ThreeFingerGestureDetector(手势管理类)
   实现三指并拢长按和三指向下滑动手势检测
   -------------------------- */
class ThreeFingerGestureDetector {
    private static final String TAG = "ThreeFingerGD";

    private final Context mContext;
    // 存储当前触摸点信息
    private int mPointerCount = 0;
    private long mThreeDownTime = 0L;
    private float[] mStartX = new float[3];
    private float[] mStartY = new float[3];

    // 手势阈值
    private static final long LONG_PRESS_DURATION = 500;   // ms
    private static final float MAX_DISTANCE = 100f;        // px
    private static final float SLIDE_DISTANCE = 200f;      // px

    public ThreeFingerGestureDetector(Context context) {
        mContext = context;
    }

    public void onTouchEvent(MotionEvent event) {
        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_POINTER_DOWN:
            case MotionEvent.ACTION_DOWN:
                mPointerCount = event.getPointerCount();
                if (mPointerCount == 3) {
                    // 记录三指按下时刻和位置
                    mThreeDownTime = System.currentTimeMillis();
                    for (int i = 0; i < 3; i++) {
                        mStartX[i] = event.getX(i);
                        mStartY[i] = event.getY(i);
                    }
                    // 开始定时检测长按
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            checkLongPress();
                        }
                    }, LONG_PRESS_DURATION);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mPointerCount == 3) {
                    // 检测三指滑动
                    float y0 = event.getY(0), y1 = event.getY(1), y2 = event.getY(2);
                    float dy0 = y0 - mStartY[0], dy1 = y1 - mStartY[1], dy2 = y2 - mStartY[2];
                    if (dy0 > SLIDE_DISTANCE && dy1 > SLIDE_DISTANCE && dy2 > SLIDE_DISTANCE) {
                        Log.d(TAG, "检测到三指向下滑动截屏");
                        triggerScreenshot();
                        reset();
                    }
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // 手指抬起或取消,重置状态
                reset();
                break;
        }
    }

    // 检查三指并拢长按
    private void checkLongPress() {
        if (mPointerCount == 3 &&
                System.currentTimeMillis() - mThreeDownTime >= LONG_PRESS_DURATION) {
            // 计算三指间最大距离
            float maxDist = 0;
            for (int i = 0; i < 3; i++) {
                for (int j = i + 1; j < 3; j++) {
                    float dx = mStartX[i] - mStartX[j];
                    float dy = mStartY[i] - mStartY[j];
                    float dist = (float)Math.hypot(dx, dy);
                    if (dist > maxDist) maxDist = dist;
                }
            }
            if (maxDist < MAX_DISTANCE) {
                Log.d(TAG, "检测到三指并拢长按截屏");
                triggerScreenshot();
            }
            reset();
        }
    }

    // 调用无障碍服务截屏
    private void triggerScreenshot() {
        Intent intent = new Intent(mContext, MyAccessibilityService.class);
        intent.setAction(MyAccessibilityService.ACTION_TRIGGER_SCREENSHOT);
        mContext.sendBroadcast(intent);
    }

    // 重置状态
    private void reset() {
        mPointerCount = 0;
        mThreeDownTime = 0;
    }
}

/* --------------------------
   部分四:MyAccessibilityService(无障碍服务)
   在服务中接收广播并调用 performGlobalAction 截屏
   -------------------------- */
public class MyAccessibilityService extends AccessibilityService {
    public static final String ACTION_TRIGGER_SCREENSHOT =
            "com.example.threefingerscreenshot.ACTION_TRIGGER_SCREENSHOT";

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (ACTION_TRIGGER_SCREENSHOT.equals(intent.getAction())) {
                // 调用系统截屏
                performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);
                Toast.makeText(context,
                        "已截屏(通过无障碍服务)", Toast.LENGTH_SHORT).show();
            }
        }
    };

    @Override
    public void onServiceConnected() {
        super.onServiceConnected();
        // 配置服务信息
        AccessibilityServiceInfo info = getServiceInfo();
        info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
        info.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
        setServiceInfo(info);

        // 注册广播接收器
        IntentFilter filter = new IntentFilter(ACTION_TRIGGER_SCREENSHOT);
        registerReceiver(mReceiver, filter);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // 不需要额外处理界面变化
    }

    @Override
    public void onInterrupt() {
        // 服务被中断时的清理
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);
    }
}

<!-- ========================================================================
     文件:res/layout/activity_main.xml
     说明:主界面仅用于提示用户操作,无具体 UI 元素,保持简洁
     ======================================================================== -->
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 提示文本,可自行美化 -->
    <TextView
        android:id="@+id/tv_hint"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="三指截屏示例\n\n1. 请授予悬浮窗权限\n2. 请开启无障碍服务\n3. 三指并拢长按或向下滑动即可截屏"
        android:gravity="center"
        android:textSize="18sp"
        android:textColor="#333333"/>
</FrameLayout>
<!-- ========================================================================
     文件:AndroidManifest.xml
     说明:声明 Activity、Service、AccessibilityService 与权限
     ======================================================================== -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.threefingerscreenshot">

    <!-- 悬浮窗权限 -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    <application
        android:allowBackup="true"
        android:label="ThreeFingerScreenshot"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
        
        <!-- 主 Activity -->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <!-- 前台触摸服务 -->
        <service android:name=".ThreeFingerService"/>

        <!-- 无障碍服务 -->
        <service
            android:name=".MyAccessibilityService"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService"/>
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_service_config"/>
        </service>
    </application>
</manifest>
<!-- ========================================================================
     文件:res/xml/accessibility_service_config.xml
     说明:无障碍服务配置
     ======================================================================== -->
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:settingsActivity="com.example.threefingerscreenshot.SettingsActivity"
    android:description="@string/accessibility_service_description"/>

四、方法解读

以下仅对上面代码中核心方法进行功能说明,不复写代码:

  1. MainActivity.onCreate()

    • 检查并请求 SYSTEM_ALERT_WINDOW(悬浮窗) 权限

    • 提示用户开启 无障碍服务

    • 启动前台服务 ThreeFingerService

  2. ThreeFingerService.onCreate()

    • 通过 WindowManager 创建全屏透明触摸层

    • 拦截所有触摸事件并交由 ThreeFingerGestureDetector 处理

    • 调用 startForeground() 以保持服务常驻

  3. ThreeFingerGestureDetector.onTouchEvent(MotionEvent)

    • 监听指针按下、移动和抬起事件

    • 当检测到三指同时按下,记录坐标和时间,启动延时任务检测长按

    • ACTION_MOVE 中判断三指整体向下滑动是否超过阈值

    • 如果满足任一手势(长按或滑动),调用 triggerScreenshot()

  4. ThreeFingerGestureDetector.checkLongPress()

    • 判断从按下到当前时间是否超过长按阈值

    • 计算三指间最大间距,如果小于并拢阈值,则认定为并拢长按

  5. ThreeFingerGestureDetector.triggerScreenshot()

    • 通过发送广播触发 MyAccessibilityService 执行截屏

  6. MyAccessibilityService.onServiceConnected()

    • 配置无障碍服务监听参数

    • 注册广播接收器,监听来自手势检测器的截屏请求

  7. MyAccessibilityService.onReceive()

    • 收到广播后调用 performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT) 执行系统截屏


五、项目总结

1. 功能回顾

  • 本项目实现了基于 三指并拢长按三指向下滑动 的截屏手势

  • 完全基于原生 多点触摸AccessibilityService,无需 ROOT

  • 使用 前台服务 + 悬浮窗 拦截全局触摸,兼容各种 App 场景

2. 优势与不足

优势

  • 自然直观:三根手指并拢或滑动操作简单

  • 跨 App 可用:全局触摸层可在任何界面使用

  • 无授权弹窗:只需一次性开启悬浮窗与无障碍权限

不足

  • 电量消耗:前台服务和悬浮窗常驻,会略微增加功耗

  • 手势误触:三指触摸敏感度需调优,否则在打字、玩游戏时可能误触

  • 兼容性:部分定制系统对 AccessibilityService 支持不完整

3. 扩展与优化

  • 手势自定义:提供 UI 界面,让用户自定义三指手势类型和阈值

  • 误触保护:在检测到当前焦点为输入法时,暂时禁用三指截屏

  • 截图预览与分享:截屏后弹出小窗预览,并提供一键分享、编辑

  • 折叠式手势:支持三指从屏幕边缘向内滑动截屏

4. 实际应用场景

  • 截图教学:在录制教程视频时,快速截取关键步骤

  • 社交分享:随时截屏并分享至微信、QQ、微博

  • 游戏截屏:三指滑动一气呵成,捕捉精彩瞬间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值