我被老板炒魷魚了!因為我在IDE里看漂亮小姐姐跳舞!
本文作者
作者:陳小緣
鏈接:
https://blog.csdn.net/u011387817/article/details/119217486
本文由作者授權(quán)發(fā)布。
這篇文章被我耽擱了好久好久沒發(fā)出來,原因是因為太長了,不太好編輯,不過我還是決定先給大家演示下效果:

就這樣,Android Studio 蹦的一聲就成了播放器,關(guān)鍵還是沉浸在代碼之下的,你還可以繼續(xù)寫代碼。
當然看電影寫代碼比較費腦子,在我讓群友多聊技術(shù)之后,他們果斷往群里發(fā)了個視頻,我們看下效果:

好了,沒錯,這篇文章講的是一個視頻背景插件的開發(fā),從想法、到調(diào)研、到各種斷點猜測、到嘗試、到優(yōu)化,最終完成。
鑒于篇幅較長,先寫上安裝方式,感興趣的同學(xué)可以先體驗上。
源碼地址:
https://github.com/wuyr/intellij-media-player
本地安裝:
到 releases 里下載最新的 intellij-media-player.zip 后拖把它拖進IDE中并重啟。
https://github.com/wuyr/intellij-media-player/releases
在線安裝:
Settings -> Plugins -> Marketplace里搜索Media Player:

點擊安裝即可;
安裝后,頂部導(dǎo)航欄會多一個 Player,剩下你應(yīng)該明白了。

注:如果需要循環(huán)播放,在控制欄的左下角有個回收圖標點擊即可。
好了,下面開始一起見證這個插件的誕生吧!
去年在新電腦上看視頻的時候,在觸摸板上做了一個縮放的手勢把程序列表call出來了:

我那時候是純黑色的壁紙,視頻也剛好播放到白色衣服人物在黑夜中的畫面,加上若隱若現(xiàn)的應(yīng)用程序圖標,這虛實結(jié)合的效果使得畫面中的人物變得立體起來了!甚至有一種身臨其境的感覺!

我當時就覺得,哇這種效果好棒啊,就像在播放透明背景的視頻一樣。記得那時候還在鴻神的群里討論了一下關(guān)于播放透明視頻的話題,后面有群友提到Android Studio就有個自帶的設(shè)置透明背景圖的功能。
第二天,好奇的我想知道Android Studio它是怎么做到把窗口下所有組件都設(shè)置成半透明并且把圖片放進去的。。。
這一節(jié)需要用到IntelliJ IDEA來進行debug,以Android Studio作為這次debug的目標,沒有Android Studio的同學(xué),用JetBrains系列的其他IDE也可以,都有這個功能的。
先把IDE對應(yīng)版本的源碼下載下來,看下AS的Build Version:

可以看到是202.7660開頭的,然后在 JetBrains/intellij-community 這里找到對應(yīng)的idea版本:
https://github.com/JetBrains/intellij-community/releases

下載、解壓(我一般會把它重命名為source并放到IDEA的根目錄下)。
接著打開IDEA,創(chuàng)建一個Plugin項目(這一步網(wǎng)上教程多如牛毛,這里就不贅述了),在Project Structure -> SDKs里把剛剛解壓的源碼關(guān)聯(lián)上;
關(guān)聯(lián)源碼之后,到Run/Debug Configuration里新建一個Remote JVM Debug的configuration:

先不要關(guān)閉頁面,打開Android Studio程序目錄下的bin文件夾,找到studio.sh(Windows系統(tǒng)的是studio.bat),復(fù)制一份,可以命名為debug.sh(Windows系統(tǒng)保留bat后綴),然后編輯:
在文件的末尾,Run the IDE注釋的下面會有"$JAVA_BIN"(Windows系統(tǒng)是"%JAVA_EXE%")字眼的:

在它后面加上剛剛新建的configuration里面的一串命令行參數(shù),比如我的是:-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
像這樣:

編輯之后保存,順便保存剛剛新建的configuration。
好了,可以正式開始了,現(xiàn)在從終端里運行剛剛修改過的debug.sh,然后打開Settings -> Appearance & Behavior -> Appearance:

