牙叔教程简单易懂效果预览 如上图所示,一共有三个悬浮存储:右侧的悬浮球十字架悬浮窗翻译的内容悬浮窗 建议只用两个悬浮窗,也就是把十字架和翻译内容合并为一个悬浮窗,这样用户在触摸移动的时候, 有更大的触摸区域。 思路十字架指针确定单词位置用autojs9提供的插件MLKitOCR,识别单词周围的内容ocr识别的结果中,带有文字的recct信息,这些rect信息和十字架的位置对比一下,就知道是哪个单词了网上找了个离线的词典,词汇量几十万用sqlite查询,速度很快 离线词典制作教程 请参考:autojs查找相同词根的单词 https:www。yuque。comyashujsbfug6uwbg0or 这个是把csv的单词数据转成了db, 本教程不提供单词数据库,若有需要,下载那个开源的单词库,跟着教程做,自己转db 多个分辨率保持大致相似的布局 我们来看看多个分辨率的布局是否一样 这是模拟器的三种分辨率,可以看到布局几乎一模一样,这是怎么做的呢? 布局的宽高使用的都是px,按照同样的比例来决定多宽多高, 文字大小使用的也是他们使用的是同一个比例;letconfig{cross:{widthRatio:0。05,},text:{sizeRatio:0。04,},translationHoveringWindow:{widthRatio:0。4,heightRatio:0。2,},}; 因此,你看到的是同一个比例的布局,但是是不同的宽高,达到了在视觉上保持一致的效果 悬浮球 为了尽量少的遮挡用户的屏幕,我们将其透明度设置为了0。5alpha0。5 悬浮球随意拖动 他可以随意拖动 这个拖动因为比较常用,建议大家封装一下; 我的代码已经写完了,就不改了,这里可以提供一个封装例子 首先写一个悬浮窗用来测试letwindowfloaty。rawWindow(verticalbuttonidmovetext移动wautohautovertical);setInterval((){},1000); 然后是封装; 封装不只是移动,后期可能还要添加各种按下拖动抬起的行为,因此封装的参数对象有多个属性letwindowData{window:window,moveViewId:move,isTouching:false,downCallback:function(){},moveCallback:function(){},upCallback:function(){},onClick:function(){},}; 但是你肯定不想写这么多属性,只有前两个是必须的,其他的可有可无,我们可以设置默认属性,然后和用户传进来的对象合并letwindowData{window:window,moveViewId:move,};letwindowDefaultData{isTouching:false,downCallback:function(){},moveCallback:function(){},upCallback:function(){},onClick:function(){},};Object。assign(windowDefaultData,windowData); 我们要让用户传入的参数对象覆盖掉默认的对象,因此,用户的参数作为Object。assign第二个参数 接下来是封装触摸移动; 第一:做参数校验,如果传递的参数对象没有必备的属性,我们就抛出错误functionmakeWindowMoveable(windowData){if(!windowData。window){thrownewError(windowData。windowisundefined);}if(!windowData。moveViewId){thrownewError(windowData。moveViewIdisundefined);} 触摸移动的基础封装varx0,y0;记录按键被按下时的悬浮窗位置varwindowX,windowY;记录按键被按下的时间以便判断长按等动作vardownTletviewwindow〔windowData。moveViewId〕;view。setOnTouchListener(function(view,event){switch(event。getAction()){caseevent。ACTIONDOWN:xevent。getRawX();yevent。getRawY();windowXwindow。getX();windowYwindow。getY();caseevent。ACTIONMOVE:移动手指时调整悬浮窗位置window。setPosition(windowX(event。getRawX()x),windowY(event。getRawY()y));caseevent。ACTIONUP:}}); 在该基础上添加各种callbackswitch(event。getAction()){caseevent。ACTIONDOWN:windowDefaultData。downCallback();caseevent。ACTIONMOVE:windowDefaultData。moveCallback();caseevent。ACTIONUP:windowDefaultData。upCallback();} 再加上点击和长按操作switch(event。getAction()){caseevent。ACTIONUP:letupTimenewDate()。getTime();lettimeupTimedownT移动距离letdistanceMath。sqrt(Math。pow(event。getRawX()x,2)Math。pow(event。getRawY()y,2));if(time150distance5){windowDefaultData。onClick();}elseif(time2000distance5){windowDefaultData。longClick();}} 完整封装如下letwindowfloaty。rawWindow(verticalbuttonidmovetext移动wautohautovertical);setInterval((){},1000);letwindowData{window:window,moveViewId:move,downCallback:function(){log(downCallback);},moveCallback:function(){log(moveCallback);},upCallback:function(){log(upCallback);},onClick:function(){log(onClick);},longClick:function(){log(longClick);},};makeWindowMoveable(windowData);functionmakeWindowMoveable(windowData){if(!windowData。window){thrownewError(windowData。windowisundefined);}if(!windowData。moveViewId){thrownewError(windowData。moveViewIdisundefined);}letwindowDefaultData{isTouching:false,downCallback:function(){},moveCallback:function(){},upCallback:function(){},onClick:function(){},longClick:function(){},};Object。assign(windowDefaultData,windowData);记录按键被按下时的触摸坐标varx0,y0;记录按键被按下时的悬浮窗位置varwindowX,windowY;记录按键被按下的时间以便判断长按等动作vardownTletviewwindow〔windowData。moveViewId〕;view。setOnTouchListener(function(view,event){switch(event。getAction()){caseevent。ACTIONDOWN:windowDefaultData。isTxevent。getRawX();yevent。getRawY();windowXwindow。getX();windowYwindow。getY();downTimenewDate()。getTime();windowDefaultData。downCallback();caseevent。ACTIONMOVE:移动手指时调整悬浮窗位置window。setPosition(windowX(event。getRawX()x),windowY(event。getRawY()y));windowDefaultData。moveCallback();caseevent。ACTIONUP:windowDefaultData。isTwindowDefaultData。upCallback();letupTimenewDate()。getTime();lettimeupTimedownT移动距离letdistanceMath。sqrt(Math。pow(event。getRawX()x,2)Math。pow(event。getRawY()y,2));if(time150distance5){windowDefaultData。onClick();}elseif(time2000distance5){windowDefaultData。longClick();}}});returnwindowDefaultD} 这个封装能满足一部分使用需求,在不同的使用场景仍要按需修改悬浮窗吸附屏幕边缘 拖动以后会自动吸附到屏幕边缘; 靠近左边就贴到左边; 靠近右边就贴到右边; 位置会记住,下次重启代码,悬浮窗还在上次的位置 上面那个触摸还封装了一个是否正在触摸的属性,为什么要加这个属性呢? 就是为了这个悬浮窗屏幕吸附效果; 吸附屏幕边缘,我是用的是定时器,每隔2秒,检查一次 在我们移动悬浮窗的时候,我们正在调整悬浮窗位置,这个时候,你肯定不想吸附到屏幕边缘; 不然,他会干扰你调整位置 为了获取悬浮窗的状态,这个封装方法makeWindowMoveable必须返回windowDefaultDreturnwindowDefaultD 然后在你需要使用触摸状态的地方,来获取触摸状态:windowDefaultData。isTouching 屏幕吸附边缘,同样会记忆,使用storage持久化存储 悬浮窗的实例化 悬浮窗还有各种隐藏,显示,以及一些其他方法要写,因此建议封装为一个类,这样我们可以更方便的调用各种方法,而不用到处翻页找方法在哪里, 如果不这样做,那就会越写越烦躁,一会找这个方法,一会找那个方法,烦死了functionCrossWindow(){this。windowfloaty。rawWindow(vertical。。。vertical);this。isSthis。storagestorages。create(crossWindow);}CrossWindow。prototype。getCrossPointfunction(){。。。};CrossWindow。prototype。togglefunction(){。。。};CrossWindow。prototype。hidefunction(){。。。};。。。。。。 悬浮窗显示和隐藏 隐藏是将悬浮窗移动到屏幕之外来实现的this。window。setPosition(10000,10000); 显示就是移动到屏幕之内; 同样的,悬浮窗的位置也会记忆,也是用storage持久化存储 十字架 十字架是一个frame布局,横着一个view,竖着一个view,就成了一个十字架 当然了,你也可以用图片,或者canvas自己画 十字架的中心点在屏幕上的位置 获取一个view在屏幕上的位置信息,我们封装了一个方法functiongetViewRect(view){letlocationOnScreenview。getLocationOnScreen();letframenewandroid。graphics。Rect();view。getHitRect(frame);letwidthframe。width();letheightframe。height();return{x:locationOnScreen〔0〕,y:locationOnScreen〔1〕,width:width,height:height,};} 获取十字架中心点,就在获取位置信息后,加减乘除,很简单就不说了 点击翻译按钮,识别单词 点击翻译按钮后,我们先获取十字架的中心点,然后调用autojs9 提供的插件MLKitOCR,这个是谷歌开发的工具,识别英文效果又快又好, ocr的识别结果带有单词的rect矩形坐标数据,我们和十字架中心点对比, 谁包含十字架,谁就是十字架指向的单词 封装的方法是判断一个点,是否在一个rect中functionisPointInRect(point,rect){letxpoint。x;letypoint。y;letx1rect。lety1rect。letx2rect。lety2rect。if(xx1xx2yy1yy2){}} 翻译单词 单词库上面已经说过了,我们使用安卓的sqlite来查询单词,命令是db。rawQuery(SELECTFROMTableNameWHEREword?,〔word〕)。single(); 在识别过程中,ocr可能会带上句号,问号,分号之类的标点符号,因此,我们需要替换一下wordwordword。replace(〔:,。〕g,); 截图 截图的时候,我们需要把十字架隐藏,我用的方法是把十字架变透明crossWindow。window。cross。attr(alpha,0);sleep(60);ui。post(function(){crossWindow。window。cross。attr(alpha,1);},100); 不隐藏十字架的会,会被截图,影响识别单词的效果 ocr的识别区域 我们这里只讨论正常的英文文章, 我们假定的是一行最少3个单词,一页最少20行英文单词;letwordRect{ratio:{w:13,h:120,},}; 同样,使用比例来决定图片的宽高 环境 设备:小米11pro Android版本:12 雷电模拟器:9。0。17 Android版本:9 Autojs版本:9。2。13 名人名言 思路是最重要的,其他的百度,bing,stackoverflow,github,安卓文档,autojs文档,最后才是群里问问牙叔教程 声明 部分内容来自网络本教程仅用于学习,禁止用于其他用途