AndroidStudio编写插件超详细教程(二)

本教程从0开始,边探索边讲解思路,保证详细~~~写的时候发现有点长,准备分2-3次写完吧。

AndroidStudio编写插件超详细教程(一)
AndroidStudio编写插件超详细教程(二)
AndroidStudio编写插件超详细教程(三)


  • 获取xml文件实体对象
  • 获取类名和id一一对应的对象集合

获取xml文件实体对象

最近重构项目实在有点忙,两篇中间也是隔的时间有点久,尽量抽时间多写一下
吧!

我们先来整理一下我们手上有的“资源”。
上一篇文章,我们得到了PsiElement(光标选到的元素)Editor(光标等一写编辑上的操作)xxx.xml(资源文件的名字)

接下来,我们的任务是根据名字取到这个xml文件的实体。

上次我们通过官网,找到了一个方法

FilenameIndex.getFilesByName(project, name, scope);

很显然,这个方法可以通过文件名,得到PsiFile。不过,这个方法除了project和name之外,还需要一个scope。字面意思应该是个范围。我们用编辑器看一下这个方法,第三个参数需要一个GlobalSearchScope

官网搜了一下这个类,似乎并没有搜到介绍它的。我们先看一下这个类有没有什么静态方法可以得到它的实体。

Android 插件不全屏 安卓插件怎么写_androud studio


看了一下,通过文件得到肯定是没办法了。看来看去,似乎也就module这个东西有点希望。通过编辑器一看,发现有个ModuleUtil,里面有一个findModuleForPsiElement()方法,所需的参数正好是我们有的psiElement。先不管这个能不能行了,反正有参数了,先试试再说。

Module moduleForPsiElement = ModuleUtil.findModuleForPsiElement(psiElement);
GlobalSearchScope searchScope = GlobalSearchScope.moduleScope(moduleForPsiElement);
Project project = anActionEvent.getData(DataKeys.PROJECT);
//得到所有名字为name的文件
PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, name, searchScope);
for (PsiFile file : psiFiles) {
    //得到的psiFiles长度为1,打印一下文件名name和内容text,发现名字为我们需要的xxxx.xml,内容也和文件里的内容一致。
    System.out.println(file.getName());
    System.out.println(file.getText());
}

通过这个方法,我们得到了我们要的xml文件实体类。


获取类名和id一一对应的对象集合

接下来就是遍历里面的id了。为了避免有bug,我们先多放几个控件,包括viewgroup的嵌套,还有include和自定义view。大概长这样。

<!-- 伪代码去除了无用代码,只保留了id -->
<!-- activity_main.xml -->
<RelativeLayout
    android:id="@+id/rlVidwGroup">

    <TextView
        android:id="@+id/tvHelloWorld"/>

    <ImageView
        android:id="@+id/ivIcon"/>

    <LinearLayout
        android:id="@+id/llViewGroup"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvInner"/>

        <include
            layout="@layout/include_plugin_test"/>

    </LinearLayout>

    <com.jarvis.myapplication.app.Custom
        android:id="@+id/custom"/>

</RelativeLayout>

<!-- include_plugin_test.xml -->
<LinearLayout
    android:id="@+id/llIncludeViewGroup"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tvInclude"/>

</LinearLayout>

不知道大家还记不记得,我们上一次再官网找到了一个能够递归遍历psiFile内元素的方法

Android 插件不全屏 安卓插件怎么写_Android 插件不全屏_02

其中参数PsiRecursiveElementWalkingVisitor有很多子类,其中就有XmlRecursiveElementVisitor,看名字正是我们需要的。(其实这里用PsiRecursiveElementWalkingVisitor也行,只不过需要把返回值手动强转成XML文件的元素)。我们调用一下这个方法,并且重写我们需要的方法。

很明显。我们可能用到的是visitXmlAttribute和visitXmlTag。但是由于我们得到id时还需要得到它对应的类,以便于我们生成参数类型,所以这里我们必须用visitXmlTag得到标签类。并且创建一个bean类,里面暂时存储我们一会得到的类名和id。

public class ResIdBean {
    String name;
    String id;
    public ResIdBean(String name, String id) {
        this.name = name;
        this.id = id;
    }
}

我们先考虑一般情况,也就是没有include的时候。这时候比较简单,类名就是标签名。自定义控件打出来的是完整的类名。

resFile.accept(new XmlRecursiveElementVisitor(true) {
    @Override
    public void visitXmlTag(XmlTag tag) {
        super.visitXmlTag(tag);
        String className = tag.getName();
    }
});

接下来我们要得到控件的id。通过tag获取名为”android:id”的attribute属性。然后分割一下字符串就可以得到对应的id了。我们打印一下,确实是我们想要的值,我们把类名和id存在一个List集合里备用。

ArrayList<ResIdBean> resIdBeans = new ArrayList<>();
resFile.accept(new XmlRecursiveElementVisitor(true) {
    @Override
    public void visitXmlTag(XmlTag tag) {
        super.visitXmlTag(tag);
        XmlAttribute attribute = tag.getAttribute("android:id");
        if (attribute != null) {
            String idValue = attribute.getValue();
            if (idValue != null && idValue.startsWith("@+id/")) {
                String[] split = idValue.split("/");
                String className = tag.getName();
                String id = split[1];
                System.out.println(className + "---" + id);
                resIdBeans.add(new ResIdBean(className, id));
            }
        }
    }
});

接下来就是获取include标签中的类名和id了。由于include中只有xml文件的名字,所以,和之前一样,我们需要先得到xml文件的名字,然后得到xml文件的实体类,在进行同样的操作得到类名和id,如果include中还有include,我们还需要进行这样的操作。显然这是一个递归。

我们完善一下代码,简单封装一下之前写的方法。如果tagName为include就继续通过文件名找到文件,然后遍历获得id,如果不是include就放入集合中。封装好的代码大概是这样。

//伪代码,需要根据前面讲的自行修改。
private void getResIdBeans(PsiFile psiFile, ArrayList<ResIdBean> container) {
    psiFile.accept(new XmlRecursiveElementVisitor(true) {
        super.visitXmlTag(tag);
        if (tag.getName().equals("include")) {
            String xmlName = String.format("%s.xml", name);
            getResIdBeans(include, container);
            PsiFile fileByName = getFileByName(psiFile, xmlName);
            getResIdBeans(fileByName, container);
        }else{
            container.add(new ResIdBean(className, id));
        }
    }
}

最后我们往这个方法中传入的ArrayList<ResIdBean> container里面就放好了我们存的ResIdBean了。

现在我们已经得到了我们选中xml文件中所有的id集合了。

文章写完后,代码会上传到github。需要的朋友可以去github下载。

代码github地址:https://github.com/GeniusLiu/FindViewById-Plugin
这个代码只是一个小demo,应该还会更新,看到的朋友给个star呗~~~