打造酷炫的AndroidStudio插件

前面几篇文章学习了AndroidStudio插件的基础后,这篇文章打算开发一个酷炫一点的插件。因为会用到前面的基础,所以如果没有看前面系列文章的话,请先返回。当然,如果有基础的可以忽略之。先看看本文实现的最终效果如下(好吧,很多人说看的眼花):

虽然并没有什么实际用途,但是作为学习插件开发感觉挺有意思的。

1. 基本思路

基本思路可以归结如下几步:

1)、通过Editor对象可以拿到封装代码编辑框的JComponent对象,即调用如下函数:JComponent component = editor.getContentComponent();

2)、获取输入或删除的字符(或字符串。通过选中多个字符删除或粘贴则为字符串)。可以通过添加DocumentListener,监听文本变化。重写beforeDocumentChange函数,并通过DocumentEvent对象取得新的字符和旧的字符。分别通过函数:documentEvent.getNewFragment()、documentEvent.getOldFragment()。它们代表着输入的字符串和删除的字符串。

3)、将输入或删除的字符串在编辑框中显示出来。只需将各个字符串分别封装到Jlabel中,并将JLabel加入到JComponent中即可显示出输入或删除的字符串(或字符)。

4)、获取用于显示各个字符串的Jlabel对象在JComponent中的坐标位置。添加CaretListener,监听光标的位置。每次光标位置发生变化,就刷新到临时变量中。当要添加一个JLabel时,获取当前的临时变量中保存的位置即为Jlabel应存放的位置。

5)、动画效果。开启一个线程,对于输入的字符串,只需不断修改字体大小。对于删除的字符串,不断修改JLabel的位置和字体大小。

6)、插件状态保存到本地。用户点击开启或者关闭插件以及其他开关选项,需要保存起来,下一次开启AndroidStudio时可以恢复。只需实现PersistentStateComponent接口即可。

7)、用户未点击Action时,能自动注册DocumentListener。这主要是考虑到,用户开启了插件,下一次打开AndroidStudio时无需点击Aciton,直接输入时就能自动注册监听Document变化。由于注册DocumentListener需要Editor对象,而想要取得Editor对象只有两种方式:通过AnActionEvent对象的getData函数;另一种是通过DataContext对象,使用
PlatformDataKeys.EDITOR.getData(dataContext)方法。显然第一种方法只能在AnAction类的actionPerformed和update方法中才能取得。因此只能考虑用第二种方法,而在前面文章中介绍过,监听键盘字符输入时,可以取得DataContext对象。即重写TypedActionHandler接口的execute函数,execute参数中传递了DataContext对象。

可以看到,以上用到的知识都是前面3篇文章中介绍过的内容,并不复杂。只有第6条没有介绍,本文中会学习本地持久化数据。

2. 插件状态本地持久化

先看看如何实现本地持久化。首先定义一个全局共享变量类GlobalVar,使之实现PersistentStateComponent接口。先来个视觉上的认识,直接看代码。

/**
 * 配置文件
 * Created by huachao on 2016/12/27.
 */
@State(
  name = "amazing-mode",
  storages = {
    @Storage(
      id = "amazing-mode",
      file = "$APP_CONFIG$/amazing-mode_setting.xml"
    )
  }
)
public class GlobalVar implements PersistentStateComponent<GlobalVar.State> {

 public static final class State {
  public boolean IS_ENABLE;
  public boolean IS_RANDOM;
 }

 @Nullable
 @Override
 public State getState() {
  return this.state;
 }

 @Override
 public void loadState(State state) {
  this.state = state;
 }

 public State state = new State();

 public GlobalVar() {

  state.IS_ENABLE = false;
  state.IS_RANDOM = false;
 }

 public static GlobalVar getInstance() {
  return ServiceManager.getService(GlobalVar.class);
 }

}

使用@State注解指定本地存储位置、id等。具体实现基本可以参照这个模板写,就是重写loadState()和getState()两个函数。另外需要注意一下getInstance()函数的写法。基本模板就这样,没有什么特别的地方,依葫芦画瓢就行。

还有一点特别重要,一定要记得在plugin.xml中注册这个持久化类。找到<extensions>标签,加入<applicationService>子标签,如下:

<extensions defaultExtensionNs="com.intellij">
 <!-- Add your extensions here -->
 <applicationService
   serviceImplementation="com.huachao.plugin.util.GlobalVar"
   serviceInterface="com.huachao.plugin.util.GlobalVar"
 />
</extensions>

