转载请注明出处:【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步:

  1. 定义Action

    • actionPerformed()
    • update()
    • AnActionEvent对象
  2. 注册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机制)的更多相关文章

  1. [Android Pro] AndroidStudio IDE界面插件开发(进阶篇之Editor)

    转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53885981] 我们开发AndroidStudio ...

  2. [Android Pro] AndroidStudio IDE界面插件开发(Hello World篇)

    转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53856916] 工欲善其事必先利其器,自打从Ecl ...

  3. [Android Pro] AndroidStudio导出jar包

    reference :  http://blog.csdn.net/beijingshi1/article/details/38681281 不像在Eclipse,可以直接导出jar包.Android ...

  4. Android攻城狮学习笔记-进阶篇一

    点击快速抵达: 第1章 AndroidManifest配置文件 第2章 使用ListView显示信息列表 第3章 使用DatePicker及TimePicker显示当前日期和时间 第4章 使用Grid ...

  5. Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码) 来源 https://blog.csdn.net/jiangwei0910410003/article/details/51 ...

  6. Android动态方式破解apk进阶篇(IDA调试so源码)

    一.前言 今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk 我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为 ...

  7. Android UI开发第三十九篇——Tab界面实现汇总及比较

    Tab布局是iOS的经典布局,Android应用中也有大量应用,前面也写过Android中TAb的实现,<Android UI开发第十八篇——ActivityGroup实现tab功能>.这 ...

  8. Java IDE 编辑器 --- IntelliJ IDEA 进阶篇 生成 hibernate 实体与映射文件

    原文:转:Java IDE 编辑器 --- IntelliJ IDEA 进阶篇 生成 hibernate 实体与映射文件 2011-04-30 12:50 很多人不知道怎么用 IntelliJ IDE ...

  9. ESP8266开发之旅 进阶篇② 闲聊Arduino IDE For ESP8266烧录配置

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

随机推荐

  1. 交叉验证(Cross Validation)简介

    参考    交叉验证      交叉验证 (Cross Validation)刘建平 一.训练集 vs. 测试集 在模式识别(pattern recognition)与机器学习(machine lea ...

  2. Django -- settings 详解(转)

    Django -- settings 详解   Django settings详解 1.基础 DJANGO_SETTING_MODULE环境变量:让settings模块被包含到python可以找到的目 ...

  3. WIN10下 VS2017+OpenCv 3.4.1 配置

    写篇博客来记录一下opencv在VS中的配置. 一.下载OpenCv安装包 下载的途径有三种: 1.官网下载 但是官网下载真的是贼头大,首先下载好好的突然说下载中断,而且无法恢复,此外,还慢,毕竟外网 ...

  4. @repository的含义,并且有时候却不用写,为什么?

    //最后发现是这样的:@repository跟@Service,@Compent,@Controller这4种注解是没什么本质区别,都是声明作用,取不同的名字只是为了更好区分各自的功能.下图更多的作用 ...

  5. Unity3D导入MAX文件的一些问题(zz)

    1.轴向偏转 MAX模型导入Unity3D后,X轴会自动偏转-90度.是因为Unity3D采用的是左手坐标系,而3DMax采用的是右手坐标系.无奈啊,这是很多游戏引擎跟Max结合后都会产生的问题.兼容 ...

  6. Linux——线程

    线程 我们都知道一个程序的执行是由进程来完成的,而进程里真正执行代码却是由线程来完成,它是真正的执行流.通常将一个程序⾥里一个执行路线就叫做线程(thread).对它更准确的定义是:线程是“一个进程内 ...

  7. CentOS添加环境变量的三种方式

    CentOS添加环境变量的三种方式,以添加php环境变量为例,假定php的安装目录为 /usr/local/php5 一.仅对当前会话临时生效 [root@bogon ~]# export PATH= ...

  8. enode

    WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例 WeText项目:一个基于.NET实现的DDD.CQRS与微服务架构的演示案例 https://mp.weixin.qq ...

  9. nginx File not found 错误(转)

    当我没初始配在lnmp的时候,用浏览器打开查看php能否解析网页的时出现File not found 不用惊奇让我我们分析一下 使用php-fpm解析PHP,"No input file s ...

  10. jPlayer插件的使用

    文讲一下本人在使用jPlayer插件时的整个过程.出现的BUG已经解决办法. 最近在做bootstrap项目,项目中需要一个响应式.兼容IE7的视频播放插件,经过上网查找,找到了所谓可以兼容到IE6的 ...