Skip to content

Commit ad2be1d

Browse files
author
Jaeger
authored
Publish Phase7 (#142)
1 parent d4c5056 commit ad2be1d

12 files changed

+2848
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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+
![](http://ww1.sinaimg.cn/large/b10d1ea5jw1fabhh2prwrj20mp0m5gnn.jpg)
20+
21+
在工程中的 src 处右键 New 选择 Action,如下图红色箭头所示:
22+
23+
![](http://ww2.sinaimg.cn/large/b10d1ea5jw1fabhhc9xxuj20wm0k1n5c.jpg)
24+
25+
紧接着自动弹出如下 New Action 配置框:
26+
27+
![](http://ww3.sinaimg.cn/large/b10d1ea5jw1fabhjuf615j20p80figpb.jpg)
28+
29+
填写好插件的基本属性之后,要选择启动方式,也就是上图蓝色选中处,我们选中的是 EditMenu 也就是 AS 或者 IDEA 中的 toolbar 中的 EDIT 选项。当你成功完成插件的编写和安装之后,选中单词然后点击 EDIT 选项,就能显示出对应的内容了。当然 JetBrains 作为如此优秀的 IDE 设计者,肯定是支持快捷方式的,即上图中的 `Keyboard Shortcuts` 我们可以输入启动该插件功能的快捷方式,如:Alt + A,当然如果的键盘快捷键设置的比较多,也可以设置第二快捷键,是很方便的:Alt + X.
30+
31+
完成上述 Action 的基本配置之后,在如下图所示的 plugin.xml 文件中就有对应的项添加进来了:
32+
33+
![](http://ww2.sinaimg.cn/large/b10d1ea5jw1fabhk1ew2ej20wm0k1dn1.jpg)
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+
![](http://ww2.sinaimg.cn/large/b10d1ea5jw1fabfjqjthej20l403vq37.jpg)
109+
110+
嘿嘿,其官网主页介绍 label,完美支持 Java 平台。所以我们就选择该网络请求框架啦。配合 Gson 解析 Json 数据。
111+
112+
为了确保 `跨平台`所带来的问题不会影响到插件的开发过程,先在 Android 平台写了一个 demo App 测试了一下,发现整个流程是没有问题的。接下来我们就在 IDEA 中编写核心代码啦。不过在此之前我们要导入两个 jar 格式的数据包,没错,你应该很熟悉,在 AS 中我们都是以 Gradle 方式导入进去的,这里好像就只能新建文件目录 libs,然后 Add 进去了:
113+
114+
![](http://ww3.sinaimg.cn/large/b10d1ea5jw1fabhnjk9ydj20qs0ik7c6.jpg)
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+
![](http://ww1.sinaimg.cn/large/b10d1ea5jw1fabhpmh6iyj20hu01l74f.jpg)
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+
![](http://ww3.sinaimg.cn/large/b10d1ea5jw1facdyu5lw8j20s10hbte3.jpg)
183+
184+
如下所示即可生成 zip 或者 jar 格式的插件包,让别人的 IDEA 或者 Android Studio 导入了:
185+
186+
![](http://ww1.sinaimg.cn/large/b10d1ea5jw1facddgmsx4j20s10hbn4s.jpg)
187+
188+
如下图所示,在 File -> Setting 底下即可见到,我们选择红色箭头所示,从磁盘中导入,当然也可以从插件中心下载,不过本篇文章只做实例讲解,就不搞这些了,大家有兴趣的话,可以试试其他两款优秀的翻译插件:
189+
190+
![](http://ww2.sinaimg.cn/large/b10d1ea5jw1face04td6uj20s80ha41w.jpg)
191+
192+
还有一点就是如上截图你可以看到显示的音标还有点乱码,这个原因我看另一款翻译插件的 Issues 上也有提到,大概是系统字体的原因。但是这个理由在我的电脑上却得到并未解决,后续再看看吧。
193+
194+
### 扩展开发
195+
196+
* 通常为了防止错误输入我们可以,用一个正则来检测一个选取的单词,如果前后有空格就说明是一个完整的单词,如果选取的单词前面或者后面还有字符或者字母,就可以一定程度上断言这不是一个完整的单词了。
197+
198+
199+
* 另外之前也提到过,扇贝 API 接口返回了单词读音数据,我们就要考虑如何能将读音播放出来,此时,只有气泡显示界面是不可以的了,我们得考虑能够触发事件的 Dialog 界面啦,其实也不是很麻烦的,如下所示截图,我们还是右键 New -> Dialog:
200+
201+
![](http://ww3.sinaimg.cn/mw690/b10d1ea5jw1facqcdc07vj20qh0hejwm.jpg)
202+
203+
​ 点击 TestDialog.form 就能见到如下所示界面了,这个其实跟 Java Swing 编程很类似,可以拖动具体的
204+
205+
​ 控件到容器布局中,然后在 TestDialog 中编写相应代码,进行事件响应:
206+
207+
![](http://ww1.sinaimg.cn/mw690/b10d1ea5jw1facqbno3duj20s30hen3i.jpg)
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+
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
title: JitPack 指南
3+
date: 2016-12-04 16:02:31
4+
---
5+
>- 文章来源:itsCoder 的 [WeeklyBolg](https://github.com/itsCoder/weeklyblog) 项目
6+
>- itsCoder主页:[http://itscoder.com/](http://itscoder.com/)
7+
>- 作者:[谢三弟](https://github.com/xcc3641)
8+
>- 审阅者:[JoeSteven](https://github.com/JoeSteven)
9+
10+
### 目录
11+
12+
### 前言
13+
14+
最初发布一些 lib 是用的 jCenter ,但是每次上传都跟玄学一样,就算我全局或者指定代理还是凭运气,后来问了老司机,结识了 [JitPack](https://jitpack.io/) ,体验更加方便和易懂。
15+
16+
### 步骤
17+
18+
#### 账号
19+
20+
1. 首先进入网页我们看到是:
21+
22+
![](http://ww2.sinaimg.cn/large/006y8lVagw1faer45z2g0j31kw12542t.jpg)
23+
24+
然后我们进行账号登陆,默认是跟 Github 绑定的。
25+
26+
2. 成功之后网站会读取你的 repo 显示在左侧:
27+
28+
![](http://ww4.sinaimg.cn/large/006y8lVagw1faer6xve2gj31kw10itbq.jpg)
29+
30+
31+
#### Gradle 配置
32+
33+
1. 首先在你的 root build.gradle 添加:
34+
35+
36+
```gradle
37+
buildscript {
38+
repositories {
39+
jcenter()
40+
}
41+
dependencies {
42+
classpath 'com.android.tools.build:gradle:2.2.2'
43+
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' // 这一栏
44+
}
45+
}
46+
```
47+
48+
2. 接下来在你的 Lib build.gradle 添加:
49+
50+
```gralde
51+
group = 'com.github.<username>' // 最好是你的 github 名字
52+
53+
// 指定编码
54+
tasks.withType(JavaCompile) {
55+
options.encoding = "UTF-8"
56+
}
57+
58+
// 打包源码
59+
task sourcesJar(type: Jar) {
60+
from android.sourceSets.main.java.srcDirs
61+
classifier = 'sources'
62+
}
63+
64+
artifacts {
65+
archives sourcesJar
66+
}
67+
```
68+
3. 配置完成后,将你的项目 push 到 github
69+
4. 点击 Releases 编写好之后发布,回到 JitPack
70+
71+
#### 发布
72+
73+
1. 根据你的 group/Lib 搜索你的裤子。
74+
75+
![](http://ww3.sinaimg.cn/large/006y8lVagw1faerh75xvtj30yu0o8ta9.jpg)
76+
77+
2. 点击 **get it**
78+
79+
![](http://ww2.sinaimg.cn/large/006y8lVagw1faerhpbkemj318u0v0gpe.jpg)
80+
81+
别人就可以用到你写的裤子了 (๑•̀ㅂ•́) ✧
82+
83+
3. 更新裤子
84+
85+
更新你的裤子,用上述同样的方法。
86+
87+
不过在你完成发布新版的时候,最好删除原来的 Tag 。
88+
89+
### 参考
90+
91+
全文以 [Watcher: Help to watch the fps and used memory of your app](https://github.com/xcc3641/Watcher) 为实践,如果有疑问可以参考该项目模仿配置。
92+
93+
- [JitPack](https://jitpack.io)

0 commit comments

Comments
 (0)