打造酷炫AndroidStudio插件

轉(zhuǎn)載請注明出處:【huachao1001的簡書:http://www.reibang.com/users/0a7e42698e4b/latest_articles】

前面幾篇文章學(xué)習(xí)了AndroidStudio插件的基礎(chǔ)后,這篇文章打算開發(fā)一個(gè)酷炫一點(diǎn)的插件胸墙。因?yàn)闀?huì)用到前面的基礎(chǔ)我注,所以如果沒有看前面系列文章的話,請先返回迟隅。當(dāng)然但骨,如果有基礎(chǔ)的可以忽略之。先看看本文實(shí)現(xiàn)的最終效果如下(好吧智袭,很多人說看的眼花):

最終效果
最終效果

雖然并沒有什么實(shí)際用途奔缠,但是作為學(xué)習(xí)插件開發(fā)感覺挺有意思的。

1. 基本思路

基本思路可以歸結(jié)如下幾步:

  1. 通過Editor對象可以拿到封裝代碼編輯框的JComponent對象吼野,即調(diào)用如下函數(shù):JComponent component = editor.getContentComponent();

  2. 獲取輸入或刪除的字符(或字符串校哎。通過選中多個(gè)字符刪除或粘貼則為字符串)⊥剑可以通過添加DocumentListener闷哆,監(jiān)聽文本變化。重寫beforeDocumentChange函數(shù)谚攒,并通過DocumentEvent對象取得新的字符和舊的字符阳准。分別通過函數(shù):documentEvent.getNewFragment()氛堕、documentEvent.getOldFragment()馏臭。它們代表著輸入的字符串和刪除的字符串。

  3. 將輸入或刪除的字符串在編輯框中顯示出來。只需將各個(gè)字符串分別封裝到Jlabel中括儒,并將JLabel加入到JComponent中即可顯示出輸入或刪除的字符串(或字符)绕沈。

  4. 獲取用于顯示各個(gè)字符串的Jlabel對象在JComponent中的坐標(biāo)位置。添加CaretListener帮寻,監(jiān)聽光標(biāo)的位置乍狐。每次光標(biāo)位置發(fā)生變化,就刷新到臨時(shí)變量中固逗。當(dāng)要添加一個(gè)JLabel時(shí)浅蚪,獲取當(dāng)前的臨時(shí)變量中保存的位置即為Jlabel應(yīng)存放的位置。

  5. 動(dòng)畫效果烫罩。開啟一個(gè)線程惜傲,對于輸入的字符串,只需不斷修改字體大小贝攒。對于刪除的字符串盗誊,不斷修改JLabel的位置和字體大小。

  6. 插件狀態(tài)保存到本地隘弊。用戶點(diǎn)擊開啟或者關(guān)閉插件以及其他開關(guān)選項(xiàng)哈踱,需要保存起來,下一次開啟AndroidStudio時(shí)可以恢復(fù)梨熙。只需實(shí)現(xiàn)PersistentStateComponent接口即可开镣。

  7. 用戶未點(diǎn)擊Action時(shí),能自動(dòng)注冊DocumentListener串结。這主要是考慮到哑子,用戶開啟了插件,下一次打開AndroidStudio時(shí)無需點(diǎn)擊Aciton肌割,直接輸入時(shí)就能自動(dòng)注冊監(jiān)聽Document變化卧蜓。由于注冊DocumentListener需要Editor對象,而想要取得Editor對象只有兩種方式:通過AnActionEvent對象的getData函數(shù)把敞;另一種是通過DataContext對象弥奸,使用
    PlatformDataKeys.EDITOR.getData(dataContext)方法。顯然第一種方法只能在AnAction類的actionPerformedupdate方法中才能取得奋早。因此只能考慮用第二種方法盛霎,而在前面文章中介紹過,監(jiān)聽鍵盤字符輸入時(shí)耽装,可以取得DataContext對象愤炸。即重寫TypedActionHandler接口的execute函數(shù),execute參數(shù)中傳遞了DataContext對象掉奄。

可以看到规个,以上用到的知識(shí)都是前面3篇文章中介紹過的內(nèi)容,并不復(fù)雜。只有第6條沒有介紹诞仓,本文中會(huì)學(xué)習(xí)本地持久化數(shù)據(jù)缤苫。

