前言
关于MVP模式,通常都会利用泛型封装一下MVP的base类,通过泛型所提供的类型去实例化View层和Presenter层,在继承封装好的基类中可以快速的使用MVP模式。无法避免的是我们在新建任意一个activity的时候都要去新建P层和V层。在继承的时候需要填写多个泛型值。这样的操作无疑相当繁琐,而且无趣。几经折腾,就想将MVP的模板代码提取出来,通过插件自动生成的方式来完成那些简单而又不得不做的操作。
插件编写
创建一个plugin项目,开发工具需要IntelliJ IDEA,android studio是在IntelliJ IDEA基础上开发的,所以IntelliJ IDEA上手很简单。
1.File->New->Project 然后选择一个plugin项目,如下图:
![](https://img.haomeiwen.com/i5459328/efbf78f92d1310ce.png)
2.点击Next,如下图所示,填写我们的项目名称和选择我们项目目录。然后点finish即可。
![](https://img.haomeiwen.com/i5459328/85dc7be4ebcb432f.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,如下
![](https://img.haomeiwen.com/i5459328/b19a4e8ba03db09e.png)
2.填写Action的信息
![](https://img.haomeiwen.com/i5459328/c2210fe01cc517e4.png)
- 说明一下需要填写的属性:
- Action ID:代表这个Action的唯一标示。
- Class Name:类名
- Name:这个插件在菜单上的名称
- Description:关于这个插件的描述信息
- Groups:代表这个插件会出现的位置。比如想让这个插件出现在Code菜单下的第一次选项,我在图中选择CodeMenu(Code),右边Anchor选择First
- Keyboard Shortcuts:快捷键设置。图中设置Alt+T。
点击OK后会生成一个MvpCreatePlugin类
编写插件代码
1.创建mvp代码模板
生成代码可以使用一些模板,这个模板可以是一个txt文件,在模板里面添加代码,提取出需要替换的代码,然后通过流读取模板文件,最后生成类文件。如下图:
![](https://img.haomeiwen.com/i5459328/3f9c29afb58209ad.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
![](https://img.haomeiwen.com/i5459328/c373d4bee9dca3bf.png)
点击这个MyDialog.form文件会进入一个可视化界面,通过拖拽就可以实现一个用户界面,效果图如下:
![](https://img.haomeiwen.com/i5459328/d14513d83c5c17f8.png)
在androidStudio上显示的效果图如下:
![](https://img.haomeiwen.com/i5459328/7279106240a22c2e.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 ...... 如图:
![](https://img.haomeiwen.com/i5459328/c88e0326e8784720.png)
会在项目的根目录生成一个jar包:MvpCreate.jar
插件使用
1.打开Android Studio,选择File->Setting->Plugins->Install plugin from disk,然后选择刚刚生成的jar包,重启Android Studio。
2.重启后可以看到在Code菜单下多了MvpCreate这个选项,在对应的包名下点击或者快捷键Alt+T调用
![](https://img.haomeiwen.com/i5459328/04cb9ec58bd5de2e.png)
总结
安装完插件,写一个新的mvp页面,只要输入module name、author、module describe、title就能创建好,避免枯燥的复制黏贴,省时省力。当然,各位可以根据自己项目封装的mvp基类,自定义插件来减少不必要的操作。
源码地址:https://gitee.com/wujf564335/MvpHelper
网友评论