这样写完以后,在获取数据的时候,直接如下:

private GlobalVar.State state = GlobalVar.getInstance().state;
//state.IS_ENABLE
//state.IS_RANDOM

3. 编写Action

主要包含2个Action:EnableAction和RandomColorAction。EnableAction用于设置插件的开启或关闭,RandomColorAction用于设置是否使用随机颜色。由于二者功能类似,我们只看看EnableAction的实现:

/**
 * Created by huachao on 2016/12/27.
 */
public class EnableAction extends AnAction {
 private GlobalVar.State state = GlobalVar.getInstance().state;

 @Override
 public void update(AnActionEvent e) {
  Project project = e.getData(PlatformDataKeys.PROJECT);
  Editor editor = e.getData(PlatformDataKeys.EDITOR);
  if (editor == null || project == null) {
   e.getPresentation().setEnabled(false);
  } else {
   JComponent component = editor.getContentComponent();
   if (component == null) {
    e.getPresentation().setEnabled(false);
   } else {
    e.getPresentation().setEnabled(true);
   }
  }
  updateState(e.getPresentation());
 }

 @Override
 public void actionPerformed(AnActionEvent e) {
  Project project = e.getData(PlatformDataKeys.PROJECT);
  Editor editor = e.getData(PlatformDataKeys.EDITOR);
  if (editor == null || project == null) {
   return;
  }
  JComponent component = editor.getContentComponent();
  if (component == null)
   return;
  state.IS_ENABLE = !state.IS_ENABLE;
  updateState(e.getPresentation());

  //只要点击Enable项,就把缓存中所有的文本清理
  CharPanel.getInstance(component).clearAllStr();

  GlobalVar.registerDocumentListener(project, editor, state.IS_ENABLE);
 }

 private void updateState(Presentation presentation) {

  if (state.IS_ENABLE) {
   presentation.setText("Enable");
   presentation.setIcon(AllIcons.General.InspectionsOK);
  } else {
   presentation.setText("Disable");
   presentation.setIcon(AllIcons.Actions.Cancel);
  }
 }

}

代码比较简单,跟前面几篇文章中写的很相似。只需注意一下actionPerformed函数中调用了两个函数:

CharPanel.getInstance(component).clearAllStr();
GlobalVar.registerDocumentListener(project, editor, state.IS_ENABLE);

CharPanel对象中的clearAllStr()函数后面介绍,只需知道它是将缓存中的所有动画对象清除。GlobalVar对象中的registerDocumentListener ()函数是添加DocumentListener监听器。实现本文效果的中枢是DocumentListener监听器,是通过监听文本内容发生变化来获取实现字符动画效果的数据。因此应应可能早地将DocumentListener监听器加入,而DocumentListener监听器加入的时刻包括:用户点击Action、用户敲入字符。也就是说,多个地方都存在添加DocumentListener监听器的可能。因此把这个函数抽出来,加入到GlobalVar中,具体实现如下:

private static AmazingDocumentListener amazingDocumentListener = null;

public static void registerDocumentListener(Project project, Editor editor, boolean isFromEnableAction) {
 if (!hasAddListener || isFromEnableAction) {
  hasAddListener = true;
  JComponent component = editor.getContentComponent();
  if (component == null)
   return;
  if (amazingDocumentListener == null) {

   amazingDocumentListener = new AmazingDocumentListener(project);
   Document document = editor.getDocument();
   document.addDocumentListener(amazingDocumentListener);
  }

  Thread thread = new Thread(CharPanel.getInstance(component));
  thread.start();
 }
}

可以看到,一旦DocumentListener监听器被加入,就会开启一个线程,这个线程是一直执行,实现动画效果。DocumentListener监听器只需加入一次即可。

4. 实现动画

前面多次使用到了CharPanel对象,CharPanel对象就是用于实现动画效果。先源码:

package com.huachao.plugin.util;

import com.huachao.plugin.Entity.CharObj;

import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;

/**
 * Created by huachao on 2016/12/27.
 */
public class CharPanel implements Runnable {
 private JComponent mComponent;
 private Point mCurPosition;
 private Set<CharObj> charSet = new HashSet<CharObj>();
 private List<CharObj> bufferList = new ArrayList<CharObj>();

 private GlobalVar.State state = GlobalVar.getInstance().state;

 public void setComponent(JComponent component) {
  mComponent = component;
 }

