本文参考了红橙的视频上稍作修改。复制并实现了 Android Studio 生成 注入的插件且添加了新功能。
效果如图
本插件采用 IntelliJ IDEA 开发。插件主要代码如下。
本示例代码主要构成如下图,
其中,
plugin.xml 定义了一些插件的版本(<id></id>)、名称(<name></name>、描述(<description></description>、动作(<action></action> 等。
ButterKnifeIOC 动作的执行者类,里面写了些查找R.layout.xxxxx 布局的内容。并把查找的id等显示出来,即看到的gif的第一帧。
Element 里面写了些,获取 R.id.xxxx ,变量名 的东西。其中
// 命名1 aa_bb_cc; 2 aaBbCc 3 mAaBbCc private int fieldNameType = 1; /** * 获取id,R.id.id * * @return */ public String getFullID() { StringBuilder fullID = new StringBuilder(); String rPrefix = "R.id."; fullID.append(rPrefix); fullID.append(id); return fullID.toString(); } /** * 获取变量名,变量名的生成方式在这里修改 * * @return */ public String getFieldName() { if (TextUtils.isEmpty(this.fieldName)) { String fieldName = id; String[] names = id.split("_"); if (fieldNameType == 2) { // aaBbCc StringBuilder sb = new StringBuilder(); for (int i = 0; i < names.length; i++) { if (i == 0) { sb.append(names[i]); } else { sb.append(Util.firstToUpperCase(names[i])); } } fieldName = sb.toString(); } else if (fieldNameType == 3) { // mAaBbCc StringBuilder sb = new StringBuilder(); for (int i = 0; i < names.length; i++) { if (i == 0) { sb.append("m"); } sb.append(Util.firstToUpperCase(names[i])); } fieldName = sb.toString(); } this.fieldName = fieldName; } return this.fieldName; }
IdBean 定义gif 第一帧面板上的一些显示数据。其中
/** * mFieldJTextField接口 ,布局id变量名在面板上修改的监听 */ public interface FieldFocusListener { void setFieldName(JTextField fieldJTextField); } /** * id注释接口 ,布局id变量名对应的注释在面板上修改的监听 */ public interface AnnotationForId { void setAnnotationForId(JTextField fieldJTextField); }
Util 定义的一些工具类,其中
/** * 解析xml获取string的值 * * @param psiFile * @param text * @return */ public static String getTextFromStringsXml(PsiFile psiFile, String text) { psiFile.accept(new XmlRecursiveElementVisitor() { @Override public void visitElement(PsiElement element) { super.visitElement(element); if (element instanceof XmlTag) { XmlTag tag = (XmlTag) element; if (tag.getName().equals("string") && tag.getAttributeValue("name").equals(text)) { PsiElement[] children = tag.getChildren(); String value = ""; for (PsiElement child : children) { value += child.getText(); } // value = <string name="app_name">My Application</string> // 用正则获取值 Pattern p = Pattern.compile("<string name=\"" + text + "\">(.*)</string>"); Matcher m = p.matcher(value); while (m.find()) { StringValue = m.group(1); } } } } }); return StringValue; } public static String getTextOrHintString(Element element, Project mProject) { // 设置变量名,获取text里面的内容 String text = element.getXml().getAttributeValue("android:text"); if (TextUtils.isEmpty(text)) { // 如果是text为空,则获取hint里面的内容 text = element.getXml().getAttributeValue("android:hint"); } // 如果是@string/app_name类似 if (!TextUtils.isEmpty(text) && text.contains("@string/")) { text = text.replace("@string/", ""); // 获取strings.xml PsiFile[] psiFiles = FilenameIndex.getFilesByName(mProject, "strings.xml", GlobalSearchScope.allScope(mProject)); if (psiFiles.length > 0) { for (PsiFile psiFile : psiFiles) { // 获取src\main\res\values下面的strings.xml文件 String dirName = psiFile.getParent().toString(); if (dirName.contains("src\\main\\res\\values")) { text = getTextFromStringsXml(psiFile, text); } } } } return text; }
/** * 获取所有id * * @param file * @param elements * @return */ public static java.util.List<Element> getIDsFromLayout(final PsiFile file, final java.util.List<Element> elements) { // To iterate over the elements in a file // 遍历一个文件的所有元素 file.accept(new XmlRecursiveElementVisitor() { @Override public void visitElement(PsiElement element) { super.visitElement(element); // 解析Xml标签 if (element instanceof XmlTag) { XmlTag tag = (XmlTag) element; // 获取Tag的名字(TextView)或者自定义 String name = tag.getName(); // 如果有include if (name.equalsIgnoreCase("include")) { // 获取布局 XmlAttribute layout = tag.getAttribute("layout", null); // 获取project Project project = file.getProject(); // 布局文件 XmlFile include = null; PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, getLayoutName(layout.getValue()) + ".xml", GlobalSearchScope.allScope(project)); if (psiFiles.length > 0) { include = (XmlFile) psiFiles[0]; } if (include != null) { // 递归 getIDsFromLayout(include, elements); return; } } // 获取id字段属性 XmlAttribute id = tag.getAttribute("android:id", null); if (id == null) { return; } // 获取id的值 String idValue = id.getValue(); if (idValue == null) { return; } XmlAttribute aClass = tag.getAttribute("class", null); if (aClass != null) { name = aClass.getValue(); } // 添加到list try { Element e = new Element(name, idValue, tag); elements.add(e); } catch (IllegalArgumentException e) { } } } }); return elements; }
ViewFieldMethodCreator 创建代码插入Android studio 中的类里,其中
/** * 创建变量 */ private void generateFields() { for (Element element : mElements) { if (mClass.getText().contains("@BindView(" + element.getFullID() + ")")) { // 不创建新的变量 continue; } StringBuilder fromText = new StringBuilder(); String fieldJTextFieldLabel = element.getAnnotationForId(); if (!TextUtils.isEmpty(fieldJTextFieldLabel)) { // 创建注释 fromText.append("/*** ").append(fieldJTextFieldLabel).append(" **/\n"); } // 创建 注解和控件名称、变量 即 @BindView(R.id.xxx) View xxx fromText.append("@BindView(").append(element.getFullID()).append(")\n"); fromText.append(element.getName()); fromText.append(" "); fromText.append(element.getFieldName()); fromText.append(";"); // 创建点击方法 if (element.isCreateFiled()) { // 添加到class mClass.add(mFactory.createFieldFromText(fromText.toString(), mClass)); } } }
FindViewByIdDialog 创建gif里的第一帧的图片上的数据,其中
定义了图片上头部和底部内容
// 标签JPanel private JPanel mPanelTitle = new JPanel(); private JLabel mTitleId = new JLabel("ViewId"); private JLabel mTitleClick = new JLabel("OnClick"); private JLabel mTitleField = new JLabel("ViewFiled"); private JLabel mTitleLabel = new JLabel("ID注释"); // 确定、取消JPanel private JPanel mPanelButtonRight = new JPanel(); private JButton mButtonConfirm = new JButton("确定"); private JButton mButtonCancel = new JButton("取消");
添加中间部分的内容
/** * 解析mElements,并添加到JPanel */ private void initContentPanel() { mContentJPanel.removeAll(); // 设置内容 for (int i = 0; i < mElements.size(); i++) { Element mElement = mElements.get(i); // 获取到id定义的文字并设置 String textOrHintString = Util.getTextOrHintString(mElement, mProject); mElement.setAnnotationForId(textOrHintString); IdBean itemJPanel = new IdBean(new GridLayout(1, 5, 10, 10), JBUI.Borders.empty(5, 10), new JCheckBox(mElement.getName()), new JLabel(mElement.getId()), new JCheckBox(), new JTextField(mElement.getFieldName()), new JTextField(mElement.getAnnotationForId()), mElement); // 监听 itemJPanel.setEnableActionListener(this); itemJPanel.setClickActionListener(clickCheckBox -> mElement.setIsCreateClickMethod(clickCheckBox.isSelected())); itemJPanel.setFieldFocusListener((fieldJTextField) -> { mElement.setFieldName(fieldJTextField.getText()); // 从输入框获取id变量数据 }); // id注释接口,获取写入的id注释文字 itemJPanel.setAnnotationForIdFocusListener(fieldJTextField -> mElement.setAnnotationForId(fieldJTextField.getText())); mContentJPanel.add(itemJPanel); mContentConstraints.fill = GridBagConstraints.HORIZONTAL; mContentConstraints.gridwidth = 0; mContentConstraints.gridx = 0; mContentConstraints.gridy = i; mContentConstraints.weightx = 1; mContentLayout.setConstraints(itemJPanel, mContentConstraints); } mContentJPanel.setLayout(mContentLayout); jScrollPane = new JBScrollPane(mContentJPanel); jScrollPane.revalidate(); // 添加到JFrame getContentPane().add(jScrollPane, 1); }
提示,
1、若要改变命名规则,请到 “Element” 中修改
2、若要改变生成的代码(即 @BindView(R.id.xxx) View xxx) 请到 “ViewFieldMethodCreator”中修改
代码下载地址:https://github.com/suiyuanyimeng/butterknifeioc.git