相信大家在开发过程中都会用到一些插件,如butterknife,插件的使用大大的节约了我们的开发时间,看了鸿洋的《学会编写Android Studio插件 别停留在用的程度了》
这篇博客后,我就想着自己去写一个插件了;在Android开发中,现在流行的mvp模式,每个界面都会创建对应的mvp框架,写的很浪费时间,虽然网上也有一些插件,但是每个人封装的base基类都有自己的风格,因此我就想写出一个自己的插件,除了鸿洋的这篇博客,我还参考了下 https://blog.csdn.net/fengdezhudi/article/details/53584497。
开发环境搭建
插件开发使用的是IntelliJ IDEA,附上下载地址https://www.jetbrains.com/idea/ ,推荐下载community版本安装,不需要注册
注意:安装时注意选上 groovy,我当时只选择了java,后来发现插件不能打包,重新下载的groovy,勿踩坑。
新建项目
[外链图片转存失败(img-RuvPWma7-1566354796743)(https://i.imgur.com/xOCsmTW.png)]
注意: sdk这里如果没有,就选择自己的sdk即可,不要勾选groovy,下面的libray是我自己下载的,因此自己去目录下选择的。
新建插件
[外链图片转存失败(img-AMkEidCi-1566354796744)(https://i.imgur.com/ACUyPQJ.png)]
注意:不要直接在src下新建action,否则打包的插件不能正常使用,一定要在src下新建一个包。
[外链图片转存失败(img-s1cPKn03-1566354796746)(https://i.imgur.com/3NpIFJs.png)]
Action ID:代表这个Action的唯一标示。
Class Name:类名
Name:这个插件在菜单上的名称
Description:关于这个插件的描述信息
Groups:代表这个插件会出现的位置。我的是在Code菜单下的第一次选项,左侧选择CodeMenu(Code),右边Anchor选择First
Keyboard Shortcuts:快捷键设置
就这样一个插件项目就新建完成。然后在src下新建我们的模板,
[外链图片转存失败(img-UDsP4xzo-1566354796749)(https://i.imgur.com/WJm8oYy.png)]
注意:这里 n a m e , name, name,packgename等这些字符都是站位用的,生成文件代码时替换。
然后新建一个Dialog,也就是插件使用时的界面,这个和Android就差不多了。
[外链图片转存失败(img-AcfJwoV6-1566354796750)(https://i.imgur.com/JPYrlpd.png)]
这里贴上我的dailog代码
package action;
import org.apache.http.util.TextUtils;
import javax.swing.*;
import java.awt.event.*;
public class CreateDialog extends JDialog {
private JPanel contentPane;
private JButton buttonOK;
private JButton buttonCancel;
private JTextField textField1;
private JTextField textField2;
private JLabel Txttip;
private DialogCallBack mCallBack;
public static final String FRAGMENT = "1";
public static final String ACTIVITY = "2";
public static final String ALL = "3";
private String type = FRAGMENT;
public CreateDialog(DialogCallBack callBack) {
this.mCallBack = callBack;
setContentPane(contentPane);
setModal(true);
setTitle("Mvp Create Helper");
setSize(300, 150);
setLocationRelativeTo(null);
getRootPane().setDefaultButton(buttonOK);
Txttip.setVisible(false);
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){
if (TextUtils.isEmpty(textField1.getText().trim())){
Txttip.setVisible(true);
Txttip.setText("No filename is created");
return;
}
if (TextUtils.isEmpty(textField2.getText().trim())) {
Txttip.setVisible(true);
Txttip.setText("No create type, fragment or activity or all");
return;
}else if ("fragment".equals(textField2.getText().trim().toLowerCase())){
type = FRAGMENT;
}else if ("activity".equals(textField2.getText().trim().toLowerCase())){
type = ACTIVITY;
}else if ("all".equals(textField2.getText().trim().toLowerCase())){
type = ALL;
}else{
Txttip.setVisible(true);
Txttip.setText("Type only fragment and activity and all");
return;
}
mCallBack.ok(type,textField1.getText().trim());
}
dispose();
}
private void onCancel() {
// add your code here if necessary
dispose();
}
// public static void main(String[] args) {
// CreateDialog dialog = new CreateDialog(new DialogCallBack() {
// @Override
// public void ok(String type, String moduleName) {
//
// }
// });
// dialog.pack();
// dialog.setVisible(true);
// System.exit(0);
// }
public interface DialogCallBack{
void ok(String type, String moduleName);
}
}
主要就是根据我们的布局在onOK()中写入我们需要的东西。然后在我们新建的action中写入逻辑代码,也就是自动生成mvp框架代码。
package action;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MvpCreateAction extends AnAction {
private Project project;
//包名
private String packageName = "";
//模块名称
private String mModuleName;
//创建的类型
private String mType = CreateDialog.FRAGMENT;
private String xmlName = "view";
private enum CodeType {
Activity, Fragment, Contract, Presenter, BaseView, BasePresenter, BaseActivity, BaseFragment
}
public MvpCreateAction(){
}
@Override
public void actionPerformed(AnActionEvent e) {
// TODO: insert action logic here
project = e.getData(PlatformDataKeys.PROJECT);
packageName = getPackageName();
init();
refreshProject(e);
}
/**
* 从AndroidManifest.xml文件中获取当前app的包名
*
* @return 包名
*/
private String getPackageName() {
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;
}
/**
* 初始化Dialog
*/
private void init() {
CreateDialog myDialog = new CreateDialog((type, moduleName) -> {
mModuleName = moduleName;
mType = type;
createClassFiles();
Messages.showInfoMessage(project, "create mvp code success", "title");
});
myDialog.setVisible(true);
}
/**
* 生成类文件
*/
private void createClassFiles() {
if (mType.equals(CreateDialog.ACTIVITY)) {
createClassFile(CodeType.Activity);
xmlName = "activity_" + mModuleName.toLowerCase() + ".xml";
createXmlFile(xmlName);
}
if (mType.equals(CreateDialog.FRAGMENT)) {
createClassFile(CodeType.Fragment);
xmlName = "fragment_" + mModuleName.toLowerCase() + ".xml";
createXmlFile(xmlName);
}
if (mType.equals(CreateDialog.ALL)) {
createClassFile(CodeType.Activity);
createClassFile(CodeType.Fragment);
createXmlFile("activity_" + mModuleName.toLowerCase() + ".xml");
createXmlFile("fragment_" + mModuleName.toLowerCase() + ".xml");
}
createBaseClassFile(CodeType.BaseView);
createBaseClassFile(CodeType.BasePresenter);
createBaseClassFile(CodeType.BaseActivity);
createBaseClassFile(CodeType.BaseFragment);
createClassFile(CodeType.Contract);
createClassFile(CodeType.Presenter);
}
/**
* 生成布局文件
*/
private void createXmlFile(String fileName) {
String content = dealTemplateContent(ReadTemplateFile("Templatexml.txt"));
String appPath = project.getBasePath() + "/App/src/main/res/layout/";
writeToFile(content, appPath, fileName);
}
/**
* 生成mvp框架代码
*
* @param codeType
*/
private void createClassFile(CodeType codeType) {
switch (codeType) {
case Activity:
createFile("TemplateActivity.txt", toUpperCaseFirstOne(mModuleName) + "Activity.java");
break;
case Fragment:
createFile("TemplateFragment.txt", toUpperCaseFirstOne(mModuleName) + "Fragment.java");
break;
case Contract:
createFile("TemplateContract.txt", toUpperCaseFirstOne(mModuleName) + "Contract.java");
break;
case Presenter:
createFile("TemplatePresenter.txt", toUpperCaseFirstOne(mModuleName) + "Presenter.java");
break;
}
}
private void createFile(String fileName, String CreateName) {
String appPath = getAppPath();
String content = dealTemplateContent(ReadTemplateFile(fileName));
writeToFile(content, appPath + mModuleName.toLowerCase(), CreateName);
}
/**
* 生成base类
*
* @param codeType
*/
private void createBaseClassFile(CodeType codeType) {
String fileName = "";
String content = "";
String basePath = getAppPath() + "base/";
switch (codeType) {
case BaseView:
if (!new File(basePath + "BaseView.java").exists()) {
fileName = "BaseView.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, basePath, "BaseView.java");
}
break;
case BasePresenter:
if (!new File(basePath + "BasePresenter.java").exists()) {
fileName = "BasePresenter.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, basePath, "BasePresenter.java");
}
break;
case BaseActivity:
if (!new File(basePath + "BaseActivity.java").exists()) {
fileName = "BaseActivity.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, basePath, "BaseActivity.java");
}
break;
case BaseFragment:
if (!new File(basePath + "BaseFragment.java").exists()) {
fileName = "BaseFragment.txt";
content = ReadTemplateFile(fileName);
content = dealTemplateContent(content);
writeToFile(content, basePath, "BaseFragment.java");
}
break;
}
}
/**
* 获取包名文件路径
*
* @return
*/
private String getAppPath() {
String packagePath = packageName.replace(".", "/");
String appPath = project.getBasePath() + "/App/src/main/java/" + packagePath + "/";
return appPath;
}
/**
* 读取模板文件中的字符内容
*
* @param fileName 模板文件名
* @return
*/
private String ReadTemplateFile(String fileName) {
InputStream in = null;
in = this.getClass().getResourceAsStream("/Template/" + fileName);
String content = "";
try {
content = new String(readStream(in));
} catch (IOException e) {
e.printStackTrace();
}
return content;
}
/**
* 替换模板中字符
*
* @param content
* @return
*/
private String dealTemplateContent(String content) {
content = content.replace("$name", toUpperCaseFirstOne(mModuleName));
if (content.contains("$rpackagename")){
content = content.replace("$packagename", packageName);
}
if (content.contains("$packagename")) {
content = content.replace("$packagename", packageName + "." + mModuleName.toLowerCase());
}
if (content.contains("$basepackagename")) {
content = content.replace("$basepackagename", packageName + ".base");
}
if (content.contains("$axmlname")){
content = content.replace("$axmlname","activity_" + mModuleName.toLowerCase());
}
if (content.contains("$fxmlname")){
content = content.replace("$fxmlname","fragment_" + mModuleName.toLowerCase());
}
// content = content.replace("$author", mAuthor);
if (content.contains("$date")) {
content = content.replace("$date", getDate());
}
return content;
}
/**
* 获取当前时间
*
* @return
*/
public String getDate() {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
String dateString = formatter.format(currentTime);
return dateString;
}
// public static void main(String[] args) {
//
// MvpCreateAction action = new MvpCreateAction();
// System.out.print(action.ReadTemplateFile("Templatexml.txt"));
//
//
String appPath = project.getBasePath() + "/App/src/main/res/layout/";
writeToFile(content, appPath, fileName);
// }
private byte[] readStream(InputStream inputStream) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
byte[] file;
int len = -1;
try {
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
file = outputStream.toByteArray(); // 取内存中保存的数据
outputStream.close();
if (inputStream != null)
inputStream.close();
}
return file;
}
/**
* 生成
*
* @param content 类中的内容
* @param classPath 类文件路径
* @param className 类文件名称
*/
private void writeToFile(String content, String classPath, String className) {
try {
File floder = new File(classPath);
if (!floder.exists()) {
floder.mkdirs();
}
File file = new File(classPath + "/" + className);
if (!file.exists()) {
file.createNewFile();
}else{
Messages.showInfoMessage(project, "The file already exists", "title");
return;
}
FileOutputStream fw = new FileOutputStream(file.getAbsoluteFile());
//BufferedWriter bw = new BufferedWriter(fw);
fw.write(content.getBytes());
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//首字母转大写
public static String toUpperCaseFirstOne(String s) {
if (Character.isUpperCase(s.charAt(0)))
return s;
else
return (new StringBuilder()).append(Character.toUpperCase(s.charAt(0))).append(s.substring(1)).toString();
}
/**
* 刷新项目
*
* @param e
*/
private void refreshProject(AnActionEvent e) {
e.getProject().getBaseDir().refresh(false, true);
}
}
打包插件
[外链图片转存失败(img-W2znCN9h-1566354796752)(https://i.imgur.com/EZhnfgg.png)]
会自动生成一个jar包,然后我们将这个jar包导入Android studio就可以使用了。
[外链图片转存失败(img-XZggqQbj-1566354796753)(https://i.imgur.com/rC8greG.png)]