2. 插件狀態(tài)本地持久化

先看看如何實(shí)現(xiàn)本地持久化。首先定義一個(gè)全局共享變量類GlobalVar墅拭,使之實(shí)現(xiàn)PersistentStateComponent接口活玲。先來個(gè)視覺上的認(rèn)識(shí),直接看代碼谍婉。

/**
 * 配置文件
 * 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注解指定本地存儲(chǔ)位置舒憾、id等。具體實(shí)現(xiàn)基本可以參照這個(gè)模板寫穗熬,就是重寫loadState()和getState()兩個(gè)函數(shù)珍剑。另外需要注意一下getInstance()函數(shù)的寫法∷缆剑基本模板就這樣招拙,沒有什么特別的地方,依葫蘆畫瓢就行措译。

還有一點(diǎn)特別重要别凤,一定要記得在plugin.xml中注冊這個(gè)持久化類。找到<extensions>標(biāo)簽领虹,加入<applicationService>子標(biāo)簽规哪,如下:

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

這樣寫完以后,在獲取數(shù)據(jù)的時(shí)候塌衰,直接如下:


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

3. 編寫Action

主要包含2個(gè)Action:EnableActionRandomColorAction诉稍。EnableAction用于設(shè)置插件的開啟或關(guān)閉,RandomColorAction用于設(shè)置是否使用隨機(jī)顏色最疆。由于二者功能類似杯巨,我們只看看EnableAction的實(shí)現(xiàn):


/**
 * 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());

        //只要點(diǎn)擊Enable項(xiàng),就把緩存中所有的文本清理
        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函數(shù)中調(diào)用了兩個(gè)函數(shù):

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

CharPanel對象中的clearAllStr()函數(shù)后面介紹,只需知道它是將緩存中的所有動(dòng)畫對象清除获诈。GlobalVar對象中的registerDocumentListener ()函數(shù)是添加DocumentListener監(jiān)聽器仍源。實(shí)現(xiàn)本文效果的中樞是DocumentListener監(jiān)聽器,是通過監(jiān)聽文本內(nèi)容發(fā)生變化來獲取實(shí)現(xiàn)字符動(dòng)畫效果的數(shù)據(jù)舔涎。因此應(yīng)應(yīng)可能早地將DocumentListener監(jiān)聽器加入笼踩,而DocumentListener監(jiān)聽器加入的時(shí)刻包括:用戶點(diǎn)擊Action、用戶敲入字符亡嫌。也就是說嚎于,多個(gè)地方都存在添加DocumentListener監(jiān)聽器的可能桶至。因此把這個(gè)函數(shù)抽出來,加入到GlobalVar中匾旭,具體實(shí)現(xiàn)如下:

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監(jiān)聽器被加入圃郊,就會(huì)開啟一個(gè)線程价涝,這個(gè)線程是一直執(zhí)行,實(shí)現(xiàn)動(dòng)畫效果持舆。DocumentListener監(jiān)聽器只需加入一次即可色瘩。

4. 實(shí)現(xiàn)動(dòng)畫

前面多次使用到了CharPanel對象,CharPanel對象就是用于實(shí)現(xiàn)動(dòng)畫效果逸寓。先源碼:

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();

                //修改各個(gè)Label的屬性居兆,使之能以動(dòng)畫形式出現(xiàn)和消失
                Iterator<CharObj> it = charSet.iterator();
                while (it.hasNext()) {
                    CharObj obj = it.next();
                    if (obj.isAdd()) {//如果是添加到文本框
                        if (obj.getSize() <= minFontSize) {//當(dāng)字體大小到達(dá)最小后,使之消失
                            mComponent.remove(obj.getLabel());
                            it.remove();
                        } else {//否則竹伸,繼續(xù)減小
                            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) {//如果到達(dá)最底下泥栖,則清理
                            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();
            }
        }
    }

    //繪制文本,本質(zhì)上只是修改各個(gè)文本的位置和字體大小
    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(); //字符串的高, 只和字體有關(guān)
            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());
            } 
        }
    }

    //單例模式勋篓,靜態(tài)內(nèi)部類
    private static class SingletonHolder {
        //靜態(tài)初始化器吧享,由JVM來保證線程安全
        private static CharPanel instance = new CharPanel();
    }

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

    //由光標(biāo)監(jiān)聽器回調(diào),由此可動(dòng)態(tài)獲取當(dāng)前光標(biāo)位置
    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);
        }
    }

    //以下用于產(chǎn)生隨機(jī)顏色
    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];
    }
}


解釋一下兩個(gè)關(guān)鍵函數(shù)run()draw()run()函數(shù)是開啟新線程開始執(zhí)行的函數(shù)拜银,它的實(shí)現(xiàn)是一個(gè)循環(huán)殊鞭,當(dāng)插件開啟時(shí)會(huì)一直循環(huán)運(yùn)行。CharPanel使用了2個(gè)集合來保持用戶刪除或者添加的字符串尼桶, charSet是會(huì)直接被顯示出來的操灿,bufferList保存的是DocumentListener監(jiān)聽器監(jiān)聽到的輸入或刪除的字符串。輸入或刪除的字符串都封裝到CharObj類中泵督。run函數(shù)中每一次循環(huán)之前牲尺,先將bufferList中數(shù)據(jù)全部轉(zhuǎn)移到charSet中。為什么要使用2個(gè)集合呢幌蚊?這主要是因?yàn)榘迹?dāng)循環(huán)遍歷charSet時(shí),如果DocumentListener監(jiān)聽到的變化數(shù)據(jù)直接加入到charSet中溢豆,會(huì)導(dǎo)致出錯(cuò)蜒简。因?yàn)镴ava的集合在遍歷時(shí),不允許添加或刪除里面的元素漩仙。

run函數(shù)每一次循環(huán)都會(huì)調(diào)用draw()函數(shù)搓茬,draw()函數(shù)根據(jù)CharObj封裝的數(shù)據(jù)犹赖,將JLabel的位置屬性和字體屬性重新設(shè)置一次,這樣就使得JLabel有動(dòng)畫效果卷仑,因?yàn)閞un函數(shù)的每次循環(huán)的最后會(huì)逐步修改字體大小和位置數(shù)據(jù)峻村。

5. 源碼

其他代碼比較簡單,對著代碼解釋也沒什么意思锡凝。直接獻(xiàn)上源碼粘昨,如有疑惑的地方請留言,我盡量找時(shí)間一一回復(fù)窜锯。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末张肾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锚扎,更是在濱河造成了極大的恐慌吞瞪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驾孔,死亡現(xiàn)場離奇詭異芍秆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)翠勉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門浪听,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人眉菱,你說我怎么就攤上這事迹栓。” “怎么了俭缓?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵克伊,是天一觀的道長。 經(jīng)常有香客問我华坦,道長愿吹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任惜姐,我火速辦了婚禮犁跪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歹袁。我一直安慰自己坷衍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布条舔。 她就那樣靜靜地躺著枫耳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪孟抗。 梳的紋絲不亂的頭發(fā)上迁杨,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天钻心,我揣著相機(jī)與錄音,去河邊找鬼铅协。 笑死捷沸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的狐史。 我是一名探鬼主播痒给,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼预皇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婉刀,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吟温,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后突颊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲁豪,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年律秃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爬橡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棒动,死狀恐怖糙申,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情船惨,我是刑警寧澤柜裸,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站粱锐,受9級特大地震影響疙挺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怜浅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一铐然、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恶座,春花似錦搀暑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至湾宙,卻和暖如春樟氢,著一層夾襖步出監(jiān)牢的瞬間冈绊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工埠啃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留死宣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓碴开,卻偏偏與公主長得像毅该,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子潦牛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫眶掌、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,105評論 4 62
  • Swift版本點(diǎn)擊這里歡迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh閱讀 25,392評論 7 249
  • 涂sir閱讀 177評論 2 2
  • 文/寐魚 回看逸爵,你的過去與現(xiàn)在具滴,駐足咂咂嘴回味,像是讀...
    寐魚閱讀 327評論 0 0
  • 完成本關(guān)的編程任務(wù)师倔,并保存作品构韵,就可以領(lǐng)取獎(jiǎng)勵(lì)啦镊逝! 你已經(jīng)領(lǐng)取過獎(jiǎng)勵(lì)了坑赡,別太貪心喲,下節(jié)課再來吧~ 獎(jiǎng)勵(lì)數(shù)量 常規(guī)...
    e5489ee10db9閱讀 667評論 0 1