上篇文章我们学习了基本的插进的知识,现在我们继续学习插件相关的基础知识。
本章节的主要内容是如何使用缓存,如何创建Setting布局,如何创建RightMenu布局。本章以一个DEMO的产生开始进行讲解其中的使用。前序知识点是要会java的Swing,虽然这个很难用,也很难看,也没几个人用。但是这个是插件的布局的方式。DEMO的例子都比较简单,涉及到的也只是文案,下拉列表,列表等常见控件。
1. 创建Setting布局
创建一个类继承SearchableConfigurable,即可实现Setting的配置,现在来看一下内部实现。 private LeanENForm form;
@NotNull
@Override
public String getId() {
return "com.longshihan.learnEn.settingID";
}
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "外语学习";
}
@Nullable
@Override
public JComponent createComponent() {
form=new LeanENForm();
form.createUI();
return form.getContentPane();
}
@Override
public boolean isModified() {
return form.isModified();
}
@Override
public void apply() throws ConfigurationException {
form.apply();
}
getId:同AnAction一致,是设置界面的指定ID
getDisplayName:设置显示的名称
createComponent:创建布局,在这里我创建了一个叫LeanENForm的布局
apply:生效,对应的是IDEA的setting下面的apply
现在来看一下LeanENForm的结构,你可以点右键创建一个form。不过swing的可视化布局的确比较难用,一般我选择手写。
public void createUI() {
mainPanel.setLayout(new GridLayout(10,0));
JPanel stateMainPane = new JPanel(new FlowLayout(0));
JPanel statusPane=new JPanel();
statusPane.add(new JLabel("是否打开近义词显示:"));
jCheckBoxPromt.addItem("TRUE");
jCheckBoxPromt.addItem("FALSE");
statusPane.add(jCheckBoxPromt);
stateMainPane.add(statusPane);
JPanel historyPane=new JPanel();
historyPane.add(new JLabel("是否打开例句显示:"));
jCheckBoxHistory.addItem("TRUE");
jCheckBoxHistory.addItem("FALSE");
historyPane.add(jCheckBoxHistory);
stateMainPane.add(historyPane);
JPanel relPane=new JPanel();
relPane.add(new JLabel("是否打开同根显示:"));
jCheckNoxRel.addItem("TRUE");
jCheckNoxRel.addItem("FALSE");
relPane.add(jCheckNoxRel);
stateMainPane.add(relPane);
mainPanel.add(stateMainPane);
//...持久化数据的读取
}
上面写的布局,相信没写过SWing的童鞋也能轻轻松松的写下来,这就是一个FLOWlayout布局(流式布局),然后jpanel里面放一个label(文案)和checkbox(下拉列表):具体的界面如下图:
界面大概就是这样的,虽然长得比较丑。
下面来说说持久化数据这块,有界面有数据,肯定要保存数据,而IDEA在这块做的就很好(我觉得可以在Android上也写这么一个功能),创建一个State类,继承实现PersistentStateComponent。
@State(name = "SettingConfig",storages = {@com.intellij.openapi.components.Storage(value = "learnEn-config.xml",
roamingType = com.intellij.openapi.components.RoamingType.DISABLED)})
public class SettingState implements PersistentStateComponent<SettingState> {
private SettingConfig initConfig;
public SettingState() {
}
@Nullable
public static SettingState getInstance() {
return (SettingState) ServiceManager.getService(SettingState.class);
}
@Nullable
@Override
public SettingState getState() {
return this;
}
@Override
public void loadState(@NotNull SettingState settingConfig) {
if (settingConfig == null) {
return;
}
XmlSerializerUtil.copyBean(settingConfig, this);
}
public SettingConfig getConfig() {
if (initConfig==null){
initConfig=new SettingConfig();
}
return initConfig;
}
public void setConfig(SettingConfig config) {
initConfig=config;
}
其中SettingConfig是一个javabean,通过这个类来实现持久化对象,另外对于一些比较重要的数据,官方也提供了其他的方法保存数据:
PasswordSafe.getInstance().storePassword(null, getClass(), "learnDictPath","value");
PasswordSafe.getInstance().getPassword(null, getClass(), "learnDictPath");
LearnENForm的createUI方法里面关于读取数据:
SettingConfig config = SettingState.getInstance().getConfig();
if (config != null) {
this.jCheckBoxPromt.setSelectedIndex(config.isSyno()?0:1);
this.jCheckBoxHistory.setSelectedIndex(config.isSentences()?0:1);
this.jCheckNoxRel.setSelectedIndex(config.isRelWord()?0:1);
}
对于apply方法则是将数据存入,具体实现如下:
public void apply() {
SettingConfig config=SettingState.getInstance().getConfig();
if (config==null){
config=new SettingConfig();
}
config.setSyno(jCheckBoxHistory.getSelectedIndex()==0);
config.setSentences(jCheckBoxPromt.getSelectedIndex()==0);
config.setRelWord(jCheckNoxRel.getSelectedIndex()==0);
SettingState.getInstance().setConfig(config);
}
上面就是Setting界面完整的情况,在plugin.xml里面将数据配置就大功告成。
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceInterface="com.longshihan.learnEN2.setting.SettingState"
serviceImplementation="com.longshihan.learnEN2.setting.SettingState"/>
<applicationConfigurable groupId="tools"
instance="com.longshihan.learnEN2.setting.LearnENSettingConfigurable"/>
</extensions>
2. 创建RightMenu布局
先看界面吧,界面比较简单,就是一个横向的流式布局+列表。先说下数据,数据是取的有道单词本,每日词汇的内容(如有侵权,请及时告知删除内容)。
首先创建一个类实现ToolWindowFactory,完整的代码如下:
public class LearnToolsWindowFactory implements ToolWindowFactory, RightMenuRefreshListener, HttpResponceListener {
HttpUtils httpUtils;
NavigatorPanel navigatorPanel;
SettingConfig config;
int startIndex;
String dictId;
int pageSize;
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
config = SettingState.getInstance().getConfig();
if (config == null) {
config = new SettingConfig();
}
if (TextUtils.isEmpty(config.getDictId())) {
config.setDictId("BEC_2");
}
if (config.getPagesize() == 0) {
config.setPagesize(20);
}
startIndex=config.getStartIndex();
dictId=config.getDictId();
pageSize=config.getPagesize();
navigatorPanel = new NavigatorPanel(toolWindow, project, this, config);
Content content = contentFactory.createContent(navigatorPanel, "", false);
toolWindow.getContentManager().addContent(content);
httpUtils = new HttpUtils(this);
getDataUrl();
config.setStartIndex(config.getStartIndex() + config.getPagesize());
SettingState.getInstance().setConfig(config);
}
@Override
public void onNext(int pageSize, String dictId) {
config.setPagesize(pageSize);
this.pageSize=pageSize;
if (!dictId.equals(config.getDictId())) {
config.setStartIndex(0);
startIndex=0;
}else {
config.setStartIndex(config.getStartIndex()+config.getPagesize());
startIndex=startIndex+pageSize;
}
config.setDictId(dictId);
this.dictId=dictId;
SettingState.getInstance().setConfig(config);
getDataUrl();
}
@Override
public void onRefresh() {
getDataUrl();
}
@Override
public void onGetMessage(ResponceInfo info) {
if (info != null && info.isSuccess() && info.getData() != null) {
Gson gson = new Gson();
EveryDayWordInfo info1 = gson.fromJson(info.getData(), EveryDayWordInfo.class);
if (info1 != null && info1.getWords() != null && info1.getWords().size() > 0) {
System.out.println("刷新");
navigatorPanel.putData(info1);
}
}
}
}
其中关键的方法是:createToolWindowContent,可以看到是生成一个panel放进contentFactory中,并加载进toolwindow,就将关键的界面一一绑定。
Content content = contentFactory.createContent(navigatorPanel, "", false);
toolWindow.getContentManager().addContent(content);
像RightMenuRefreshListener,HttpResponceListener是布局内事件的接口回调和网络的接口回调,这边都是无关紧要的东西。具体界面布局如下:
public class NavigatorPanel extends SimpleToolWindowPanel implements DataProvider {
private JPanel queryPanel;
private JBScrollPane contentScrollPanel;
private SimpleTree tree;
private SimpleToolWindowPanel treePanel;
private WordElement wordElement=new WordElement();
private JList list;
private JComboBox dictcomboBox=new JComboBox();
private JComboBox pageSizeomboBox=new JComboBox();
private Map<String,String> dictMap=new HashMap<>();
private SettingConfig config;
public NavigatorPanel(ToolWindow toolWindow, Project project, RightMenuRefreshListener listener, SettingConfig config) {
super(true, true);
dictMap.put("BEC_2","商务英语词汇");
dictMap.put("CET4luan_1","四级词汇");
ActionManager actionManager = ActionManager.getInstance();
treePanel=new SimpleToolWindowPanel(true,true);
JPanel toolsPanel=new JPanel();
toolsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
for (String dictStr :dictMap.values()) {
dictcomboBox.addItem(dictStr);
}
if ("BEC_2".equals(config.getDictId())){
dictcomboBox.setSelectedIndex(0);
}else if ("CET4luan_1".equals(config.getDictId())){
dictcomboBox.setSelectedIndex(1);
}
toolsPanel.add(dictcomboBox);
pageSizeomboBox.addItem(20);
pageSizeomboBox.addItem(40);
pageSizeomboBox.addItem(60);
if (config.getPagesize()==20){
pageSizeomboBox.setSelectedIndex(0);
}else if (config.getPagesize()==40){
pageSizeomboBox.setSelectedIndex(1);
}else if (config.getPagesize()==60){
pageSizeomboBox.setSelectedIndex(2);
}
toolsPanel.add(pageSizeomboBox);
JButton nextButton=new JButton();
nextButton.setText("下一页");
nextButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (listener!=null){
listener.onNext(pageSizeomboBox.getSelectedIndex()*20+20, (String) dictMap.keySet().toArray()[dictcomboBox.getSelectedIndex()]);
}
}
});
toolsPanel.add(nextButton);
JButton refreshButton=new JButton();
refreshButton.setText("刷新");
refreshButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (listener!=null){
listener.onRefresh();
}
}
});
toolsPanel.add(refreshButton);
treePanel.setToolbar(toolsPanel);
JScrollPane scrollPane=new JScrollPane(); //创建滚动面板
treePanel .add(scrollPane,BorderLayout.CENTER); //将面板增加到边界布局中央
list=new JBList();
//限制只能选择一个元素
list.setCellRenderer(new LearnCellRender(config));
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
scrollPane.setViewportView(list); //在滚动面板中显示列表
list.setModel(wordElement);
setContent(treePanel);
}
public void putData(EveryDayWordInfo info1) {
config= SettingState.getInstance().getConfig();
list.setModel(new WordElement(info1.getWords()));
}
}
这里的布局咋一看复杂,其实也就是一个流式布局+列表。其中列表内元素的绘制是在LearnCellRender这个方法里面。
下面是个缩减版的代码,具体绘制是在getListCellRendererComponent这个方法中,可以看到返回的是Component。代码也没什么好说的,就是拿控件按你的布局塞进去,比较死板。
public class LearnCellRender implements ListCellRenderer {
private SettingConfig config;
public LearnCellRender(SettingConfig config) {
this.config = config;
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value != null) {
if (value instanceof WordsBeanX) {
WordsBeanX data = (WordsBeanX) value;
// JPanel jPanel = new JPanel(new GridLayout(0, 1));
Box jPanel = Box.createVerticalBox(); //创建横向Box容器
// jPanel.setBackground(Color.WHITE);
if (data.getWord() != null) {
JLabel titleLabel = new JLabel(data.getWord().getWordHead());
titleLabel.setFont(new Font(null, Font.ITALIC, 20));
jPanel.add(titleLabel);
if (data.getWord().getContent() != null) {
jPanel.add(new JLabel("美:[" + data.getWord().getContent().getUsphone() + "]"));
jPanel.add(new JLabel("英:[" + data.getWord().getContent().getUsphone() + "]"));
Map<String, JTextArea> adjMap = new HashMap<>();
if (data.getWord().getContent().getSyno() != null &&
data.getWord().getContent().getSyno().getSynos() != null) {
List<WordsBeanX.WordBean.ContentBean.SynoBean.SynosBean> adjList
= data.getWord().getContent().getSyno().getSynos();
for (int i = 0; i < adjList.size(); i++) {
JTextArea adjTextArea = new JTextArea();
adjTextArea.setLineWrap(true);
String adjStr = adjList.get(i).getPos() + ":" + adjList.get(i).getTran();
adjTextArea.append(adjStr);
if (config!=null&&config.isSyno()) {
if (adjList.get(i).getHwds() != null) {
adjTextArea.append("\n ");
adjTextArea.append("近义词:");
for (int j = 0; j < adjList.get(i).getHwds().size(); j++) {
if (!TextUtils.isEmpty(adjList.get(i).getHwds().get(j).getW())) {
adjTextArea.append(adjList.get(i).getHwds().get(j).getW() + ",");
}
}
}
}
adjMap.put(adjList.get(i).getPos(), adjTextArea);
}
}
for (JTextArea jTextArea : adjMap.values()) {
jPanel.add(jTextArea);
}
}
}
jPanel.add(new JLabel(" "));
return jPanel;
} else {
return new JLabel("对象解析失败");
}
} else {
return new JLabel("对象解析失败");
}
}
}
另外还有重要的plugin.xml需要添加
<extensions defaultExtensionNs="com.intellij">
<toolWindow id="learnEnPlugin" secondary="true" anchor="right"
factoryClass="com.longshihan.learnEN2.rightmenu.LearnToolsWindowFactory"/>
</extensions>
可以看到rightMenu这块的代码也很简单,复杂的逻辑都是从简单的逻辑一点点推理出来的。所以打好基础很重要。
3. 结束语
上面的demo的源码地址是:源码
下面讲一下如何上传吧,首先plugin.xml里面相关的配置配置完善,
<id>com.longshihan.learnEN</id>
<name>程序员每日英语</name>
<version>1.0</version>
<vendor email="577093937@qq.com" url="http://longshihan.androider.com">龙逝寒</vendor>
<description><![CDATA[
程序员每日英语<br>
]]></description>
<change-notes><![CDATA[
Add change notes here.<br>
<em>most HTML tags may be used</em>
]]>
</change-notes>
否则打包上传的时候也会提示报错。在build目录下面有个打包选项:
点击之后就可以看到生成一个jar或zip压缩包,这时候进IDEA的插件仓库(需要登录):跳转
按照要求上传就行,这个没什么难度。
至此IDEA初级部分已经说完了,像控制台的布局等都是万变不离其宗的,基础部分关于Anaction,布局,数据持久化这块已经说完了,后面会讲到插件的中级部分,关于Editor编辑器的处理,实时监听输入,插入代码等,敬请亟待。