autojs悬浮窗翻译单词
牙叔教程 简单易懂效果预览
如上图所示, 一共有三个悬浮存储:右侧的悬浮球十字架悬浮窗翻译的内容悬浮窗
建议只用两个悬浮窗, 也就是把 十字架和翻译内容合并为一个悬浮窗, 这样用户在触摸移动的时候,
有更大的触摸区域.
思路十字架指针确定单词位置用autojs9提供的插件 MLKitOCR , 识别单词周围的内容ocr识别的结果中, 带有文字的recct信息,这些rect信息和十字架的位置对比一下, 就知道是哪个单词了网上找了个离线的词典, 词汇量几十万用sqlite查询, 速度很快
离线词典制作教程
请参考: autojs查找相同词根的单词
https://www.yuque.com/yashujs/bfug6u/wbg0or
这个是把csv的单词数据转成了db,
本教程不提供单词数据库, 若有需要, 下载那个开源的单词库, 跟着教程做, 自己转db
多个分辨率保持大致相似的布局
我们来看看多个分辨率的布局是否一样
这是模拟器的三种分辨率, 可以看到布局几乎一模一样, 这是怎么做的呢?
布局的宽高使用的都是px, 按照同样的比例来决定多宽多高,
文字大小使用的也是px; 他们使用的是同一个比例;let config = { cross: { widthRatio: 0.05, }, text: { sizeRatio: 0.04, }, translationHoveringWindow: { widthRatio: 0.4, heightRatio: 0.2, }, };
因此, 你看到的是同一个比例的布局, 但是是不同的宽高, 达到了在视觉上保持一致的效果
悬浮球
为了尽量少的遮挡用户的屏幕, 我们将其透明度设置为了0.5alpha="0.5"
悬浮球随意拖动
他可以随意拖动
这个拖动因为比较常用, 建议大家封装一下;
我的代码已经写完了, 就不改了, 这里可以提供一个封装例子
首先写一个悬浮窗用来测试let window = floaty.rawWindow( ); setInterval(() => {}, 1000);
然后是封装;
封装不只是移动, 后期可能还要添加各种按下拖动抬起的行为, 因此封装的参数对象有多个属性let windowData = { window: window, moveViewId: "move", isTouching: false, downCallback: function () {}, moveCallback: function () {}, upCallback: function () {}, onClick: function () {}, };
但是你肯定不想写这么多属性, 只有前两个是必须的, 其他的可有可无, 我们可以设置默认属性, 然后和用户传进来的对象合并let windowData = { window: window, moveViewId: "move", }; let windowDefaultData = { isTouching: false, downCallback: function () {}, moveCallback: function () {}, upCallback: function () {}, onClick: function () {}, }; Object.assign(windowDefaultData, windowData);
我们要让用户传入的参数对象覆盖掉默认的对象, 因此, 用户的参数作为 Object.assign 第二个参数
接下来是封装触摸移动;
第一: 做参数校验, 如果传递的参数对象没有必备的属性, 我们就抛出错误function makeWindowMoveable(windowData) { if (!windowData.window) { throw new Error("windowData.window is undefined"); } if (!windowData.moveViewId) { throw new Error("windowData.moveViewId is undefined"); }
触摸移动的基础封装var x = 0, y = 0; //记录按键被按下时的悬浮窗位置 var windowX, windowY; //记录按键被按下的时间以便判断长按等动作 var downTime; let view = window[windowData.moveViewId]; view.setOnTouchListener(function (view, event) { switch (event.getAction()) { case event.ACTION_DOWN: x = event.getRawX(); y = event.getRawY(); windowX = window.getX(); windowY = window.getY(); return true; case event.ACTION_MOVE: //移动手指时调整悬浮窗位置 window.setPosition(windowX + (event.getRawX() - x), windowY + (event.getRawY() - y)); return true; case event.ACTION_UP: return true; } return true; });
在该基础上添加各种callbackswitch (event.getAction()) { case event.ACTION_DOWN: windowDefaultData.downCallback(); return true; case event.ACTION_MOVE: windowDefaultData.moveCallback(); return true; case event.ACTION_UP: windowDefaultData.upCallback(); return true; }
再加上点击和长按操作switch (event.getAction()) { case event.ACTION_UP: let upTime = new Date().getTime(); let time = upTime - downTime; // 移动距离 let distance = Math.sqrt(Math.pow(event.getRawX() - x, 2) + Math.pow(event.getRawY() - y, 2)); if (time < 150 && distance < 5) { windowDefaultData.onClick(); } else if (time > 2000 && distance < 5) { windowDefaultData.longClick(); } return true; }
完整封装如下let window = floaty.rawWindow( ); setInterval(() => {}, 1000); let windowData = { 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); function makeWindowMoveable(windowData) { if (!windowData.window) { throw new Error("windowData.window is undefined"); } if (!windowData.moveViewId) { throw new Error("windowData.moveViewId is undefined"); } let windowDefaultData = { isTouching: false, downCallback: function () {}, moveCallback: function () {}, upCallback: function () {}, onClick: function () {}, longClick: function () {}, }; Object.assign(windowDefaultData, windowData); //记录按键被按下时的触摸坐标 var x = 0, y = 0; //记录按键被按下时的悬浮窗位置 var windowX, windowY; //记录按键被按下的时间以便判断长按等动作 var downTime; let view = window[windowData.moveViewId]; view.setOnTouchListener(function (view, event) { switch (event.getAction()) { case event.ACTION_DOWN: windowDefaultData.isTouching = true; x = event.getRawX(); y = event.getRawY(); windowX = window.getX(); windowY = window.getY(); downTime = new Date().getTime(); windowDefaultData.downCallback(); return true; case event.ACTION_MOVE: //移动手指时调整悬浮窗位置 window.setPosition(windowX + (event.getRawX() - x), windowY + (event.getRawY() - y)); windowDefaultData.moveCallback(); return true; case event.ACTION_UP: windowDefaultData.isTouching = false; windowDefaultData.upCallback(); let upTime = new Date().getTime(); let time = upTime - downTime; // 移动距离 let distance = Math.sqrt(Math.pow(event.getRawX() - x, 2) + Math.pow(event.getRawY() - y, 2)); if (time < 150 && distance < 5) { windowDefaultData.onClick(); } else if (time > 2000 && distance < 5) { windowDefaultData.longClick(); } return true; } return true; }); return windowDefaultData; }
这个封装能满足一部分使用需求, 在不同的使用场景仍要按需修改悬浮窗吸附屏幕边缘
拖动以后会自动吸附到屏幕边缘;
靠近左边就贴到左边;
靠近右边就贴到右边;
位置会记住, 下次重启代码, 悬浮窗还在上次的位置
上面那个触摸还封装了一个是否正在触摸的属性, 为什么要加这个属性呢?
就是为了这个悬浮窗屏幕吸附效果;
吸附屏幕边缘, 我是用的是定时器, 每隔2秒, 检查一次
在我们移动悬浮窗的时候, 我们正在调整悬浮窗位置, 这个时候, 你肯定不想吸附到屏幕边缘;
不然, 他会干扰你调整位置
为了获取悬浮窗的状态, 这个封装方法 makeWindowMoveable 必须返回 windowDefaultData;return windowDefaultData;
然后在你需要使用触摸状态的地方, 来获取触摸状态: windowDefaultData.isTouching
屏幕吸附边缘, 同样会记忆, 使用storage持久化存储
悬浮窗的实例化
悬浮窗还有各种隐藏, 显示, 以及一些其他方法要写, 因此建议封装为一个类, 这样我们可以更方便的调用各种方法, 而不用到处翻页找方法在哪里,
如果不这样做, 那就会越写越烦躁,一会找这个方法, 一会找那个方法, 烦死了function CrossWindow() { this.window = floaty.rawWindow(...); this.isShowing = false; this.storage = storages.create("crossWindow"); } CrossWindow.prototype.getCrossPoint = function () {...}; CrossWindow.prototype.toggle = function () {...}; CrossWindow.prototype.hide = function () {...}; ......
悬浮窗显示和隐藏
隐藏是将悬浮窗移动到屏幕之外来实现的this.window.setPosition(-10000, -10000);
显示就是移动到屏幕之内;
同样的, 悬浮窗的位置也会记忆, 也是用storage持久化存储
十字架
十字架是一个 frame 布局, 横着一个view, 竖着一个view, 就成了一个十字架
当然了, 你也可以用图片, 或者canvas自己画
十字架的中心点在屏幕上的位置
获取一个view在屏幕上的位置信息, 我们封装了一个方法function getViewRect(view) { let locationOnScreen = view.getLocationOnScreen(); let frame = new android.graphics.Rect(); view.getHitRect(frame); let width = frame.width(); let height = frame.height(); return { x: locationOnScreen[0], y: locationOnScreen[1], width: width, height: height, }; }
获取十字架中心点, 就在获取位置信息后, 加减乘除, 很简单就不说了
点击翻译按钮, 识别单词
点击翻译按钮后, 我们先获取十字架的中心点, 然后调用autojs9
提供的插件 MLKitOCR , 这个是谷歌开发的工具, 识别英文效果又快又好,
ocr的识别结果带有单词的rect矩形坐标数据, 我们和十字架中心点对比,
谁包含十字架, 谁就是十字架指向的单词
封装的方法是判断一个点, 是否在一个rect中function isPointInRect(point, rect) { let x = point.x; let y = point.y; let x1 = rect.left; let y1 = rect.top; let x2 = rect.right; let y2 = rect.bottom; if (x >= x1 && x <= x2 && y >= y1 && y <= y2) { return true; } return false; }
翻译单词
单词库上面已经说过了, 我们使用安卓的sqlite来查询单词, 命令是db.rawQuery("SELECT * FROM " + TableName + " WHERE word = ?", [word]).single();
在识别过程中, ocr可能会带上句号, 问号, 分号之类的标点符号, 因此, 我们需要替换一下wordword = word.replace(/[:|,|.]/g, "");
截图
截图的时候, 我们需要把十字架隐藏, 我用的方法是把十字架变透明crossWindow.window.cross.attr("alpha", 0); sleep(60); ui.post(function () { crossWindow.window.cross.attr("alpha", 1); }, 100);
不隐藏十字架的会, 会被截图, 影响识别单词的效果
ocr的识别区域
我们这里只讨论正常的英文文章,
我们假定的是一行最少3个单词, 一页最少20行英文单词;let wordRect = { ratio: { w: 1 / 3, h: 1 / 20, }, };
同样, 使用比例来决定图片的宽高
环境
设备: 小米11pro
Android版本: 12
雷电模拟器:9.0.17
Android版本: 9
Autojs版本: 9.2.13
名人名言
思路是最重要的, 其他的百度, bing, stackoverflow, github, 安卓文档, autojs文档, 最后才是群里问问 --- 牙叔教程
声明
部分内容来自网络 本教程仅用于学习, 禁止用于其他用途
为什么机关里穿着漂亮反被边缘化?小冯新入职来见我,她一身时髦打扮,涂着口红戴着耳环,穿着名牌时装,头发还遮住了半边脸,就像朱德庸漫画里涩女郎的模样。我跟她说机关里是不能这样打扮的。她问为什么呀?我说你看见过这里有
Pragnell乔治培诺大S刷卡失败也要再次尝试的珠宝品牌大S与汪小菲战况升级,网友更是从汪小菲的信用卡账单里推算出各种消费商品。其中在3月26日和4月3日发生的大额失败消费格外吸睛。万能的网友根据当时汇率,推算出这几笔一而再再而三尝试支
白百何胖太多,虎背熊腰还穿裸色针织衫,勒出粗手臂跟大妈无异现在大家的生活水平可以说是真的大幅度的提升呀,以前可能买东西都是要掂量着买,但是现在大家都是差不多自己想要的东西,有这个能力都会去买。白百何确实是胖了挺多的呀,真的是肉眼可见的在长
夜读励志一直走天会亮起来的1。hr没有人可以拦住一切公平的努力而这一切一定会迎来好的结果图源网,下均,侵删致歉2。世界上99的事都能用钱解决剩下的1需要用更多钱才能解决3。hr不前进就倒退停滞状态是没有的4
伤感语录集如果爱情是一本童话故事集我们应该在第一页就相遇了。世间所有的相遇,都是久别重逢。可遇不可求,可遇不可留,可遇不可有。这世上让人憋屈的事情有很多,比如越爱越远的人,越下越大的雨。有的
聪明的人简单一点说吧,可能是因为是天选之子,所以要空乏其身,饿其体肤,行拂乱其所为,增益其所不能。也就是俗话说的慧极必伤。但是有一点,聪明的人并不一定能成功,因为他需要从他所遭遇的比别人多
陪伴一程相思一生思念一个人的时候,就连星星都带着忧郁的神情。执笔,用文字一遍遍记录着依恋的心绪,相思的深情。爱你那么久,忘不了与你在一起的点点滴滴,琐琐碎碎。将你的名字书写千遍万遍,相思漫漫,爱你
西安的钟楼的历史由来,西安为什么要以钟楼为中心?头条创作挑战赛在西安市的最中心,大家都知道有钟鼓楼,其实这是两个楼,一个钟楼,一个鼓楼,也是西安的一个标志性建筑,这两座楼都是明代建立的,遥相呼应,很是壮观。古代的时候击钟报晨,击
随手放进口袋的真无线耳机,4款无线耳机声音醇美长续航现在的年轻人对于耳机的追求越来越高,尤其讨厌耳机线的束缚,但是还要追求很高的音质。接下来推荐4款真无线耳机,小巧玲珑的可以直接放进口袋随身携带,在音质方面,声音穿透力强,声音醇美。
团团小课堂了解文身的危害,向未成年人文身说不!文身,我国古代称刺青,是以细针蘸染料在皮肤写字作画,由于组织细胞不能吞噬移除这些不溶性的固体微粒,而永远留在真皮并产生异物反应,使皮肤显出永不消退的字画,所以文身的本质是一种色素斑
润唇膏涂多了对嘴唇有什么影响?马上到夏季,唇部的护理又提上了各位集美们的日程了,很多姑娘都有在问,这润唇膏涂多了,到底会不会对嘴唇右踝处呢?其实经常涂润唇膏绝对会上瘾!成瘾还是从生理和心理两方面的生理上很好解释