 public void run() {
  while (state.IS_ENABLE) {
   if (GlobalVar.font != null) {
    synchronized (bufferList) {
     charSet.addAll(bufferList);
     bufferList.clear();
    }
    draw();
    int minFontSize = GlobalVar.font.getSize();

    //修改各个Label的属性,使之能以动画形式出现和消失
    Iterator<CharObj> it = charSet.iterator();
    while (it.hasNext()) {
     CharObj obj = it.next();
     if (obj.isAdd()) {//如果是添加到文本框
      if (obj.getSize() <= minFontSize) {//当字体大小到达最小后,使之消失
       mComponent.remove(obj.getLabel());
       it.remove();
      } else {//否则,继续减小
       int size = obj.getSize() - 6 < minFontSize ? minFontSize : (obj.getSize() - 6);
       obj.setSize(size);
      }
     } else {//如果是从文本框中删除
      Point p = obj.getPosition();
      if (p.y <= 0 || obj.getSize() <= 0) {//如果到达最底下,则清理
       mComponent.remove(obj.getLabel());
       it.remove();
      } else {
       p.y = p.y - 10;
       int size = obj.getSize() - 1 < 0 ? 0 : (obj.getSize() - 1);
       obj.setSize(size);
      }
     }
    }

   }
   try {
    if (charSet.isEmpty()) {
     synchronized (charSet) {
      charSet.wait();
     }
    }
    Thread.currentThread().sleep(50);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }

 //绘制文本,本质上只是修改各个文本的位置和字体大小
 private void draw() {
  if (mComponent == null)
   return;

  for (CharObj obj : charSet) {
   JLabel label = obj.getLabel();

   Font font = new Font(GlobalVar.font.getName(), GlobalVar.font.getStyle(), obj.getSize());

   label.setFont(font);
   FontMetrics metrics = label.getFontMetrics(label.getFont());
   int textH = metrics.getHeight(); //字符串的高, 只和字体有关
   int textW = metrics.stringWidth(label.getText()); //字符串的宽
   label.setBounds(obj.getPosition().x, obj.getPosition().y - (textH - GlobalVar.minTextHeight), textW, textH);
  }
  mComponent.invalidate();
 }

 public void clearAllStr() {
  synchronized (bufferList) {
   bufferList.clear();
   charSet.clear();

   Iterator<CharObj> setIt = charSet.iterator();
   while (setIt.hasNext()) {
    CharObj obj = setIt.next();
    mComponent.remove(obj.getLabel());
   }

   Iterator<CharObj> bufferIt = bufferList.iterator();
   while (bufferIt.hasNext()) {
    CharObj obj = bufferIt.next();
    mComponent.remove(obj.getLabel());
   }
  }
 }

 //单例模式,静态内部类
 private static class SingletonHolder {
  //静态初始化器,由JVM来保证线程安全
  private static CharPanel instance = new CharPanel();
 }

 //返回单例对象
 public static CharPanel getInstance(JComponent component) {
  if (component != null) {
   SingletonHolder.instance.mComponent = component;
  }
  return SingletonHolder.instance;
 }

 //由光标监听器回调,由此可动态获取当前光标位置
 public void setPosition(Point position) {
  this.mCurPosition = position;
 }

 /**
  * 将字符串添加到列表中。
  *
  * @isAdd 如果为true表示十新增字符串,否则为被删除字符串
  * @str 字符串
  */
 public void addStrToList(String str, boolean isAdd) {
  if (mComponent != null && mCurPosition != null) {

   CharObj charObj = new CharObj(mCurPosition.y);
   JLabel label = new JLabel(str);
   charObj.setStr(str);
   charObj.setAdd(isAdd);
   charObj.setLabel(label);
   if (isAdd)
    charObj.setSize(60);
   else
    charObj.setSize(GlobalVar.font.getSize());
   charObj.setPosition(mCurPosition);
   if (state.IS_RANDOM) {
    label.setForeground(randomColor());
   } else {
    label.setForeground(GlobalVar.defaultForgroundColor);
   }
   synchronized (bufferList) {
    bufferList.add(charObj);
   }
   if (charSet.isEmpty()) {
    synchronized (charSet) {
     charSet.notify();
    }
   }

   mComponent.add(label);
  }
 }

 //以下用于产生随机颜色
 private static final Color[] COLORS = {Color.GREEN, Color.BLACK, Color.BLUE, Color.ORANGE, Color.YELLOW, Color.RED, Color.CYAN, Color.MAGENTA};

 private Color randomColor() {
  int max = COLORS.length;
  int index = new Random().nextInt(max);
  return COLORS[index];
 }
}

解释一下两个关键函数run()和draw()。run()函数是开启新线程开始执行的函数,它的实现是一个循环,当插件开启时会一直循环运行。CharPanel使用了2个集合来保持用户删除或者添加的字符串, charSet是会直接被显示出来的,bufferList保存的是DocumentListener监听器监听到的输入或删除的字符串。输入或删除的字符串都封装到CharObj类中。run函数中每一次循环之前,先将bufferList中数据全部转移到charSet中。为什么要使用2个集合呢?这主要是因为,当循环遍历charSet时,如果DocumentListener监听到的变化数据直接加入到charSet中,会导致出错。因为Java的集合在遍历时,不允许添加或删除里面的元素。

run函数每一次循环都会调用draw()函数,draw()函数根据CharObj封装的数据,将JLabel的位置属性和字体属性重新设置一次,这样就使得JLabel有动画效果,因为run函数的每次循环的最后会逐步修改字体大小和位置数据。

5. 源码

其他代码比较简单,对着代码解释也没什么意思。直接献上源码,如有疑惑的地方请留言,我尽量找时间一一回复。

Github地址:https://github.com/huachao1001/Amazing-Mode

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 为Android Studio编写自定义Gradle插件的教程

    Google已经建议Android开发全部转向Android Studio开发,Android Studio 是使用gradle编译.打包的,那么问题来了,gradle可是有一堆东西...,为了彻底了解gradle,今天就来学习下如何写自己的gradle插件(当然插件源码是使用groovy写的),先看如下代码目录: 如上图所示,plugin目录是插件源码目录,sample是用来测试插件的. 1.在目录plugin/src/main/groovy/com/micky/gradle/下新建插件类My

  • Jenkins 关闭和重启详细介绍及实现

    Jenkins 关闭和重启 我们用jar -jar jenkins.war来启动jenkins服务器,那么我们如何关闭或者重启jenkins服务器呢?经过搜索找到了相应的方法. 关闭jenkins服务 只需要在访问jenkins服务器的网址url地址后加上exit.例如我jenkins的地址http://localhost:8080/,那么我只需要在浏览器地址栏上敲下http://localhost:8080/exit 网址就能关闭jenkins服务. 按return键后会跳转到如下网页: 点击

  • jenkins 远程构建Android的过程详解

    由于企业的需求,需要做一个网站开分享每个版本的Android的app,所以需要使用的工具如下: Jenkins平台,远程编译环境服务器一台,web服务器一台,根据自己的选择,可以搭配自己的资源,废话少说,直奔主题 1. Jenkins的操作 在Jenkins中添加一个节点,设置好远程的工作目录,创建好服务器的标签,然后创建好相关的环境键值对,比如Android_home,Java_home,Gradle_home等,这些都是比较平常的操作,这里就不罗嗦了,值得注意的有两点:第一,java的路径问

  • Android Studio插件之Jenkins插件详解

    现在我就来介绍Android Studio上的Jenkins插件,让你可以更加方便地使用Jenkins.用Jenkins持续集成很久了,再Android Studio上的Jenkins插件也有一段时间了,用了该Jenkins插件之后,就不需要每次都在浏览器中区打开Jenkins服务器,然后输入用户名和密码进行登陆,再接着去找到相应的Jenkins任务进行编译了.至少我用起来很方便,这里推荐你们也使用! 现在先来介绍介绍该插件如何使用. 一.Jenkins插件功能介绍 1.Jenkins任务列表

  • 打造酷炫的AndroidStudio插件

    前面几篇文章学习了AndroidStudio插件的基础后,这篇文章打算开发一个酷炫一点的插件.因为会用到前面的基础,所以如果没有看前面系列文章的话,请先返回.当然,如果有基础的可以忽略之.先看看本文实现的最终效果如下(好吧,很多人说看的眼花): 虽然并没有什么实际用途,但是作为学习插件开发感觉挺有意思的. 1. 基本思路 基本思路可以归结如下几步: 1).通过Editor对象可以拿到封装代码编辑框的JComponent对象,即调用如下函数:JComponent component = edito

