android AccessibilityService无障碍功能开发,实现自动化测试

这篇具有很好参考价值的文章主要介绍了android AccessibilityService无障碍功能开发,实现自动化测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

android AccessibilityService无障碍功能开发,实现自动化测试,这里使用抖音为例子,仅供技术研究学习使用。

使用方法

安装好APP后,需要打开无障碍功能,打开后,在次打开抖音APP,随便找一个直播间,上下滑动切换直接后,实现模拟点击屏幕,可以自动完成关注。

代码如下

package com.nyw.testclick;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.GestureDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        boolean enabled = isAccessibilityServiceEnabled(this, MyAccessibilityService.class);
        //判断是否安装抖音
        boolean exist1 = checkAppInstalled(MainActivity.this, "com.ss.android.ugc.aweme");
        //抖音极速版
        //boolean exist1 = checkAppInstalled(getContext(), "com.ss.android.article.video");
        //抖音火山版
        //boolean exist1 = checkAppInstalled(getContext(), "com.ss.android.ugc.live");
        if (exist1) {
            Intent intent = new Intent();
            //抖音 打开个人中心  104248958804 需要去获取抖音的UserId
//            intent.setData(Uri.parse("snssdk1128://user/profile/104248958804"));
            // 打开首页
            intent.setData(Uri.parse("snssdk1128://feed?refer=web&gd_label=1"));
            //抖音极速版
            //intent.setData(Uri.parse("snssdk1112://user/profile/104248958804"));
            //抖音火山版
            //intent.setData(Uri.parse("snssdk1112://profile?id=104248958804"));
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        } else {
            Toast.makeText(MainActivity.this, "请先安装此应用", Toast.LENGTH_SHORT).show();
        }

        if (enabled==false){
            final String actionOpen=Settings.ACTION_ACCESSIBILITY_SETTINGS;//系统辅助功能服务
            Intent intent=new Intent(actionOpen);
            startActivity(intent);

        }
    }

    /**
     * 获取已启用的辅助功能服务
     * @param context
     * @param service
     * @return
     */
    public static boolean isAccessibilityServiceEnabled(Context context, Class<? extends AccessibilityService> service) {
        AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
        List<AccessibilityServiceInfo> enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);

        for (AccessibilityServiceInfo enabledService : enabledServices) {
            ServiceInfo enabledServiceInfo = enabledService.getResolveInfo().serviceInfo;
            if (enabledServiceInfo.packageName.equals(context.getPackageName()) && enabledServiceInfo.name.equals(service.getName()))
                return true;
        }

        return false;
    }

    /**
     * 判断是否安装指定包名应用
     * @param context
     * @param pName
     * @return
     */
    private boolean checkAppInstalled(Context context, String pName) {
        if (pName == null || pName.isEmpty()) {
            return false;
        }
        final PackageManager packageManager = context.getPackageManager();
        List<PackageInfo> info = packageManager.getInstalledPackages(0);
        if (info == null || info.isEmpty()) {
            return false;
        }
        for (int i = 0; i < info.size(); i++) {
            if (pName.equals(info.get(i).packageName)) {
                return true;
            }
        }
        return false;
    }





}

 自定义一个服务MyAccessibilityService,继承AccessibilityService,实现2个方法,重写一个方法,代码如下

package com.nyw.testclick;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.GestureDescription;
import android.content.Context;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;

import androidx.annotation.RequiresApi;

import java.util.List;

/**
 * 参考文章https://blog.csdn.net/u012758088/article/details/53899485
 * 自定义辅助功能服务类AccessibilityService:实现自动化操作的系统服务
 */
public class MyAccessibilityService extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        //这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的
        //event.getSource()  这是当前event的节点信息
       // AccessibilityService.getRootInActiveWindow();  获取到当前活跃中本服务的可检索到窗口的根节点
       // AccessibilityNodeInfo.recycle()//为避免创建重复的实例通过recycle方法回收掉nodeInfo
        //event.TYPE_NOTIFICATION_STATE_CHANGED  基本窗口view的变化都可以使用这个type来监听
        //event.TYPE_WINDOW_STATE_CHANGED  打开popupwindow,菜单,对话框时候会触发
        //event.TYPE_WINDOW_CONTENT_CHANGED  更加精确的代表了基于当前event.source中的子view的内容变化
        //event.TYPE_WINDOWS_CHANGED  窗口的变化
        //获取到当前活跃中本服务的可检索到窗口的根节点 两种方式获取的childNode个数不一致
//        AccessibilityNodeInfo mNodeInfo1 = getRootInActiveWindow();//获取NodeInfo
//        AccessibilityNodeInfo mNodeInfo2= event.getSource();//获取NodeInfo
        //查找我们需要做操作的view
//        List<AccessibilityNodeInfo> listNodes1 =mNodeInfo1. findAccessibilityNodeInfosByViewId("id");//操作的view,查找我们需要操作的对象方法之一
//        List<AccessibilityNodeInfo> listNodes2=mNodeInfo2.findAccessibilityNodeInfosByText("id");//操作的view,查找我们需要操作的对象方法之一
       // findFocus(0);//查找拥有特定焦点类型的控件
       // getRootInActiveWindow();//如果配置能够获取窗口内容,则会返回当前活动窗口的根结点
//        getServiceInfo();//获取当前服务的配置信息
//        performGlobalAction(0);//执行全局操作,比如返回,回到主页,打开最近等操作
//        event.getClassName();//获取事件源对应类的类型,比如点击事件是有某个Button产生的,那么此时获取的就是Button的完整类名
//        event. getText();//获取事件源的文本信息,比如事件是有TextView发出的,此时获取的就是TextView的text属性.如果该事件源是树结构,那么此时获取的是这个树上所有具有text属性的值的集合
//        event.isEnabled();//事件源(对应的界面控件)是否处在可用状态
//        event.getItemCount();//如果事件源是树结构,将返回该树根节点下子节点的数量

        try {

            int eventType = event.getEventType();//事件类型
            String packageName = event.getPackageName().toString();
            String className = event.getClassName().toString();
            List<AccessibilityNodeInfo> list5 = null;
            List<AccessibilityNodeInfo> list6 = null;
            List<AccessibilityNodeInfo> list7 = null;
            AccessibilityNodeInfo rootNodeInfo = getRootInActiveWindow();

            if ("android.widget.ImageView".equals(event.getClassName().toString())) {
                perforGlobalClick("com.ss.android.ugc.aweme:id/gsb");
                Log.i("sdfkslfsfks", "用户名:" + getTextById("com.ss.android.ugc.aweme:id/m39"));
                Log.i("sdfkslfsfks", "文本信息:" + getTextById(" id:com.ss.android.ugc.aweme:id/ag7"));
            }

            switch (eventType) {
                case AccessibilityEvent.TYPE_VIEW_CLICKED:
                    // 界面点击
                    Log.i("sdfkslfsfks", "页面点击了");
                    Log.d("sdfkslfsfks", event.getClassName() + "                    " + event.getSource().getViewIdResourceName());
//                logViewHierarchy(getRootInActiveWindow(), 0);
//                perforGlobalClick("com.ss.android.ugc.aweme:id/efr");
                    break;
                case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
                    // 界面文字改动
                    Log.i("sdfkslfsfks", "界面文字改动");
                    perforGlobalClick("com.ss.android.ugc.aweme:id/gsb");
                    break;
                case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                    //滚动视图的事件。此事件类型通常不直接发送
                    Log.e("sdfkslfsfks", "onServiceConnected:" + "实现辅助功能");
                    Log.d("TAG", "packageName = " + packageName + ", className = " + className);

                    //滑动就自动点赞及关注
                    perforGlobalClick("com.ss.android.ugc.aweme:id/gsb");

                    if (className.equals("com.lynx.tasm.behavior.KeyboardMonitor")) {
                        Log.e("sdfkslfsfks", "执行了搜索按钮");
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                            list5 = rootNodeInfo.findAccessibilityNodeInfosByViewId("com.ss.android.ugc.aweme:id/rzy");
                        }
                        if (null != list5) {
                            for (AccessibilityNodeInfo info : list5) {
                                clickByNode(this, info);
                            }
                        }
                    }

                    if (className.equals("androidx.recyclerview.widget.RecyclerView")) {
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                            list6 = rootNodeInfo.findAccessibilityNodeInfosByText("用户");
                        }
                        if (null != list6) {
                            for (AccessibilityNodeInfo info : list6) {
                                Log.e("sdfkslfsfks", info.toString());
                                clickByNode(this, info.getParent());
                            }
                        }
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                            list7 = rootNodeInfo.findAccessibilityNodeInfosByText("关注");
                        }
                        if (null != list7) {
                            for (AccessibilityNodeInfo info : list7) {
                                Log.e("sdfkslfsfks", info.toString());
                            }
                        }
                    }

                    //获取根节点
                    AccessibilityNodeInfo rootNode = getRootInActiveWindow();
                    //匹配Text获取节点
                    List<AccessibilityNodeInfo> list1 = rootNode.findAccessibilityNodeInfosByText("match_text");
                    //匹配id获取节点
                    List<AccessibilityNodeInfo> list2 = rootNode.findAccessibilityNodeInfosByViewId("match_id");
                    //获取子节点
                    AccessibilityNodeInfo infoNode = rootNode.getChild(0);

                    break;

            }
        }catch (Exception exception){}

    }

    @Override
    public void onInterrupt() {
        //在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。
       // disableSelf();//禁用服务。调用此方法后,服务将被禁用,设置将显示它已关闭。
    }

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        //重载的方法
        //系统成功绑定该服务时被触发调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作
        //配置,可在这里配制,也可以在values中添加配制文件进行配制,2种方法二选一
