第一個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.知悉開發流程

這裏是IntelliJ IDEA插件開發的指南,我就是看着這個來開發的。

我在我的項目裏引入了兩個官方的例子:在這裏插入圖片描述

就是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步:

  1. 生成layout文件
  2. 生成Java文件
  3. 將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啦:

在這裏插入圖片描述

項目地址:FirstIntellijPlugin

可以對比我寫的Gradle插件:CreateActivityPlugin,關於Gradle插件請看發佈一個Gradle插件–實現一鍵創建Activity

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章