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日
    浏览(87)
  • 为社会开发,无障碍开发,开发人员的公益时间

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

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

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

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

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

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

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

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

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

    一、使用的工具 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日
    浏览(51)
  • 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日
    浏览(48)
  • 有 AI,无障碍,AIoT 设备为视障人群提供便利

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

    2024年02月08日
    浏览(59)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包