  • 基于 Vue 实现一个酷炫的 menu插件

    写在前面 最近看到一个非常酷炫的menu插件,一直想把它鼓捣成vue形式,谁让我是vue的死灰粉呢,如果这都不算爱:pensive:.:laughing:开个小玩耍,我们一起来探索黑魔法吧.观看本教程的读者需要具备一定的vue和css3的知识. 本文结构 1.效果演示 2.使用方法介绍 3.关键步骤讲解 正文 1.效果演示 pic_1 pic2 pic_3 在线演示 live demo 2.使用介绍 项目地址:github.com/MingSeng-W/vue-bloom-menu ,clone

  • 简单JS打造酷炫代码雨(黑客高逼格)

    电影黑客帝国有个代码雨效果,满满的既视感,身为程序猿的你羡慕吗?只要很简单的HTML+JavaScript就能实现,甚至不需要懂任何技术.这篇文章主要介绍了简单JS打造酷炫代码雨(黑客高逼格),需要的朋友可以参考下 <!doctype html> <html> <head> <meta charset="utf-8" /> <title>流星雨</title> <meta name="keyword

  • 基于RxJava实现酷炫启动页

    前言 RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的.基于事件的程序的库).这就是 RxJava ,概括得非常精准. 之前注意到coding APP启动页很是酷炫,今天我们使用RxJava和属性动画模仿实现其效果.

  • Asp.net使用SignalR实现酷炫端对端聊天功能

    一.引言 在前一篇文章已经详细介绍了SignalR了,并且简单介绍它在Asp.net MVC 和WPF中的应用.在上篇博文介绍的都是群发消息的实现,然而,对于SignalR是为了实时聊天而生的,自然少了不像QQ一样的端对端的聊天了.本篇博文将介绍如何使用SignalR来实现类似QQ聊天的功能. 二.使用SignalR实现端对端聊天的思路 在介绍具体实现之前,我先来介绍了使用SignalR实现端对端聊天的思路.相信大家在前篇文章已经看到过Clients.All.sendMessage(name,

  • Python+Kepler.gl轻松制作酷炫路径动画的实现示例

    1. 简介 Kepler.gl相信很多人都听说过,作为Uber几年前开源的交互式地理信息可视化工具,kepler.gl依托WebGL强大的图形渲染能力,可以在浏览器端以多种形式轻松展示大规模数据集. 更令人兴奋的是Kepler.gl在去年推出了基于Python的接口库keplergl,结合jupyter notebook/jupyter lab的相关拓展插件,使得我们可以通过编写Python程序配合Kepler.gl更灵活地制作各种可视化作品. 而随着近期keplergl的更新,更多的新特性得以

  • JavaWeb文件上传下载实例讲解(酷炫的文件上传技术)

    一.课程概述 在Web应用系统开发中,文件上传功能是非常常用的功能,今天来主要讲讲JavaWeb中的文件上传功能的相关技术实现,并且随着互联网技术的飞速发展,用户对网站的体验要求越来越高,在文件上传功能的技术上也出现许多创新点,例如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,大文件断点续传,大文件秒传等等. 本课程需要的基础知识: 了解基本的Http协议内容 基本IO流操作技术 Servlet基础知识 javascript/jQuery技术基础知识 二.文件上传的基础 对于文件

  • 酷炫!趣味十足的Linux命令

    1. pv 命令 有时候我们在电影屏幕上看到一些字幕一个个匀速显示出来,像有人在边敲键盘,边显示一样.Linux上的pv命令可以实现这种效果. 默认情况下,Linux是没有pv命令的,需要自行安装. 首先安装命令: # yum install pv [On RedHat based Systems] # sudo apt-get install pv [On Debian based Systems] 现在运行如下命令: 复制代码 代码如下: $ echo "Tecmint[dot]com is

  • 通过JQuery实现win8一样酷炫的动态磁贴效果(示例代码)

    我个人表示非常喜欢微软新一代的产品,先不管它产品的成熟与否,但是它带来的是全新的产品.所谓全新,是指在用户体验上,苹果这些年的成功使得所有产品都在模仿它的界面,包括安卓在内,不知道大家的感觉如何,反正我是对这些圆角矩形产生了审美疲劳(苹果以及安卓的粉丝勿喷,这里仅仅是从界面上评价,事实上从整体上来说,微软还是有差距的),当年wp的推出让我眼前一亮,马上喜欢上了Metro风格的产品,直至今天wp8以及win8开始越来越成熟. 写的不好,欢迎各位看官指正批评,不欢迎无故猛喷.大神请绕道. 废话少说,

  • JS库particles.js创建超炫背景粒子插件(附源码下载)

    插件描述:particles.js用于创建粒子的轻量级 JavaScript 库. 查看 效果             源码下载 使用 加载 particles.js和配置粒子 <div id="particles-js"></div> <script src="particles.js"></script> app.js /* particlesJS.load(@dom-id, @path-json, @callba

随机推荐