第一個AndroidStudio外掛,一鍵建立Activity
前言
之前寫過一個建立Activity的Gradle外掛CreateActivityPlugin,但是使用起來並非像使用AndroidStudio自帶的功能new Activity一樣方便。
而且我也做了一些思考,覺得建立Activity這個過程,其實和Gradle沒什麼關係。Gradle主要做的應該是幫助我們構建編譯專案,而我們建立Activity僅僅是建立修改檔案罷了。
正好最近公司Android組內想利用AS外掛做一些便於開發的基礎建設,我這裡就寫了一個demo來做個嘗試。
所謂的外掛,在我看來其實就是對於主程式的一個功能的擴充套件。不同的人使用AS肯定有一些特殊的需求,但是AndroidStudio開發者不可能預見所有的需要,統統加入到主程式中來。這時候就需要我們自己編寫外掛,來擴充套件主程式,滿足我們的需求。
準備工作
1.下載開發工具IntelliJ IDEA
由於AndroidStudio是基於IntelliJ IDEA的開源版本做的,所以開發AndroidStudio的外掛 ,其實就是開發IntelliJ IDEA的外掛。而開發IntelliJ IDEA外掛,要用到的就是IntelliJ IDEA。所以我們要去官網,下載免費的社群(Community)版本。
2.知悉開發流程
我在我的專案裡引入了兩個官方的例子:
就是sample包下的HelloAction和TextBoxes。
HelloAction和TextBoxes都繼承自AnAction,AnAction是什麼呢,用通俗的話來說就是一個動作
我們點選AS中某個按鈕的時候,觸發的就是一個AnAction,可以類比Android中的OnClickListener。然後我們繼承AnAction,要實現它的一個方法public abstract void actionPerformed(AnActionEvent e);
,可以類比OnClickListener的void onClick(View v);
。
在actionPerformed方法中我們寫的是這個動作觸發時我們應該去做的事情,然後該方法有一個入參AnActionEvent,它包含了你可能需要的狀態,資訊等,比方說我在哪裡觸發了這個點選事件(哪個檔案下)。
AnAction寫好了以後,我們還要像註冊Activity一樣在一個檔案中將它註冊(也可以用Java程式碼註冊):
注意看一下我在上圖中的註釋,是不是很好理解呢,當你引入(不會引入外掛的自行百度)了我的這個外掛,你就可以看到:
比如點選HelloAction,會出現如下彈窗:
一鍵建立Activity
1.展示建立Activity的彈窗
我們回想下,我們如果利用AndroidStudio的new Activity功能是如何建立Activity的?
是先右鍵點選一個資料夾,然後在一個彈窗中輸入資訊,然後確認,建立Activity的吧:
我這裡建立了一個叫CreateActivityAction的AnAction,我在actionPerformed方法中去展示了一個彈窗CreateActivityDialog。
彈窗的程式碼如下,用的是Java GUI的那一套,因為這個是執行在AndroidStudio上的嘛,我儘可能用Android的方式去寫了程式碼:
public class CreateActivityDialog extends JFrame {
private OnConfirmClickListener onConfirmClickListener;
private CreateActivityDialog() {
}
/**
* 展示彈窗
*/
public void showDialog() {
setSize(320, 120);
// 設定點選左上角x按鈕, 僅退出JFrame介面
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// 建立面板, 類似Android的RelativeLayout
JPanel panel = new JPanel();
// 新增面板
add(panel);
// 呼叫使用者定義的方法並新增元件到面板
placeComponents(panel);
// 讓JFrame位於螢幕中央
setLocationRelativeTo(null);
// 設定介面可見
setVisible(true);
}
/**
* 呼叫使用者定義的方法並新增元件到面板
*
* @param panel
*/
private void placeComponents(JPanel panel) {
// 這邊設定佈局為null
panel.setLayout(null);
// JLabel類似Android的TextView
JLabel activityLabel = new JLabel("ActivityName:");
// 這個方法定義了元件的位置
activityLabel.setBounds(10, 20, 110, 25);
panel.add(activityLabel);
// JTextField類似Android的EditText
JTextField activityText = new JTextField(20);
activityText.setBounds(110, 20, 165, 25);
panel.add(activityText);
// 建立按鈕, 類似Android的Button
JButton createButton = new JButton("create Activity");
createButton.setBounds(90, 60, 140, 25);
createButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 介面消失, 釋放JFrame資源
dispose();
if (onConfirmClickListener != null) {
onConfirmClickListener.onConfirm(activityText.getText());
}
}
});
panel.add(createButton);
}
public static class Builder {
private CreateActivityDialog dialog;
public Builder() {
dialog = new CreateActivityDialog();
}
public Builder setOnConfirmClickListener(OnConfirmClickListener onConfirmClickListener) {
dialog.setOnConfirmClickListener(onConfirmClickListener);
return this;
}
public CreateActivityDialog build() {
return dialog;
}
}
public CreateActivityDialog setOnConfirmClickListener(OnConfirmClickListener onConfirmClickListener) {
this.onConfirmClickListener = onConfirmClickListener;
return this;
}
public interface OnConfirmClickListener {
void onConfirm(String activityName);
}
}
實際效果如下:
哈哈,介面有點low。
然後你輸入ActivityName,點選create Activity按鈕,我就根據你輸入的ActivityName去建立檔案了。
2.建立Activity的檔案,並註冊。
分為以下3步:
- 生成layout檔案
- 生成Java檔案
- 將Activity註冊到AndroidManifest
具體程式碼如下:
/**
* 建立Activity
*
* @param event
* @param activityName
*/
private void createActivity(AnActionEvent event, String activityName) {
if (TextUtils.isEmpty(activityName)) {
return;
}
VirtualFile file = event.getData(PlatformDataKeys.VIRTUAL_FILE);
// 生成layout檔案
String layoutName = generateLayout(file, activityName);
// 生成Java檔案
String fullClassName = generateJavaFile(file, activityName, layoutName);
// 將Activity註冊到AndroidManifest
registerToManifest(file, fullClassName);
}
/**
* 生成layout檔案
*
* @param file
* @param activityName
* @return layout的名字
*/
private String generateLayout(VirtualFile file, String activityName) {
String filePath = file.getPath();
StringBuilder sb = new StringBuilder("activity");
int rightBorder = activityName.length() - "activity".length();
for (int i = 0; i < rightBorder; i++) {
if (Character.isUpperCase(activityName.charAt(i))) {
// 如果是大寫
sb.append("_");
}
sb.append(Character.toLowerCase(activityName.charAt(i)));
}
String layoutName = sb.toString();
String src_main = "src/main/";
int index = filePath.indexOf(src_main) + src_main.length();
while (true) {
String layoutPath = filePath.substring(0, index) +
"res/layout/" +
layoutName +
".xml";
File layoutFile = new File(layoutPath);
if (!layoutFile.exists()) {
// 如果layout檔案不存在, 去建立
try {
layoutFile.createNewFile();
writeFile(layoutPath, generateXmlContent());
} catch (IOException e) {
layoutName = null;
}
break;
} else {
// 如果layout檔案存在, 修改檔名
layoutName += "_1";
}
}
return layoutName;
}
/**
* 生成Java檔案
*
* @param file
* @param activityName
* @param layoutName
* @return Activity的全類名
*/
private String generateJavaFile(VirtualFile file, String activityName, String layoutName) {
// 生成Java檔案內容
String importPath = getImportPath(file);
String rPath = getRPath(file);
String content = generateJavaContent(importPath, rPath, activityName, layoutName);
// 生成Java檔案
String javaPath = file.getPath()
+ "/"
+ activityName
+ ".java";
File newFile = new File(javaPath);
try {
newFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// 寫入Java檔案內容
writeFile(javaPath, content);
String fullClassName = importPath + "." + activityName;
return fullClassName;
}
/**
* 將Activity註冊到AndroidManifest
*
* @param file
* @param fullClassName
*/
private void registerToManifest(VirtualFile file, String fullClassName) {
FileReader reader = null;
FileWriter writer = null;
BufferedReader bufferedReader = null;
try {
String manifestPath = getManifestPath(file.getPath());
reader = new FileReader(manifestPath);
bufferedReader = new BufferedReader(reader);
StringBuilder sb = new StringBuilder();
// 每一行的內容
String line = "";
while ((line = bufferedReader.readLine()) != null) {
// 找到application節點的末尾
if (line.contains("</application>")) {
// 在application節點最後插入新建立的activity節點
String activityNode = generateActivityNodeContent(fullClassName);
sb.append(activityNode + "\n");
}
sb.append(line + "\n");
}
String content = sb.toString();
// 刪除最後多出的一行
content = content.substring(0, content.length() - 1);
writer = new FileWriter(manifestPath);
writer.write(content);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 獲取當前的導包路徑
*
* @param file
* @return
*/
private String getImportPath(VirtualFile file) {
String path = file.getPath();
String str = "src/main/java/";
int index = path.indexOf(str) + str.length();
String importPath = path.substring(index, path.length());
importPath = importPath.replace("/", ".");
return importPath;
}
/**
* 獲取AndroidManifest檔案的路徑
*
* @param filePath
* @return
*/
private String getManifestPath(String filePath) {
String str = "/src/main";
int index = filePath.indexOf(str) + str.length();
return filePath.substring(0, index) + "/AndroidManifest.xml";
}
/**
* 通過解析AndroidManifest檔案
* 獲取R檔案的路徑
*
* @param file
* @return
*/
private String getRPath(VirtualFile file) {
String manifestPath = getManifestPath(file.getPath());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
File manifestFile = new File(manifestPath);
Document doc = documentBuilder.parse(manifestFile);
Element root = doc.getDocumentElement();
// 獲取manifest節點下的package屬性
String packageName = root.getAttribute("package");
return packageName;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 生成xml內容
*
* @return
*/
private String generateXmlContent() {
return XmlTemplate.template;
}
/**
* 生成Java檔案內容
*
* @param importPath
* @param rPath
* @param activityName
* @param layoutName
* @return
*/
private String generateJavaContent(String importPath, String rPath, String activityName, String layoutName) {
String content = String.format(ActivityTemplate.template, importPath, rPath, activityName, layoutName);
return content;
}
/**
* 生成Activity節點內容
*
* @param fullClassName
* @return
*/
private String generateActivityNodeContent(String fullClassName) {
String content = String.format(ActivityNodeTemplate.template, fullClassName);
return content;
}
/**
* 向檔案寫入內容
*
* @param file
* @param content
* @throws IOException
*/
private void writeFile(String file, String content) {
RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile(file, "rw");
if (randomAccessFile.length() > 2) {
randomAccessFile.seek(randomAccessFile.length() - 2);
}
randomAccessFile.write(content.getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
如果你引入了我的外掛,這時候就可以右一鍵生成Activity啦: