美文网首页
Android Studio开发快速创建MVP框架插件

Android Studio开发快速创建MVP框架插件

作者: 番茄焖西红柿 | 来源:发表于2020-06-06 17:20 被阅读0次

前言

关于MVP模式,通常都会利用泛型封装一下MVP的base类,通过泛型所提供的类型去实例化View层和Presenter层,在继承封装好的基类中可以快速的使用MVP模式。无法避免的是我们在新建任意一个activity的时候都要去新建P层和V层。在继承的时候需要填写多个泛型值。这样的操作无疑相当繁琐,而且无趣。几经折腾,就想将MVP的模板代码提取出来,通过插件自动生成的方式来完成那些简单而又不得不做的操作。

插件编写

创建一个plugin项目,开发工具需要IntelliJ IDEA,android studio是在IntelliJ IDEA基础上开发的,所以IntelliJ IDEA上手很简单。

1.File->New->Project 然后选择一个plugin项目,如下图:


create_plugin.png

2.点击Next,如下图所示,填写我们的项目名称和选择我们项目目录。然后点finish即可。


plugin_name.png
  • 项目结构


    project.png
    • .idea : idea的一些配置信息。
    • resources/META-INF/plugin.xml: 插件的一些描述信息。类似android中的
      Manifest文件。
    • src: 这里就是写代码的地方
  • 项目整体配置


    project_setting.png
    • id: 这个就是该插件的唯一标识符了。类似于android的包名。在android 中如果包名重复的话,就会出现后面的软件无法安装的问题。而在插件中表现出来的就是后面安装的插件会覆盖前面的。所以一定要记得改成自己的id。
    • name: 这个就类似于android 中的应用名称
    • version: 插件的版本
    • vendor: 这个里面就是你的个人信息了。如果你想给自己写的插件打上自己的烙印,就改写这里。
    • description: 这个里面就是描述一下,这个插件是干啥的。可以使用html标签来编写
    • change-notes 更新信息
    • extensions defaultExtensionNs 默认依赖的库
    • actions 类似于android 中的activity。所有使用到的action,都要在这里进行注册。
创建一个菜单

1.开始建一个自己的菜单,选择src->New->Action,如下


create_action.png

2.填写Action的信息


action_menu.png
  • 说明一下需要填写的属性:
    • Action ID:代表这个Action的唯一标示。
    • Class Name:类名
    • Name:这个插件在菜单上的名称
    • Description:关于这个插件的描述信息
    • Groups:代表这个插件会出现的位置。比如想让这个插件出现在Code菜单下的第一次选项,我在图中选择CodeMenu(Code),右边Anchor选择First
    • Keyboard Shortcuts:快捷键设置。图中设置Alt+T。
      点击OK后会生成一个MvpCreatePlugin类
编写插件代码

1.创建mvp代码模板
生成代码可以使用一些模板,这个模板可以是一个txt文件,在模板里面添加代码,提取出需要替换的代码,然后通过流读取模板文件,最后生成类文件。如下图:


mvp_template.png

我这边没有把封装的基类放上去。每个人的项目封装或是依赖的库不一样,可以参考去定义适合自己项目的插件。
这边以TemplateListFragment.txt说明

package $packagename;

import android.os.Bundle;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProviders;

import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import $applicationId.adapter.$nameAdapter;
import $applicationId.base.RecyclerBaseFragment;

import java.util.ArrayList;

/**
 * Author: $author
 * Time: $data
 * Description:$description
 */
public class $nameFragment extends RecyclerBaseFragment<$nameContract.Presenter, String> implements $nameContract.View {
    public static $nameFragment newInstance() {

        Bundle args = new Bundle();

        $nameFragment fragment = new $nameFragment();
        fragment.setArguments(args);
        return fragment;
    }


    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (mPresenter != null) {
            mPresenter.onStart();
        }
    }

    @Override
    protected BaseQuickAdapter<String, BaseViewHolder> getAdapter() {
        return new $nameAdapter(0, new ArrayList<>());
    }

    @Override
    protected void onItemClick(String item, int position) {

    }

    @Override
    protected $nameContract.Presenter getViewModel() {
        return ViewModelProviders.of(getActivity()).get($namePresenter.class);
    }
}

  • 提取字符说明
    • $packagename:该类所在的包名
    • $applicationId:项目applicationId
    • $nameAdapter;recyclerview的适配器类
    • $nameFragment :Fragment类名
    • $namePresenter:Presenter类名
    • $author:类注释,作者
    • $data:类注释,时间
    • $description:类注释,类作用说明
      这些字符都是可以动态替换的,当然也可以根据自己的需要写入自己的代码。

2.创建dialog


create_dialog.png

点击这个MyDialog.form文件会进入一个可视化界面,通过拖拽就可以实现一个用户界面,效果图如下:


dialog_swing.png
在androidStudio上显示的效果图如下:
dialog_androidstudio.png
package template.code;

import template.TextFieldInputListener;

import javax.swing.*;
import java.awt.event.*;

public class MyDialog extends JDialog {
    private JPanel contentPane;
    private JButton buttonOK;
    private JButton buttonCancel;
    private JTextField textField1;
    private JTextField textField2;
    private JTextField textField3;
    private JTextField textField4;
    private JCheckBox isRecyclerFragmentCheckBox;
    private DialogCallBack mCallBack;

    public MyDialog(DialogCallBack dialogCallBack) {
        this.mCallBack = dialogCallBack;
        setTitle("Mvp Create Helper");
        setContentPane(contentPane);
        setSize(600, 300);
        setLocationRelativeTo(null);
        setModal(false);

        //设置
        textField1.addCaretListener(new TextFieldInputListener());

        getRootPane().setDefaultButton(buttonOK);

        buttonOK.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onOK();
            }
        });

        buttonCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        });

        // call onCancel() when cross is clicked
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                onCancel();
            }
        });

        // call onCancel() on ESCAPE
        contentPane.registerKeyboardAction(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    }

    private void onOK() {
        // add your code here
        if (null != mCallBack) {
            mCallBack.ok(textField1.getText().trim(), textField2.getText().trim(), textField3.getText().trim(), textField4.getText().trim(), isRecyclerFragmentCheckBox.isSelected());
        }
    }

    private void onCancel() {
        // add your code here if necessary
        dispose();
    }

    public interface DialogCallBack {
        /**
         * @param moduleName         模块名称
         * @param author             作者
         * @param describe           模块说明
         * @param title              标题
         * @param isRecyclerFragment 是列表样式
         */
        void ok(String moduleName, String author, String describe, String title, boolean isRecyclerFragment);
    }

}

DialogCallBack是这个dialog的“ok”按钮的回调,在MvpCreatePlugin.java中获取处理dialog中输入的信息。

MvpCreatePlugin.java实现生成mvp各个类

package template;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import template.code.MyDialog;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by wujf on 2020/6/1.
 */
public class MvpCreatePluginAction extends AnAction {
    Project project;
    VirtualFile selectGroup;
    private String mPackageName;//包名
    private String mClassName;//类名称(处理输入的module name)
    private String mAuthor;//作者
    private String mDescriber;//mocule说明
    private String mLayoutName;//布局名称
    private String mTitle;//页面标题
    private MyDialog myDialog;

    private enum CodeType {
        Activity, Fragment, Contract, Presenter, ListActivity, ListFragment, ListContract, ListPresenter, ListAdapter
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
        project = e.getProject();
        selectGroup = DataKeys.VIRTUAL_FILE.getData(e.getDataContext());
        init();
        project.getBaseDir().refresh(false, true);
    }

