|
| 1 | +尝试一下 IDEA 插件的开发。 |
| 2 | + |
| 3 | +之前具有试过开发一个翻译插件,没错,就是那个 AS 翻译插件很火的那段时间,但是开发过程中很多 Bug 解决不了,而且国内这方面资料相对较少,也很难查出个为什么。所以这个小项目就一直摆在那里。最近刚好都是零碎时间,又调试了几次,没想到竟然成功运行了。下面就从一个开发者的角度来讲讲如何开发一个自己的翻译插件。 |
| 4 | + |
| 5 | +### 流程步骤规划 |
| 6 | + |
| 7 | +网上也有几款类似的翻译插件,大部分的思路都是如下所示,我们就不另辟他径了,直接按照这种流程来吧: |
| 8 | + |
| 9 | +* 熟悉基本的 IDEA 插件开发过程; |
| 10 | +* 能够获取选中的单词; |
| 11 | +* 调用某家 API,获得返回 JSON 数据并解析; |
| 12 | +* 展示出解析的数据; |
| 13 | +* 优化显示界面。 |
| 14 | + |
| 15 | +### 基本开发流程 |
| 16 | + |
| 17 | +下载 Intellij IDEA 编辑器,新建工程如下所示: |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | +在工程中的 src 处右键 New 选择 Action,如下图红色箭头所示: |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | +紧接着自动弹出如下 New Action 配置框: |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | +填写好插件的基本属性之后,要选择启动方式,也就是上图蓝色选中处,我们选中的是 EditMenu 也就是 AS 或者 IDEA 中的 toolbar 中的 EDIT 选项。当你成功完成插件的编写和安装之后,选中单词然后点击 EDIT 选项,就能显示出对应的内容了。当然 JetBrains 作为如此优秀的 IDE 设计者,肯定是支持快捷方式的,即上图中的 `Keyboard Shortcuts` 我们可以输入启动该插件功能的快捷方式,如:Alt + A,当然如果的键盘快捷键设置的比较多,也可以设置第二快捷键,是很方便的:Alt + X. |
| 30 | + |
| 31 | +完成上述 Action 的基本配置之后,在如下图所示的 plugin.xml 文件中就有对应的项添加进来了: |
| 32 | + |
| 33 | + |
| 34 | + |
| 35 | +接下来看看 TestAction 类中代码吧: |
| 36 | + |
| 37 | +``` java |
| 38 | +public class TestAction extends AnAction { |
| 39 | + |
| 40 | + @Override |
| 41 | + public void actionPerformed(AnActionEvent e) { |
| 42 | + // TODO: insert action logic here |
| 43 | + } |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +基本的格式就如同上面的样子了,继承自 AnAction 类。英文注释说的很明显,在 actionPerFormed() 方法中进行你的逻辑代码编写,感觉有点像 Main() 方法的感觉啊。 |
| 48 | + |
| 49 | +### 获取选中数据 |
| 50 | + |
| 51 | +这一块大家所采用的方法比较一致,其实也就是 IDEA 开发过程中的 API 的调用而已,获取选中字符。代码如下所示: |
| 52 | + |
| 53 | +``` java |
| 54 | +// 获得 editor 对象 |
| 55 | +mEditor = e.getData(PlatformDataKeys.EDITOR); |
| 56 | + if (mEditor != null) { |
| 57 | + // 获得编辑模式 |
| 58 | + SelectionModel model = mEditor.getSelectionModel(); |
| 59 | + // 获取选中的文本 |
| 60 | + String selectedText = model.getSelectedText(); |
| 61 | + if (selectedText != null) { |
| 62 | + // 翻译并显示 |
| 63 | + getTranslation(selectedText); |
| 64 | + } |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +### 接口和网络请求 |
| 69 | + |
| 70 | +在接口请求方面,我们选择扇贝的 API,除了简单清晰之外,其查询单词 API 直接返回单词的读音,这方便了后续的功能扩展。 |
| 71 | + |
| 72 | +其 API 为: https://api.shanbay.com/bdc/search/?word={word} :查询 hello 返回的 API 接口数据如下: |
| 73 | + |
| 74 | +``` json |
| 75 | +{ |
| 76 | + "status_code": 0, |
| 77 | + "msg": "SUCCESS", |
| 78 | + "data": { |
| 79 | + "en_definitions": { |
| 80 | + n: [ |
| 81 | + "an expression of greeting" |
| 82 | + ] |
| 83 | + }, |
| 84 | + "cn_definition": { |
| 85 | + "pos": "", |
| 86 | + "defn": "int.(见面打招呼或打电话用语)喂,哈罗" |
| 87 | + }, |
| 88 | + "id": 3130, |
| 89 | + "retention": 4, |
| 90 | + "definition": " int.(见面打招呼或打电话用语)喂,哈罗", |
| 91 | + "target_retention": 5, |
| 92 | + "en_definition": { |
| 93 | + "pos": "n", |
| 94 | + "defn": "an expression of greeting" |
| 95 | + }, |
| 96 | + "learning_id": 2915819690, |
| 97 | + "content": "hello", |
| 98 | + "pronunciation": "hә'lәu", |
| 99 | + "audio": "http://media.shanbay.com/audio/us/hello.mp3", |
| 100 | + "uk_audio": "http://media.shanbay.com/audio/uk/hello.mp3", |
| 101 | + "us_audio": "http://media.shanbay.com/audio/us/hello.mp3" |
| 102 | + }, |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +可见如上数据基本上能满足我们当前的需要和日后的扩展了。接下来我们选择网络请求框架,看了两款翻译插件的源码都是基本的 httpclient 来作为请求数据的框架的,比较接近最底层的 http 请求,这种其实挺好的,学习一下最基础的请求过程。但是底层的话随之带来的就是自己要去配置很多参数,配置请求头,同时也要去处理异步和回调之类的,就有点略显麻烦。作为一个追求效率的程序员,我们力求用最少的代码,完成同样的效果。所以我们想到了 `Retrofit`这个目前为止最火的网络请求框架: |
| 107 | + |
| 108 | + |
| 109 | + |
| 110 | +嘿嘿,其官网主页介绍 label,完美支持 Java 平台。所以我们就选择该网络请求框架啦。配合 Gson 解析 Json 数据。 |
| 111 | + |
| 112 | +为了确保 `跨平台`所带来的问题不会影响到插件的开发过程,先在 Android 平台写了一个 demo App 测试了一下,发现整个流程是没有问题的。接下来我们就在 IDEA 中编写核心代码啦。不过在此之前我们要导入两个 jar 格式的数据包,没错,你应该很熟悉,在 AS 中我们都是以 Gradle 方式导入进去的,这里好像就只能新建文件目录 libs,然后 Add 进去了: |
| 113 | + |
| 114 | + |
| 115 | + |
| 116 | +开发 IDEA 插件的或者阅读本篇博客的应该都是 Android 司机啦,一定能够明白下面写法的用意了,没错,就跟 AS 中使用 Retrofit 作为网络请求框架的过程一样的,先定义一个 `ShanBeiApi`接口,里面写上相关代码,指定了实体类为 `Translation`,然后其他的看看 Retrofit 的使用文档就明白了: |
| 117 | + |
| 118 | +``` java |
| 119 | +public interface ShanBeiApi { |
| 120 | + // https://api.shanbay.com/bdc/search/?word={} |
| 121 | + @GET("bdc/search") |
| 122 | + Call<Translation> listInfos(@Query("word") String query); |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +如下就是本插件的核心之处了,我们将选中的单词传入该方法中: |
| 127 | + |
| 128 | +``` java |
| 129 | +private void getTranslation(String selectedText) { |
| 130 | + Retrofit retrofit = new Retrofit.Builder() |
| 131 | + .baseUrl(BASE_URL) |
| 132 | + .addConverterFactory(GsonConverterFactory.create()) |
| 133 | + .build(); |
| 134 | + ShanBeiApi shanbei= retrofit.create(ShanBeiApi.class); |
| 135 | + Call<Translation> call = shanbei.listInfos(selectedText); |
| 136 | + // 事件回调 |
| 137 | + call.enqueue(new Callback<Translation>() { |
| 138 | + @Override |
| 139 | + public void onResponse(Call<Translation> call, Response<Translation> response) { |
| 140 | + // 获取在实体类中定义好的字符串 |
| 141 | + String msg = response.body().toString(); |
| 142 | + // 显示返回的数据 |
| 143 | + showPopupBalloon(msg); |
| 144 | + } |
| 145 | + |
| 146 | + @Override |
| 147 | + public void onFailure(Call<Translation> call, Throwable throwable) { |
| 148 | + //showPopupBalloon("error msg:"+ throwable.getMessage()); |
| 149 | + } |
| 150 | + }); |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +如上代码看起来应该没什么问题,因为在 App 中是能够行得通的,但是写好插件后调试偏偏又不显示窗口,更别说显示数据是否能正常返回了。解决这个有点小麻烦的 Bug,我的大概思路是这样的:先传入固定字符到调用的函数中,看看窗口是否能正常显示。测试一下之后发现窗口的显示是没有问题的。接着由于刚开始开发插件,还不太懂怎么去调式,把 Log 打出到控制台上。想着能不能在窗口显示的基础之上,把 Log 给显示出来,于是我就在 onFailure() 方法中打印了 error 的 msg,显示如下信息: |
| 155 | + |
| 156 | + |
| 157 | + |
| 158 | +错误信息给出来了就好办了,赶紧复制黏贴一下 Google, 成功的找到了解决方案,导入一个 Gson.jar 即可。恩,虽然,成功解决了这个 Bug,但是具体原因我还真不知道为什么,我导入的相关的 gson 包中应该包含了所需的模块啊? |
| 159 | + |
| 160 | +### 展示数据内容 |
| 161 | + |
| 162 | +获得了相关的数据之后,我们考虑的就是如何展示这些返回的数据了,是以 Dialog 的形式还是以 Popupwindow 的形式,默认的 Dialog 样式感觉有点难看,Popupwindow 有一种气泡模式,还挺好看的,也就是下面这段代码带来的效果: |
| 163 | + |
| 164 | +``` java |
| 165 | +// Refrence:https://github.com/Skykai521/ECTranslation/blob/master/src/RequestRunnable.java#L77 |
| 166 | +private void showPopupBalloon(final String result) { |
| 167 | + ApplicationManager.getApplication().invokeLater(new Runnable() { |
| 168 | + public void run() { |
| 169 | + JBPopupFactory factory = JBPopupFactory.getInstance(); |
| 170 | + factory.createHtmlTextBalloonBuilder(result, null, new JBColor(new Color(186, 238, 186), new Color(73, 117, 73)), null) |
| 171 | + // 显示时长 |
| 172 | + .setFadeoutTime(5000) |
| 173 | + .createBalloon() |
| 174 | + .show(factory.guessBestPopupLocation(mEditor), Balloon.Position.below); |
| 175 | + } |
| 176 | + }); |
| 177 | + } |
| 178 | +``` |
| 179 | + |
| 180 | +目前入门阶段就这么显示一下了,其实接口返回的数据还有音频的链接,下一步的如果要优化和丰富该翻译插件,就需要自定义显示的样式了,毕竟目前的显示样式还是一个气泡样式的,并且过了指定时间就会消失的,根本来不及响应事件的触发。具体显示的效果就是下面截图所示啦: |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | +如下所示即可生成 zip 或者 jar 格式的插件包,让别人的 IDEA 或者 Android Studio 导入了: |
| 185 | + |
| 186 | + |
| 187 | + |
| 188 | +如下图所示,在 File -> Setting 底下即可见到,我们选择红色箭头所示,从磁盘中导入,当然也可以从插件中心下载,不过本篇文章只做实例讲解,就不搞这些了,大家有兴趣的话,可以试试其他两款优秀的翻译插件: |
| 189 | + |
| 190 | + |
| 191 | + |
| 192 | +还有一点就是如上截图你可以看到显示的音标还有点乱码,这个原因我看另一款翻译插件的 Issues 上也有提到,大概是系统字体的原因。但是这个理由在我的电脑上却得到并未解决,后续再看看吧。 |
| 193 | + |
| 194 | +### 扩展开发 |
| 195 | + |
| 196 | +* 通常为了防止错误输入我们可以,用一个正则来检测一个选取的单词,如果前后有空格就说明是一个完整的单词,如果选取的单词前面或者后面还有字符或者字母,就可以一定程度上断言这不是一个完整的单词了。 |
| 197 | + |
| 198 | + |
| 199 | +* 另外之前也提到过,扇贝 API 接口返回了单词读音数据,我们就要考虑如何能将读音播放出来,此时,只有气泡显示界面是不可以的了,我们得考虑能够触发事件的 Dialog 界面啦,其实也不是很麻烦的,如下所示截图,我们还是右键 New -> Dialog: |
| 200 | + |
| 201 | + |
| 202 | + |
| 203 | + 点击 TestDialog.form 就能见到如下所示界面了,这个其实跟 Java Swing 编程很类似,可以拖动具体的 |
| 204 | + |
| 205 | + 控件到容器布局中,然后在 TestDialog 中编写相应代码,进行事件响应: |
| 206 | + |
| 207 | + |
| 208 | + |
| 209 | +* 恩,如果你用心阅读了本篇文章,会发现其实这个插件的开发一点挑战性都没有,建议的话,还是多看一些 star 数目比较高的开源项目,深入学习一下。 |
| 210 | + |
| 211 | +后续看看吧,有时间就继续优化一下,完善一下。不过也不一定,毕竟我这人喜欢跳票(逃... |
| 212 | + |
| 213 | +最后本篇文章示例代码的 Demo 在[这里](https://github.com/wuchangfeng/idea-plugins/tree/master/TranslationPlugin),有需要的自取一下就好了。 |
| 214 | + |
| 215 | +文章中单词显示的悬浮窗效果代码参考[这里。](https://github.com/Skykai521/ECTranslation/blob/master/src/RequestRunnable.java#L77)如果读者有兴趣后续自己去优化的话,可以参考[这里](https://github.com/YiiGuxing/TranslationPlugin),这个插件的样子大概做出来我想要的效果,Orz. |
| 216 | + |
| 217 | +以上,谢谢阅读!!! |
| 218 | + |
0 commit comments