看到那個Background Image按鈕沒有?我們現(xiàn)在就要debug它的鼠標事件,以拿到這個按鈕的對象,然后把它的listener扒出來。
現(xiàn)在可以回到IDEA這邊,點一下那個綠色的小蟲子,attach到AS進程了,成功之后會彈出debug控制臺窗口并有以下字眼:

沒有就是沒成功,請重試上面的步驟。
成功attach之后,開始打斷點。
隨便新建一個類,在里面輸入java.awt.Component,然后點開它的源碼并找到processMouseEvent(MouseEvent e)這個方法:

在它調(diào)用listener.mousePressed方法那一行打個斷點(監(jiān)聽鼠標按下)。接著回到AS窗口,鼠標點一下那個Background Image按鈕:

會看到已經(jīng)斷點成功了,當前點擊的Component是個JButton,熟悉Java Swing的同學(xué)會知道,這個JButton繼承自JComponent,而JComponent里有個listenerList,里面的數(shù)組是專門存放EventListener的,看一下這個JButton設(shè)置了哪些listener:

看第4個listener,它里面持有一個AnAction的引用,這個AnAction的實例是SetBackgroundImageAction,還有toString的內(nèi)容也是"Set Background Image"。
基本可以斷定它就是這個按鈕所對應(yīng)的Action了,SHIFT + F4看看這個類的代碼:
public?class?SetBackgroundImageAction?extends?DumbAwareAction?{
??...??
??@Override
??public?void?actionPerformed(@NotNull?AnActionEvent?e)?{
????Project?project?=?e.getProject();
????if?(project?==?null)?return;
????VirtualFile?file?=?e.getData(CommonDataKeys.VIRTUAL_FILE);
????boolean?image?=?file?!=?null?&&?ImageFileTypeManager.getInstance().isImage(file);
????BackgroundImageDialog?dialog?=?new?BackgroundImageDialog(project,?image???file.getPath()?:?null);
????dialog.showAndGet();
??}
}
它在重寫的actionPerformed方法里創(chuàng)建了BackgroundImageDialog對象并調(diào)用了showAndGet方法。
好,按F9讓代碼繼續(xù)運行,果然彈出了一個這樣的dialog:

隨便設(shè)置一張圖片,然后點OK:

emmmm,看來就是最后點擊的OK按鈕生效的,來看看這個BackgroundImageDialog的代碼:
public?class?BackgroundImageDialog?extends?DialogWrapper?{
????@Override
????protected?void?doOKAction()?{
????????super.doOKAction();
????????storeRecentImages();
????????String?value?=?calcNewValue();
????????String?prop?=?getSystemProp();
????????myResults.put(prop,?value);
????????if?(value.startsWith(","))?value?=?null;
????????boolean?perProject?=?myThisProjectOnlyCb.isSelected();
????????PropertiesComponent.getInstance(myProject).setValue(prop,?perProject???value?:?null);
????????if?(!perProject)?{
????????????PropertiesComponent.getInstance().setValue(prop,?value);
????????}
????????repaintAllWindows();
????}
????public?static?void?repaintAllWindows()?{
????????UISettings.getInstance().fireUISettingsChanged();
????????for?(Window?window?:?Window.getWindows())?{
????????????window.repaint();
????????}
????}
}????
doOKAction方法,邏輯比較清晰,無非做了4件事:
保存最近使用過的圖片路徑;
獲取當前圖片路徑和一些設(shè)置相關(guān)的信息;
應(yīng)用當前設(shè)置的圖片;
重繪所有窗口;
我們重點看第3,也就是PropertiesComponent那幾句,稍微把它改造一下讓它看起來更直觀些:
boolean?currentProjectOnly?=?myThisProjectOnlyCb.isSelected();
if?(currentProjectOnly)?{
????//?只對當前項目生效
????PropertiesComponent.getInstance(myProject).setValue(prop,?value);
}?else?{
????//?清除當前項目設(shè)置的背景
????PropertiesComponent.getInstance(myProject).setValue(prop,?null);
????//?換成全局的
????PropertiesComponent.getInstance().setValue(prop,?value);
}
熟悉IDEA插件開發(fā)的同學(xué)會知道,這個PropertiesComponent其實只是持久化鍵值對的工具類,并不會直接給窗口設(shè)置背景圖。也就是說,上面PropertiesComponent.getInstance().setValue(prop, value)這句代碼,只是把prop=value這個鍵值對保存到本地而已。
先來弄清楚它這個鍵值對是怎么樣的吧,在doOKAction方法中打個斷點:

注意到這個value的格式?jīng)]有?
它完整的格式其實是這樣的:
圖片絕對路徑,透明度,繪制方式,基準點,翻轉(zhuǎn)(可選項)
透明度的取值范圍是0~100;
圖片的繪制方式有:plain(原始尺寸)、scale(縮放到合適的大小)、tile(平鋪);
基準點:top-left、top-center、top-right、middle-left、center、middle-right、bottom-left、bottom-center、bottom-right;
翻轉(zhuǎn):flipH(水平翻轉(zhuǎn))、flipV(垂直翻轉(zhuǎn))、flipHV(水平垂直翻轉(zhuǎn));
prop(key)是固定的:"idea.background.editor"。
好了,現(xiàn)在我們已經(jīng)知道了背景圖的設(shè)置方式,接下來就試著簡單實現(xiàn)一下這個功能。
像其他插件一樣,先創(chuàng)建一個Action:
class?SetBackgroundAction?:?AnAction()?{
????override?fun?actionPerformed(event:?AnActionEvent)?{
????????//?固定的key
????????val?key?=?"idea.background.editor"
????????//?圖片路徑
????????val?path?=?"/home/wuyr/Downloads/Images/DSC00892.JPG"
????????//?15%透明度
????????val?transparency?=?15
????????//?自動縮放圖片以適應(yīng)屏幕
????????val?type?=?"scale"
????????//?以圖片中心區(qū)域為基點進行縮放變換
????????val?pivot?=?"center"
????????//?拼接格式
????????val?value?=?"$path,$transparency,$type,$pivot"
????????//?更新到本地
????????PropertiesComponent.getInstance().setValue(key,?value)
????????//?重繪所有窗口
????????IdeBackgroundUtil.repaintAllWindows()
????}
}
這個value的格式是完全按照上面的BackgroundImageDialog來定義的。
接著在plugin.xml里聲明這個Action:
????"SetBackgroundAction"?class="SetBackgroundAction"?text="Set?Background">
????????<add-to-group?group-id="ViewMenu"?anchor="last"/>
????
id可以隨便設(shè)置,保證跟其他Action不沖突就行;
class就是Action的完整類名(這個Action我沒有放在任何一個package下所以這里看上去只有一個類名);
text:顯示的文本;
add-to-group這一行表示我們把這個按鈕放在菜單欄View的底部。
好,編譯運行看一下效果:

點擊這個選項,會發(fā)現(xiàn)背景圖已經(jīng)成功替換成Action里面指向的圖片了。
現(xiàn)在,想讓背景動起來非常簡單,只需要周期性地更換圖片的路徑就行。不過當你真的這樣做的時候,你會發(fā)現(xiàn)調(diào)用repaintAllWindows重繪界面會非常非常的慢,就跟播放PPT一樣!
也許我們不應(yīng)該直接使用這種方法來做動態(tài)背景效果,還是再研究一下它具體是怎么實現(xiàn)繪制背景圖的吧,看看能不能找到粒度更小的實現(xiàn)方式。
到這簡單收個尾,大概的探索方式就是這樣的,希望大家能夠有所收獲。
完整的插件完成過程,后面大概還有 2/3的內(nèi)容,大家可以移步:
https://blog.csdn.net/u011387817/article/details/119217486
源碼地址:
https://github.com/wuyr/intellij-media-player
祝摸魚快樂?。。。ǖ驼{(diào)點,不要真的被炒魷魚了噢?。?/span>
-- END --
推薦:
全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了