//        AccessibilityServiceInfo accessibilityServiceInfo=new AccessibilityServiceInfo();
//        accessibilityServiceInfo.eventTypes=AccessibilityEvent.TYPES_ALL_MASK;
//        accessibilityServiceInfo.feedbackType=AccessibilityServiceInfo.FEEDBACK_GENERIC;
//        accessibilityServiceInfo.flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;//获取View的Id
//        accessibilityServiceInfo.notificationTimeout=100;
//        accessibilityServiceInfo.packageNames=new String[]{"com.nyw.testclick","com.ss.android.ugc.aweme"};//自动点击的包名
//        setServiceInfo(accessibilityServiceInfo);//设置当前服务的配置信息

        }

//    @Override
//    protected boolean onKeyEvent(KeyEvent event) {
//        //如果允许服务监听按键操作,该方法是按键事件的回调,需要注意,这个过程发生了系统处理按键事件之前
//        return super.onKeyEvent(event);
//    }

    /**
     * 借助服务管理器AccessibilityManager来判断,但是该方法不能检测app本身开启的服务。
     * @param name
     * @return
     */
    private boolean enabled(String name) {
        AccessibilityManager am = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
        List<AccessibilityServiceInfo> serviceInfos = am
                .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
        List<AccessibilityServiceInfo> installedAccessibilityServiceList = am
                .getInstalledAccessibilityServiceList();
        for (AccessibilityServiceInfo info : installedAccessibilityServiceList) {
            Log.d("MainActivity", "all -->" + info.getId());
            if (name.equals(info.getId())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 系统属性都在settings中进行设置,比如wifi,蓝牙状态等,而这些信息主要是存储在settings对应的的数据库中(system表和serure表)
     * ,同样我们也可以通过直接读取setting设置来判断相关服务是否开启
     * @param service
     * @return
     */
    private boolean checkStealFeature1(String service) {
        int ok = 0;
        try {
            ok = Settings.Secure.getInt(getApplicationContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {

        }

        TextUtils.SimpleStringSplitter ms = new TextUtils.SimpleStringSplitter(':');
        if (ok == 1) {
            String settingValue = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (settingValue != null) {
                ms.setString(settingValue);
                while (ms.hasNext()) {
                    String accessibilityService = ms.next();
                    if (accessibilityService.equalsIgnoreCase(service)) {
                        return true;
                    }

                }
            }
        }
        return  false;
    }



    /**
     * 判断页面是否有此文本的view
     */
    private boolean viewByText(String str) {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (nodeInfo != null) {
            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(str);
            nodeInfo.recycle();
            for (AccessibilityNodeInfo item : list) {
                if (str.equals(item.getText().toString())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获取指定ID的文本
     */
    private String getTextById(String id) {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (nodeInfo != null) {
            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId(id);
            nodeInfo.recycle();
            for (AccessibilityNodeInfo item : list) {
                return item.getText() + "";
            }
        }
        return "";
    }

    /**
     * 按钮点击事件(View必须是可点击的clickable=true,在uiautomatorviewer中可查看View的属性)
     * 根据ID点击某个视图
     */
    public void perforGlobalClick(String id) {
        try {
            AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
            if (nodeInfo != null) {
                List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId(id);
                nodeInfo.recycle();
                for (AccessibilityNodeInfo item : list) {
                    item.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                }
            }
        }catch (Exception exception){}
    }

    /**
     * 基本的递归函数来记录整个可访问性节点视图层次结构
     * @param nodeInfo
     * @param depth
     */
    public static void logViewHierarchy(AccessibilityNodeInfo nodeInfo, final int depth) {
        if (nodeInfo == null) return;

        String spacerString = "";

        for (int i = 0; i < depth; ++i) {
            spacerString += '-';
        }
        //Log the info you care about here... I choce classname and view resource name, because they are simple, but interesting.
        Log.d("sdfkslfsfks", spacerString + nodeInfo.getClassName() + "                    " + nodeInfo.getViewIdResourceName());

        for (int i = 0; i < nodeInfo.getChildCount(); ++i) {
            logViewHierarchy(nodeInfo.getChild(i), depth+1);
        }
    }

    /**
     * 实现位置坐标点击
     * @param service
     * @param nodeInfo
     * @return
     */
    public static boolean clickByNode(AccessibilityService service, AccessibilityNodeInfo nodeInfo) {

        if (service == null || nodeInfo == null) {
            return false;
        }

        Rect rect = new Rect();
        nodeInfo.getBoundsInScreen(rect);
        int x = rect.centerX();
        int y = rect.centerY();
        Log.e("acc_", "要点击的像素点在手机屏幕位置::" + rect.centerX() + " " + rect.centerY());
        Point point = new Point(x, y);
        GestureDescription.Builder builder = new GestureDescription.Builder();
        Path path = new Path();
        path.moveTo(point.x, point.y);
        builder.addStroke(new GestureDescription.StrokeDescription(path, 0L, 100L));
        GestureDescription gesture = builder.build();

        boolean isDispatched = service.dispatchGesture(gesture, new AccessibilityService.GestureResultCallback() {
            @Override
            public void onCompleted(GestureDescription gestureDescription) {
                super.onCompleted(gestureDescription);
//                LogUtil.d(TAG, "dispatchGesture onCompleted: 完成...");
            }

            @Override
            public void onCancelled(GestureDescription gestureDescription) {
                super.onCancelled(gestureDescription);
//                LogUtil.d(TAG, "dispatchGesture onCancelled: 取消...");
            }
        }, null);

        return isDispatched;
    }




}

AndroidManifest.xml文件配制如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.nyw.testclick">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TestClick"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

<!--        配置<intent-filter>,其name为固定的android.accessibilityservice.AccessibilityService-->
<!--        声明BIND_ACCESSIBILITY_SERVICE权限,以便系统能够绑定该服务(4.1版本后要求)-->
        <service
            android:name=".MyAccessibilityService"
            android:enabled="true"
            android:exported="true"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService"/>
            </intent-filter>
            <!--        代码中配制有了,这里不需要xml配制的,可二选一-->
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessible_service_config" />
        </service>


    </application>

</manifest>

 在xml中添加一个accessible_service_config文件,代码如下

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault|flagRequestEnhancedWebAccessibility|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagReportViewIds|flagRequestTouchExplorationMode"
    android:canPerformGestures="true"
    android:packageNames="com.ss.android.ugc.aweme"
    android:canRetrieveWindowContent="true"
    android:canRequestFilterKeyEvents="true"
    android:canRequestEnhancedWebAccessibility="true"
    android:settingsActivity="com.nyw.testclick.MyAccessibilityService"
    android:notificationTimeout="2000" />
<!--    android:canPerformGestures="true" 允许进行手势操作-->
<!--    android:canRetrieveWindowContent="true" 允许读取页面上的数据-->
<!--    android:accessibilityFlags="flagReportViewIds"获取view id 上线去掉,找VIEW id使用-->

 文章来源地址https://www.toymoban.com/news/detail-518967.html

到了这里,关于android AccessibilityService无障碍功能开发,实现自动化测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 手机APP-小米手机无障碍功能开启失败

    在设置中关闭以下三个设置: 不要设置为全面屏模式,不过可以在打开无障碍功能后再换回来。     ————————————————————— 以上就是今日博客的全部内容了 创作不易,若对您有帮助,可否点赞、关注一二呢, 感谢支持

    2024年02月08日
    浏览(49)
  • 为社会开发,无障碍开发,开发人员的公益时间

    无障碍开发是指开发人员在设计和开发软件时,考虑到残障人士的需求,以使他们能够享受到与其他人相同的体验。 无障碍开发是一种道德责任,也是一种商业机会,因为它可以为更广泛的人群提供无障碍产品和体验。 无障碍开发的重要性 无障碍开发可以使许多人受益,包

    2023年04月08日
    浏览(35)
  • [游戏开发]Unity颜色矫正无障碍方案

    之前有在关注色盲视觉纠正问题,最近在调整游戏的时候就打算把这个用上。 色弱色盲,这其实算是一种误称吧,只是人类中的少数派,只不过看到的颜色和大部分人不一样。下文用,视觉少数者,来称呼吧。 本质上是因为感知颜色的细胞发生突变,感知与大部分人有差异

    2024年02月15日
    浏览(31)
  • unidbg-补环境之无障碍模式

    2024年02月16日
    浏览(32)
  • auto.js autojs pro9 autox.js实现adb自动化测试脚本开发自动生成代码 防无障碍检测

    不需要开无障碍就可以实现自动化 ,功能上和无障碍效果一样, 但是可以过目标app的检测,因为软件基本上都不检测adb(usb调试), 游戏脚本的福音 ,最主要是可以 直接生成自动化代码 ,所以写adb的自动化脚本 和无障碍的自动化脚本都同样简单高效,傻瓜式操作。 对于

    2024年01月19日
    浏览(47)
  • Facebook的可访问性使命:构建无障碍社交空间

    在当今数字时代,社交媒体不仅是人们交流、分享和连接的平台,更是构建开放、包容社交环境的关键。Facebook,作为全球最大的社交媒体平台之一,积极推动着可访问性使命,致力于构建一个无障碍的社交空间,使每个用户都能平等参与其中。本文将深入探讨Facebook在可访问

    2024年01月23日
    浏览(42)
  • 无障碍工具条在前端项目中的使用

    一、使用的工具 https://gitee.com/tywAmblyopia/ToolsUI 二、使用 VUE中使用 -1.拉取代码 -2.将 canyou 文件夹放到 public 目录下 -3.在 public 文件夹下的 index.html 文件中 /head标签前,引用v1.8以上的jquery.min.js(原网站已引用v1.8以上的jquery跳过此步骤)。 -4.在 public 文件夹下的 index.html 文件中 /he

    2024年02月14日
    浏览(39)
  • 有 AI,无障碍,AIoT 设备为视障人群提供便利

    据世界卫生组织统计,全球共 22 亿人视力受损,包含 2.85 亿视障人群和 3,900 万全盲人群。而且,这一数字将随老龄化加剧不断增加。 虽然视障人群面临着诸多不便,但是针对视障人群的辅助设备却存在成本高、维护困难、操作复杂等问题,很难满足他们的生活需求。 为此,

    2024年02月08日
    浏览(46)
  • flutter flutter_accessibility_service无障碍服务

    flutter_accessibility_service a plugin for interacting with Accessibility Service in Android. Accessibility services are intended to assist users with disabilities in using Android devices and apps, or I can say to get android os events like keyboard key press events or notification received events etc. for more info check Accessibility Service Installation

    2024年02月10日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包