    private void init() {
        myDialog = new MyDialog(new MyDialog.DialogCallBack() {
            @Override
            public void ok(String moduleName, String author, String describe, String title, boolean isRecyclerFragment) {
                if (moduleName == null || moduleName.equals("")) {
                    System.out.print("没有输入类名");
                    return;
                }
                //若是module name已经存在,则返回
                String path = selectGroup.getPath() + "/" + moduleName.toLowerCase();
                File file = new File(path);
                if (file.exists()) {
                    Messages.showWarningDialog("module name is exists", "提示 ");
                    return;
                }

                //作者
                if (author == null || author.equals("")) {
                    mAuthor = "wujf";
                } else {
                    mAuthor = author;
                }

                //项目说明
                if (describe == null || describe.equals("")) {
                    mDescriber = "";
                } else {
                    mDescriber = describe;
                }

                //标题
                if (describe == null || title.equals("")) {
                    mTitle = "";
                } else {
                    mTitle = title;
                }
                mLayoutName = "fragment_quick_create_" + humpToUnderline(moduleName);//布局名称quick_create防止重名
                createClassFile(moduleName, isRecyclerFragment);//创建mvp各个类
                project.getBaseDir().refresh(false, true);
                myDialog.dispose();
            }
        });
        myDialog.setVisible(true);
    }


    /**
     * 获取applicationid
     *
     * @return
     */
    private String getApplicationId() {
        String package_name = "";
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(project.getBasePath() + "/App/src/main/AndroidManifest.xml");

            NodeList nodeList = doc.getElementsByTagName("manifest");
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                Element element = (Element) node;
                package_name = element.getAttribute("package");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return package_name;
    }

    /**
     * 创建MVP架构
     *
     * @param moduleName         输入的名称
     * @param isRecyclerFragment 是否是列表类型页面,默认不是
     */
    private void createClassFile(String moduleName, boolean isRecyclerFragment) {
        if (moduleName.endsWith("Fragment") || moduleName.endsWith("fragment") || moduleName.endsWith("Activity") || moduleName.endsWith("activity")) {
            moduleName = moduleName.substring(0, moduleName.length() - 8);
        }
        String path = selectGroup.getPath() + "/" + moduleName.toLowerCase();
        mPackageName = path.substring(path.indexOf("java") + 5, path.length()).replace("/", ".");
        mClassName = moduleName.substring(0, 1).toUpperCase() + moduleName.substring(1);

        if (!isRecyclerFragment) {
            createClassFile(CodeType.Activity, path);
            createClassFile(CodeType.Contract, path);
            createClassFile(CodeType.Fragment, path);
            createClassFile(CodeType.Presenter, path);
            createLayout();//创建布局文件
        } else {
            createClassFile(CodeType.ListActivity, path);
            createClassFile(CodeType.ListContract, path);
            createClassFile(CodeType.ListFragment, path);
            createClassFile(CodeType.ListPresenter, path);
            createClassFile(CodeType.ListAdapter, path);

        }

    }

    /**
     * 生成mvp框架代码
     *
     * @param codeType
     */
    private void createClassFile(CodeType codeType, String path) {
        String fileName = "";
        String content = "";
        switch (codeType) {
            case Activity:
                fileName = "TemplateActivity.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, path, mClassName + "Activity.java");
                break;

            case Fragment:
                fileName = "TemplateFragment.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, path, mClassName + "Fragment.java");
                break;

            case Contract:
                fileName = "TemplateContract.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, path, mClassName + "Contract.java");
                break;

            case Presenter:
                fileName = "TemplatePresenter.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, path, mClassName + "Presenter.java");
                break;

            case ListActivity:
                fileName = "TemplateListActivity.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, path, mClassName + "Activity.java");
                break;

            case ListFragment:
                fileName = "TemplateListFragment.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, path, mClassName + "Fragment.java");
                break;

            case ListContract:
                fileName = "TemplateListContract.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, path, mClassName + "Contract.java");
                break;

            case ListPresenter:
                fileName = "TemplateListPresenter.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, path, mClassName + "Presenter.java");
                break;

            case ListAdapter://适配器路劲
                String adapterPath = project.getBasePath() + "/app/src/main/java/" + getApplicationId().replace(".", "/") + "/adapter";//自己项目下的adapter,没有会新建
                fileName = "TemplateAdapter.txt";
                content = dealTemplateContent(readFile(fileName));
                writetoFile(content, adapterPath, mClassName + "Adapter.java");
                break;

        }
    }


    /**
     * 替换模板中字符
     *
     * @param dealContent
     * @return
     */
    private String dealTemplateContent(String dealContent) {
        String content = dealContent.replace("$packagename", mPackageName)
                .replace("$applicationId", getApplicationId())
                .replace("$nameActivity", mClassName + "Activity")
                .replace("$nameFragment", mClassName + "Fragment")
                .replace("$nameContract", mClassName + "Contract")
                .replace("$namePresenter", mClassName + "Presenter")
                .replace("$nameAdapter", mClassName + "Adapter")
                .replace("$layout", mLayoutName)
                .replace("$author", mAuthor)
                .replace("$data", getDate())
                .replace("$description", mDescriber)
                .replace("$title", mTitle);
        return content;
    }

    /**
     * 获取当前时间
     *
     * @return
     */
    public String getDate() {
        Date currentTime = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
        String dateString = formatter.format(currentTime);
        return dateString;
    }

    /**
     * 创建layout布局文件
     */
    private void createLayout() {
        String layoutPath = project.getBasePath() + "/app/src/main/res/layout/";
        String layout = readFile("layout.txt");
        writetoFile(layout, layoutPath, mLayoutName + ".xml");
    }


    /**
     * 驼峰转下划线
     *
     * @param humpString created by hbd 20160722
     * @return
     */
    public String humpToUnderline(String humpString) {
        if (humpString == null || humpString.equals("")) return "";
        String regexStr = "[A-Z]";
        Matcher matcher = Pattern.compile(regexStr).matcher(humpString);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String g = matcher.group();
            matcher.appendReplacement(sb, "_" + g.toLowerCase());
        }
        matcher.appendTail(sb);
        if (sb.charAt(0) == '_') {
            sb.delete(0, 1);
        }
        return sb.toString();
    }


    private String readFile(String filename) {
        InputStream in = null;
        in = this.getClass().getResourceAsStream("code/" + filename);
        String content = "";
        try {
            content = new String(readStream(in));
        } catch (Exception e) {
        }
        return content;
    }

    private void writetoFile(String content, String filepath, String filename) {
        try {
            File floder = new File(filepath);
            // if file doesnt exists, then create it
            if (!floder.exists()) {
                floder.mkdirs();
            }
            File file = new File(filepath + "/" + filename);
            if (!file.exists()) {
                file.createNewFile();
            }

            // 获取该文件的缓冲输出流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file.getAbsoluteFile()), "UTF-8"));
            bw.write(content);
            bw.flush();// 清空缓冲区
            bw.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private byte[] readStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        try {
            byte[] buffer = new byte[1024];
            int len = -1;
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
                System.out.println(new String(buffer));
            }

        } catch (IOException e) {
        } finally {
            outSteam.close();
            inStream.close();
        }
        return outSteam.toByteArray();
    }

}

生成对应java、xml文件流程:
1、读取模板文件
2、替换对应占位符
3、写入对应文件路径
思路挺简单,需要细心替换要抽取的内容。我的做法
1、在app项目里新创建一个没有业务逻辑的mvp功能模块
2、用自定义的符号替换类中需要替换的字符
3、复制黏贴到新建的.txt文件

插件编译

点击Build->Prepare Plugin Module ...... 如图:


build_plugin.png

会在项目的根目录生成一个jar包:MvpCreate.jar

插件使用

1.打开Android Studio,选择File->Setting->Plugins->Install plugin from disk,然后选择刚刚生成的jar包,重启Android Studio。
2.重启后可以看到在Code菜单下多了MvpCreate这个选项,在对应的包名下点击或者快捷键Alt+T调用


code.png

总结

安装完插件,写一个新的mvp页面,只要输入module name、author、module describe、title就能创建好,避免枯燥的复制黏贴,省时省力。当然,各位可以根据自己项目封装的mvp基类,自定义插件来减少不必要的操作。
源码地址:https://gitee.com/wujf564335/MvpHelper

相关文章

网友评论

      本文标题:Android Studio开发快速创建MVP框架插件

      本文链接:https://www.haomeiwen.com/subject/qhnvzhtx.html