[Android Pro] AndroidStudio IDE界面插件开发(进阶篇之Action机制)
转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53883500】
从上一篇《AndroidStudio插件开发(Hello World篇)》中我们已经大致了解了Action,这篇文章继续深入探究IntelliJ IDEA插件开发中的Action机制。一个Action本质上来说就是一个Java类,并且这个类需要继承AnAction。而一个Action对应于一个菜单项,每一次点击这个菜单项就回调这个Action的actionPerformed(AnActionEvent event)函数,因此我们定义的Action在继承AnAction时,需要重写actionPerformed函数。定义好Action类后,我们需要注册Action,即在plugin.xml文件中添加Action对应的标签,在这个标签中定义了Action应放置在界面的的哪个位置,作为哪个菜单项的子项等。接下来我们对Action机制进行深入。
1. 定义Action(继承AnAction)
定义Action只需简单地定义一个继承AnAction的子类即可,子类中,最重要的就是actionPerformed函数和update函数。
1.1 重写actionPerformed函数
我们知道,每次在菜单项中点击我们自定义的Action时,对应会执行AnAction的actionPerformed函数。对应actionPerformed函数的理解,只需记住,当回调actionPerformed函数函数时,就意味着当前Action被点击了一次。重写actionPerformed函数非常简单,这里简单弹出一个Hello World。
package com.huachao.plugin; import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages; /**
* Created by huachao on 2016/12/26.
*/
public class MyAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent event) {
Project project = event.getData(PlatformDataKeys.PROJECT);
Messages.showMessageDialog(project, "Hello World!", "Information", Messages.getInformationIcon());
}
}
1.2 重写update函数
我们知道,为了响应用户的点击操作,我们重写了actionPerformed函数。在actionPerformed函数中执行一些逻辑,比如弹出对话框,在打开文件中自动生成代码等等操作。这些逻辑在actionPerformed函数中完成就好,但是,有时候我们定义的插件只在某些场景中使用。比如说,当我们编写自动生成代码的插件时,只有当有文件打开时才可以正常执行。因此,当我们不希望用户点击我们定义的插件时,我们可以将插件隐藏,让用户无法看到插件,只有当符合插件执行的环境时,才让插件在菜单中显示。
为了能在用户点击自定义插件对应的菜单项之前动态判断是否将插件项显示,只需重写update函数。
update函数在Action状态发生更新时被回调,当Action状态刷新时,update函数被IDEA回调,并且传递AnActionEvent对象,AnAction对象中封装了当前Action对应的环境。
如何理解上面这段话呢?我们知道,我们定义的每个Action都在菜单中对应一个子选项(为了方便描述,本文称之为Action菜单项),当Action菜单项被点击或者是Action的父菜单(包含Action菜单项的菜单)被点击使得Action菜单项被显示出来时,就会回调update函数。在update被回调时,传入AnActionEvent对象,通过AnActionEvent对象我们可以判断当前编辑框是否已经打开等实时IDEA环境状况。
注意:先执行update函数,再执行actionPerformed函数。换言之,update发生在actionPerformed之前。
比如,我们想要实现:当编辑框被打开时显示自定义的Action菜单项,否则,将Action菜单项设置为灰色。
@Override
public void update(AnActionEvent e) {
Editor editor = e.getData(PlatformDataKeys.EDITOR); if (editor != null)
e.getPresentation().setEnabled(true);
else
e.getPresentation().setEnabled(false); }
代码中,如果editor!=null即编辑框已打开,将Action菜单项设置为可用状态(即正常颜色,黑色),否则设置为不可用状态(即灰色)。当然了,你也可以通过e.getPresentation().setVisible(false);将Action菜单项设置为不可见,这样Action菜单项就不会出现在菜单中。
另外,不要忘记在plugin.xml中将MyAction注册,具体注册方法可以参考上一篇文章《AndroidStudio插件开发(Hello World篇)》或者后一节的详细介绍。
当编辑框被打开时(即有文件打开时),可以看到我们自定义的插件是正常。
当编辑框被关闭时(即没有文件被打开时),可以看到我们自定义的插件是灰色。
注意:Action菜单项为灰色并不意味着被点击时actionPerformed不会被调用,相反,只要Action菜单项被点击,actionPerformed函数就会被调用。因此如果希望点击Action菜单项时不做响应,需要在actionPerformed函数里面再次做具体判断。
1.3 关于AnActionEvent
前面我们多次用到了AnActionEvent 对象,AnActionEvent 函数和update函数的形参都包含AnActionEvent对象。AnActionEvent对象是我们与IntelliJ IDEA交互的桥梁,我们可以通过AnActionEvent对象获取当前IntelliJ IDEA的各个模块对象,如编辑框窗口对象、项目窗口对象等,获取到这些对象我们就可以做一些定制的效果。
1.3.1 getData函数
通过AnActionEvent对象的getData函数可以得到IDEA界面各个窗口对象以及各个窗口为实现某些特定功能的对象。getData函数需要传入DataKey<T>对象,用于指明想要获取的IDEA中的哪个对象。在CommonDataKeys已经定义好各个IDEA对象对应的DataKey<T>对象。
CommonDataKeys.java定义的DataKey<T>对象如下:
public static final DataKey<Project> PROJECT = DataKey.create("project");
public static final DataKey<Editor> EDITOR = DataKey.create("editor");
public static final DataKey<Editor> HOST_EDITOR = DataKey.create("host.editor");
public static final DataKey<Caret> CARET = DataKey.create("caret");
public static final DataKey<Editor> EDITOR_EVEN_IF_INACTIVE = DataKey.create("editor.even.if.inactive");
public static final DataKey<Navigatable> NAVIGATABLE = DataKey.create("Navigatable");
public static final DataKey<Navigatable[]> NAVIGATABLE_ARRAY = DataKey.create("NavigatableArray");
public static final DataKey<VirtualFile> VIRTUAL_FILE = DataKey.create("virtualFile");
public static final DataKey<VirtualFile[]> VIRTUAL_FILE_ARRAY = DataKey.create("virtualFileArray");
public static final DataKey<PsiElement> PSI_ELEMENT = DataKey.create("psi.Element");
public static final DataKey<PsiFile> PSI_FILE = DataKey.create("psi.File");
public static final DataKey<Boolean> EDITOR_VIRTUAL_SPACE = DataKey.create("editor.virtual.space");
不仅仅CommonDataKeys中定义了DataKey<T>对象,为了添加更多的DataKey<T>对象并且兼容等,又提供了PlatformDataKeys类,PlatformDataKeys类是CommonDataKeys子类,也就是说,只要是CommonDataKeys有的,PlatformDataKeys类都有。
PlatformDataKeys.java定义的DataKey<T>对象如下:
public static final DataKey<FileEditor> FILE_EDITOR = DataKey.create("fileEditor");
public static final DataKey<String> FILE_TEXT = DataKey.create("fileText");
public static final DataKey<Boolean> IS_MODAL_CONTEXT = DataKey.create("isModalContext");
public static final DataKey<DiffViewer> DIFF_VIEWER = DataKey.create("diffViewer");
public static final DataKey<DiffViewer> COMPOSITE_DIFF_VIEWER = DataKey.create("compositeDiffViewer");
public static final DataKey<String> HELP_ID = DataKey.create("helpId");
public static final DataKey<Project> PROJECT_CONTEXT = DataKey.create("context.Project");
public static final DataKey<Component> CONTEXT_COMPONENT = DataKey.create("contextComponent");
public static final DataKey<CopyProvider> COPY_PROVIDER = DataKey.create("copyProvider");
public static final DataKey<CutProvider> CUT_PROVIDER = DataKey.create("cutProvider");
public static final DataKey<PasteProvider> PASTE_PROVIDER = DataKey.create("pasteProvider");
public static final DataKey<DeleteProvider> DELETE_ELEMENT_PROVIDER = DataKey.create("deleteElementProvider");
public static final DataKey<Object> SELECTED_ITEM = DataKey.create("selectedItem");
public static final DataKey<Object[]> SELECTED_ITEMS = DataKey.create("selectedItems");
public static final DataKey<Rectangle> DOMINANT_HINT_AREA_RECTANGLE = DataKey.create("dominant.hint.rectangle");
public static final DataKey<ContentManager> CONTENT_MANAGER = DataKey.create("contentManager");
public static final DataKey<ToolWindow> TOOL_WINDOW = DataKey.create("TOOL_WINDOW");
public static final DataKey<TreeExpander> TREE_EXPANDER = DataKey.create("treeExpander");
public static final DataKey<ExporterToTextFile> EXPORTER_TO_TEXT_FILE = DataKey.create("exporterToTextFile");
public static final DataKey<VirtualFile> PROJECT_FILE_DIRECTORY = DataKey.create("context.ProjectFileDirectory");
public static final DataKey<Disposable> UI_DISPOSABLE = DataKey.create("ui.disposable");
public static final DataKey<ContentManager> NONEMPTY_CONTENT_MANAGER = DataKey.create("nonemptyContentManager");
public static final DataKey<ModalityState> MODALITY_STATE = DataKey.create("ModalityState");
public static final DataKey<Boolean> SOURCE_NAVIGATION_LOCKED = DataKey.create("sourceNavigationLocked");
public static final DataKey<String> PREDEFINED_TEXT = DataKey.create("predefined.text.value");
public static final DataKey<String> SEARCH_INPUT_TEXT = DataKey.create("search.input.text.value");
public static final DataKey<Object> SPEED_SEARCH_COMPONENT = DataKey.create("speed.search.component.value");
public static final DataKey<Point> CONTEXT_MENU_POINT = DataKey.create("contextMenuPoint");
1.3.2 Presentation对象
一个Presentation对象表示一个Action在菜单中的外观,通过Presentation可以获取Action菜单项的各种属性,如显示的文本、描述、图标(Icon)等。并且可以设置当前Action菜单项的状态、是否可见、显示的文本等等。通过AnActionEvent对象的getPresentation()函数可以取得Presentation对象。
2. 注册Action(修改plugin.xml)
注册Action,我们可以手动直接修改plugin.xml文件,也可由IDEA直接自动帮我们生成,甚至是通过代码动态注册。其中,个人认为必须把手动注册过程掌握透彻,这样就能理解自动注册与代码注册的原理。
2.1 手动注册Action
2.1.1 单个Action
手动注册即我们直接修改plugin.xml文件,在plugin.xml文件(resoutces/META-INF/plugin.xml)中找到<actions>标签,并在<actions>标签中添加<action>标签。<action>标签的属性在上一篇文章中解释过,这里再解释一遍:
id:作为
<action>标签的唯一标识。一般以<项目名>.<类名>方式。
class:即我们自定义的AnAction类
text:显示的文字,如我们自定义的插件放在菜单列表中,这个文字就是对应的菜单项
description:对这个AnAction的描述
<add-to-group>标签用于描述当前Action放入到那个菜单组中,<add-to-group>标签主要关注anchor属性和relative-to-action属性。anchor属性用于描述位置,主要有四个选项:first、last、before、after。他们的含义如下:
first:放在所有子菜单的最前面
last:放在所有子菜单的最后
before:放在relative-to-action属性指定的ID的子菜单的前面
after:放在relative-to-action属性指定的ID的子菜单的后面
<keyboard-shortcut>标签用于描述快捷键,主要关注2个属性:keymap和first-keystroke。keymap使用默认值($default)就好,first-keystroke用于指定快捷键。
将Action菜单项放入到Help菜单的最前面,示例如下:
<actions>
<!-- Add your actions here -->
<action class="com.huachao.plugin.MyAction" id="StudyAction.MyAction" text="Hello Action">
<add-to-group group-id="HelpMenu" anchor="first"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt Q"/>
</action>
</actions>
2.1.2 Action组(Action Group)
前面我们都是将一个Action放入到已有的菜单中作为子选项。现在我们定义一个跟Help同级的菜单,或者是定义包含多个子选项的菜单,这就是Action Group。使用Action Group非常简单,就是在<actions>标签中添加<group>子标签,<group>标签主要关注3个属性:id、text、popup。id和text跟<action>标签意义一样,不再解释,但需要注意,text中如果需要首字母加下划线,则开头下“_”即可。popup属性用于描述是否有子菜单弹出,如果取值为true,则<group>标签的内所有的<action>子标签作为<group>菜单的子选项,否则,<group>标签的内所有的<action>子标签将替换<group>菜单项所在的位置,即没有<group>这一层菜单。下面通过一个例子进行对比。
<actions>
<group id="StudyAction.MyGroup" text="_MyGroup" popup="true">
<add-to-group group-id="HelpMenu" anchor="first"/>
<action class="com.huachao.plugin.MyAction" id="StudyAction.MyAction" text="Hello Action">
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt Q"/>
</action>
<action id="StudyAction.SecondAction" class="com.huachao.plugin.SecondAction" text="SecondAction"/>
</group> </actions>
运行结果如下:
将popup属性改为false运行结果如下:
注意到,我们将<group>的<add-to-group>子标签的group-id属性依然指定为Help菜单,现在我们换成与Help同级。将group-id属性指定为MainMenu,运行如下:
可以看到,IDEA的所有的导航菜单都放在MainMenu中,我们指定了anchor="first",因此被加入第一个位置。接下来我们再看看将group加入到编辑框窗口右键菜单,只需将group-id属性指定为EditorPopupMenu,运行如下:
修改为项目窗口右键菜单,修改group-id为:ProjectViewPopupMenu。运行如下:
2.2 IDEA自动注册Action
在我们熟悉了手动修改plugin.xml后,使用IDEA的方式就更简单了。直接点击在包目录上右击>New>Action。弹出框对应填写属性即可,这样在自动创建Action的同时,完成了Action的注册。
2.3 代码动态注册Action
代码动态注册Action主要是以Action Group动态添加和移除Action。前面我们在使用<group>标签时,没有使用到class属性,即我们没有定义自己的Action Group,而是使用默认的Action Group(DefaultActionGroup)。为了定制自己的Action Group,我们定义MyGroup类,使之继承ActionGroup类,并在<group>标签的class属性中指定com.huachao.plugin.MyGroup。
package com.huachao.plugin; import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; /**
* Created by huachao on 2016/12/26.
*/
public class MyGroup extends ActionGroup { @NotNull
@Override
public AnAction[] getChildren(@Nullable AnActionEvent anActionEvent) {
return new AnAction[]{new CustomAction("first"),new CustomAction("second")};
} class CustomAction extends AnAction {
public CustomAction(String text) {
super(text);
}
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
}
}
}
plugin.xml文件中对应的<actions>标签如下:
<actions>
<group id="StudyAction.MyGroup" class="com.huachao.plugin.MyGroup" text="_MyGroup" popup="true">
<add-to-group group-id="MainMenu" anchor="last"/>
</group>
</actions>
运行结果如下:
如果我们想在plugin.xml中注册Action,并且想修改Group的菜单属性。我们只需重写DefaultActionGroup的update函数,DefaultActionGroup的update函数与AnAction的update函数意义差不多,前面解释过AnAction的update函数,这里就不再解释。例如,我们将Group菜单添加一个图标,代码如下:
package com.huachao.plugin; import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.editor.Editor; /**
* Created by huachao on 2016/12/26.
*/
public class MyGroup extends DefaultActionGroup { @Override
public void update(AnActionEvent e) {
Editor editor = e.getData(CommonDataKeys.EDITOR);
e.getPresentation().setVisible(true);
e.getPresentation().setEnabled(editor != null);
e.getPresentation().setIcon(AllIcons.General.Error);
}
}
运行结果如下:
3. 总结
定义一个AndroidStudio插件只需简单的2步:
- 定义Action
- actionPerformed()
- update()
- AnActionEvent对象
- 注册Action
- 手动修改plugin.xml
- Action Group
- IDEA自动生成(New>Action>…)
- 代码注册(通过Acton Group动态添加)
相比上一篇文章,在本文中,我们知其然更知其所以然。为后面定制AndroidStudio打下基础。
参考资料
Action System相关类源码(Github):《intellij-community》
官网资料:http://www.jetbrains.org/intellij/sdk/docs/tutorials/action_system.html
[Android Pro] AndroidStudio IDE界面插件开发(进阶篇之Action机制)的更多相关文章
- [Android Pro] AndroidStudio IDE界面插件开发(进阶篇之Editor)
转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53885981] 我们开发AndroidStudio ...
- [Android Pro] AndroidStudio IDE界面插件开发(Hello World篇)
转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53856916] 工欲善其事必先利其器,自打从Ecl ...
- [Android Pro] AndroidStudio导出jar包
reference : http://blog.csdn.net/beijingshi1/article/details/38681281 不像在Eclipse,可以直接导出jar包.Android ...
- Android攻城狮学习笔记-进阶篇一
点击快速抵达: 第1章 AndroidManifest配置文件 第2章 使用ListView显示信息列表 第3章 使用DatePicker及TimePicker显示当前日期和时间 第4章 使用Grid ...
- Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)
Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码) 来源 https://blog.csdn.net/jiangwei0910410003/article/details/51 ...
- Android动态方式破解apk进阶篇(IDA调试so源码)
一.前言 今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk 我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为 ...
- Android UI开发第三十九篇——Tab界面实现汇总及比较
Tab布局是iOS的经典布局,Android应用中也有大量应用,前面也写过Android中TAb的实现,<Android UI开发第十八篇——ActivityGroup实现tab功能>.这 ...
- Java IDE 编辑器 --- IntelliJ IDEA 进阶篇 生成 hibernate 实体与映射文件
原文:转:Java IDE 编辑器 --- IntelliJ IDEA 进阶篇 生成 hibernate 实体与映射文件 2011-04-30 12:50 很多人不知道怎么用 IntelliJ IDE ...
- ESP8266开发之旅 进阶篇② 闲聊Arduino IDE For ESP8266烧录配置
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
随机推荐
- PHP 与redis 操作添加处理投票
<?php header("Content-Type:text/html;charset=utf-8"); include 'lib/mysql.class.php'; $m ...
- bzoj 1150
思路:写的时候感觉是贪心但是没有什么思路... 看了题解,原来有一个选了能反悔的贪心思路, 如果最优那么每个城市只能和旁边的相邻 城市连边,所以问题变成了由n个数,不能取相邻的两个数,取k个最小是多少 ...
- Spark中hashshuffle与sortshuffle
在spark1.2以上的版本中,默认shuffle的方式已经变成了sortshuffle(在spark.shuffle.manager修改org.apache.spark.shuffle.sort.H ...
- Mysql介绍,与将脚本导入新数据库
一:介绍 1.介绍 Mysql是最流行的关系型数据库管理系统,在WEB应用方面MySQL是最好的RDBMS(Relational Database Management System:关系数据库管理系 ...
- Chrome浏览器被hao123劫持,浏览器主页会被篡改为 hao123等
先放一个知乎帖子: https://www.zhihu.com/question/21883209 我就只放几个有效解决办法了,具体的可以看上边那个帖子 方案一: 删掉桌面上的chrome图标 ...
- luoguP4000 斐波那契数列
题目链接 luoguP4000 斐波那契数列 题解 根据这个东西 https://www.cnblogs.com/sssy/p/9418732.html 我们可以找出%p意义下的循环节 然后就可以做了 ...
- Cdq分治整体二分学习记录
这点东西前前后后拖了好几个星期才学会……还是自己太菜啊. Cdq分治的思想是:把问题序列分割成左右两个,先单独处理左边,再处理左边对右边的影响,再单独处理右边.这样可以消去数据结构上的一个log,降低 ...
- BZOJ1768 : [Ceoi2009]logs
从上到下枚举行,可以$O(m)$更新现在每一列往上连续的1的个数,也可以在$O(m)$的时间内完成排序.总复杂度$O(nm)$. #include<cstdio> #define M 15 ...
- 20172308 实验四《Java面向对象程序设计 》实验报告
20172308 2017-2018-2 <程序设计与数据结构>实验四报告 课程:<程序设计与数据结构> 班级: 1723 姓名: 周亚杰 学号:20172308 实验教师:王 ...
- Intel Code Challenge Elimination Round (Div.1 + Div.2, combined) A. Broken Clock 水题
A. Broken Clock 题目连接: http://codeforces.com/contest/722/problem/A Description You are given a broken ...