Android 提升开发效率工具(Template/Plugin)

Android 提升开发效率工具(Template/Plugin)


本文由 Luzhuo 编写,转发请保留该信息.
原文: https://blog.csdn.net/Rozol/article/details/81947949


Templates(模板)

IntelliJ IDEA 自动编码利器
环境: Windows10 / AndroidStudio3.1
参考资源: https://www.jetbrains.com/help/idea/working-with-source-code.html

自定义代码模板 (Live Templates)

  • 模板存放位置:
    • AndroidStudio 文件存放于: C:\Users\LZLuz\.AndroidStudio3.1\config\templates
  • 快速打开: Ctrl+Alt+S
  • 简介与使用:
    • Android 提升开发效率工具(Template/Plugin)_第1张图片
  • 自带变量参数
    • $END$: 最后都编辑完后光标所处的位置
    • $SELECTION$: 设置环绕实时代码模板 (就是模板包围代码)
  • 内置函数:
    • annotated(“annotation qname”) - Creates a symbol of type with an annotation that resides at the specified location. For an example, see Live Templates in the iterations group.
    • anonymousSuper() - Suggests a supertype for a Kotlin object expression.
    • arrayVariable() - Suggests all array variables applicable in the current scope. For an example, see Live Templates in the iterations group.
    • blockCommentEnd - Returns the characters that indicate the end of a block comment in the current language context.
    • blockCommentStart - Returns the characters that indicate the start of a block comment in the current language context.
    • camelCase(String) - Converts a string into camelCase. For example, camelCase(my-text-file), camelCase(my text file), and camelCase(my_text_file) all return myTextFile.
    • capitalize(String) - Capitalizes the first letter of the parameter.
    • capitalizeAndUnderscore(sCamelCaseName) - Capitalizes all the letters of a CamelCase name passed as the parameter, and inserts an underscore between the parts. For example, capitalizeAndUnderscore(FooBar) returns FOO_BAR.
    • castToLeftSideType() - Casts the right-side expression to the left-side expression type. It is used in the iterations group to have a single template for generating both raw-type and Generics Collections.
    • className() - Returns the name of the current class (the class where the template is expanded).
    • classNameComplete() - This expression substitutes for the class name completion at the variable position.
    • clipboard() - Returns the contents of the system clipboard.
    • commentEnd() - Returns the characters that indicate the end of a comment in the current language context. The return value is empty if line comments are defined in the current language.
    • commentStart() - Returns the characters that indicate the start of a comment in the current language context. If line comments are defined in the current language, their start indicator is preferable.
    • complete() - Invokes code completion at the position of the variable.
    • completeSmart() - Invokes smart type completion at the position of the variable.
    • componentTypeOf() - Returns component type of an array. For an example, see live templates in the iterations group and in the other group.
    • concat(expressions…) - Returns a concatenation of all the strings passed to the function as parameters.
    • currentPackage() - Returns the current package name.
    • date(sDate) - Returns the current system date in the specified format.
      • date(): 11/23/12
      • date(“dd MMM yyyy”): 23 Nov 2012
    • decapitalize(sName) - Replaces the first letter of the parameter with the corresponding lowercase letter.
    • descendantClassEnum() - Returns the children of the class specified as a string parameter.
    • enum(sCompletionString1,sCompletionString2,…) - Returns a list of comma-separated strings suggested for completion when the template is expanded.
    • escapeString(sEscapeString) - Escapes the string specified as the parameter.
    • expectedType() - Returns the expected type of the expression into which the template expands. Makes sense if the template expands in the right part of an assignment, after return, etc.
    • fileName() - Returns the name of the current file with its extension.
    • fileNameWithoutExtension() - Returns the name of the current file without its extension.
    • firstWord(sFirstWord) - Returns the first word of the string passed as the parameter.
    • groovyScript(“”groovy code”“, arg1) - Returns a Groovy script with the specified code.”
      • You can use the groovyScript() function with multiple arguments. The first argument is the text of the script that is executed or the path to the file that contains the script. The next arguments are bound to the _1, _2, _3, …_n variables that are available inside your script. Also, the _editor variable is available inside the script. This variable is bound to the current editor.
      • 案例: groovyScript("_1.take(Math.min(23, _1.length()));", className())
    • guessElementType () - Makes a guess on the type of elements stored in a java.util.Collection. To make a guess, IntelliJ IDEA tries to find the places where the elements were added to or extracted from the container.
    • iterableComponentType() - Returns the type of an iterable component, such as an array or a collection.
    • iterableVariable() - Returns the name of a variable that can be iterated.
    • JsArrayVariable() - Returns the name of the current JavaScript array.
    • jsClassName() - Returns the name of the current JavaScript class.
    • jsComponentTypeOf() - Returns the type of the current JavaScript component.
    • jsDefineParameter - Based on the name of the module, returns the parameter from**define([“”module”“], function (>) {}).**
    • jsMethodName() - Returns the name of the current JavaScript method.
    • jsQualifiedClassName() - Returns the complete name of the current JavaScript class.
    • jsSuggestIndexName() - Returns a suggested name for an index variable from most commonly used ones: i, j, k, etc. The names that are not used in the the current scope yet are shown first.
    • jsSuggestVariableName() - Returns the suggested name for a variable based on its variable type and initializer expression, according to your code style settings that refer to the variable naming rules.
      • For example, if it is a variable that holds an element within an iteration, IntelliJ IDEA makes a guess on the most reasonable name, taking into account the name of the container that is iterated.
    • jsSuggestDefaultVariableKind(Boolean) - The Boolean parameter determines whether constants are allowed or not in the current context. If no parameter is specified, constants are allowed. When the templates expands, a drop-down list is shown with var, let, const options for TypeScript and ES6 and with only one var option for earlier JavaScript versions.
    • jsSuggestImportedEntityName() - Suggests the name for import statements of the type import * as $ITEM$ from ""$MODULE$"" or import $ITEM$ from ""$MODULE$""based on the file name.
    • lineCommentStart - Returns the characters that indicate the start of a line comment in the current language context.
    • lineNumber() - Returns the current line number.
    • lowercaseAndDash(String) - Converts a camelCase string into lower case and inserts n-dashes as separators. For example, lowercaseAndDash(MyExampleName) returns my-example-name.
    • methodName() - Returns the name of the method in which the template is expanded.
    • methodParameters() - Returns the list of parameters of the method in which the template is expanded.
    • methodReturnType() - Returns the type of the value returned by the current method (in which the template is expanded).
    • qualifiedClassName() - Returns the fully qualified name of the current class (in which the template is expanded).
    • rightSideType() - Declares the left-side variable with a type of the right-side expression. It is used in the iterations group to have a single template for generating both raw-type and Generics Collections.
    • snakeCase(String) - Converts a string into snake_case. For example, snakeCase(fooBar)**returns **foo_bar.
    • spaceSeparated(String) - Converts a string into lowercase and inserts spaces as separators. For example, spaceSeparated(fooBar) returns foo bar.
    • substringBefore(String,Delimiter) - Removes the extension after the specified delimiter and returns only the file name. This is helpful for test file names (for example, substringBefore( FileName F i l e N a m e ,”“.”“) returns component-test in component-test.js).
    • subtypes(sType) - Returns the subtypes of the type passed as the parameter.
    • suggestFirstVariableName(sFirstVariableName) - Doesn’t suggest true, false, this, super.
    • suggestIndexName() - Suggests the name of an index variable from most commonly used ones: i, j, k, and so on (first one that is not used in the current scope).
    • suggestVariableName() - Suggests the name for a variable based on the variable type and its initializer expression, according to your code style settings that refer to the variable naming rules.
      • For example, if it is a variable that holds an element within an iteration, IntelliJ IDEA makes a guess on the most reasonable name, taking into account the name of the container being iterated.”
    • time(sSystemTime) - Returns the current system time in the specified format.
    • typeOfVariable(VAR) - Returns the type of the variable passed as the parameter.
    • underscoresToCamelCase(String) - Replaces underscores with camelCase letters in the string passed as the parameter. For example, underscoresToCamelCase(foo_bar)**returns **fooBar.
    • underscoresToSpaces(sParameterWithSpaces) - Replaces underscores with spaces in the string passed as the parameter. For example, underscoresToSpaces(foo_bar) returns foo bar.
    • user() - Returns the name of the current user.
    • variableOfType() - Suggests all variables that may be assigned to the type passed as the parameter, for example, variableOfType(“”java.util.Vector”“). If you pass an empty string (“”) as the parameter, the function suggests all variables regardless of their types.

AndroidStudio自带代码模板

  • fori: for循环
  • sout: System.out.println();
  • ifn: if (savedInstanceState == null) { }

自定义文件模板 (File and Code Templates)

  • 文件模板: 创建文件时, 帮你生成预设代码的模板
  • 简介与使用:
    • Android 提升开发效率工具(Template/Plugin)_第2张图片
  • 模板预设变量:
    • ${PACKAGE_NAME} - the name of the target package where the new class or interface will be created.
    • ${PROJECT_NAME} - the name of the current project.
    • ${FILE_NAME} - the name of the PHP file that will be created.
    • ${NAME} - the name of the new file which you specify in the New File dialog box during the file creation.
    • ${USER} - the login name of the current user.
    • ${DATE} - the current system date.
    • ${TIME} - the current system time.
    • ${YEAR} - the current year.
    • ${MONTH} - the current month.
    • ${DAY} - the current day of the month.
    • ${HOUR} - the current hour.
    • ${MINUTE} - the current minute.
    • ${PRODUCT_NAME} - the name of the IDE in which the file will be created.
    • ${MONTH_NAME_SHORT} - the first 3 letters of the month name. Example: Jan, Feb, etc.
    • ${MONTH_NAME_FULL} - full name of a month. Example: January, February, etc.

插件 (Plugins)

  • 简介与使用:
    • Android 提升开发效率工具(Template/Plugin)_第3张图片

插件开发

  • 开发文档: http://www.jetbrains.org/intellij/sdk/docs/welcome.html
  • AndroidStudio = SDK + Intellij + Android Plugin For Intellji
  • AS常见插件: (Generrate…: 快捷键 Alt+Insert)
    • Android ButterKnife Zelezny: 自动生成依赖注入代码
      • 使用: 右键代码中的布局 -> Generrate… -> Generate Butterknife Injections
    • Android Parcelable Code generator: 自动生成Parcelable代码
      • 使用: 创建Bean类 -> Generrate… -> Parcelable
    • SelectorChapek for Android: 生成资源选择器
      • 使用: drawable-xhdpi下放两张图片(normal / pressed) -> 右键drawable-xhdpi -> Generate Android Selectors
    • GsonFormat: 解析json生成javaBean
      • 使用: 创建Bean类 -> Generrate… -> GsonFormat
    • GodeGlance: 编辑器右侧添加小图预览功能
    • CommonCode: 代码快速查找/插入
      • 使用: Code -> Generrate CommonCode(Alt+G)
    • Translation: 翻译
      • 使用: 选词(中文/英文) -> Ctrl + Shift + Y
        • 打开翻译对话框: Ctrl + Shift + O
  • 准备工作:
    • 安装/配置JDK
    • 下载IDEA IC版本(社区版 Intellij IDEA **Community **Edition)
      • https://www.jetbrains.com/idea
  • 创建AS插件:
    • 创建:
      • 1.创建新工程: Create New Project -> Intellij Platform Plugin -> project name
    • 编写:
      • 2.src -> 创建包 -> 创建Action
      • Android 提升开发效率工具(Template/Plugin)_第4张图片
    • 添加库:
      • 2.1. 项目下创建lib文件夹 -> 放入jar包 -> 选中jar包 Add as Library… ->
    • 运行:
      • 3.run
        • 会打开一个加载了插件的Intellij
    • 发布:
      • 4.0.完善信息: plugin.xml
      • 4.生成本地插件: Build -> Prepare Plugin Module…
      • 5.1.上传到插件库服务器:
        • 登录: https://account.jetbrains.com
    • 使用plugin:
      - Your Account -> upload plugin -> category(Administraion Tools) -> 等待审核
      • 5.复制xxx.zip -> AS安装本地插件
  • plugin.xml配置:

    
      lluzhuo.me.unique.plugin.id
      TextPlugin
      1.0
      Luzhuo
    
      
          most HTML tags may be used
        ]]>
    
      
          most HTML tags may be used
        ]]>
      
    
      
      
    
      
      
      
      com.intellij.modules.lang
    
    
      
        
      
    
      
        
        
          
          
        
      
    
    
    
  • 常用代码演示:

package me.luzhuo;

import com.intellij.codeInsight.actions.ReformatCodeProcessor;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.EverythingGlobalScope;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.JBColor;

import java.awt.*;

public class TestAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
        // 1.动作出发之后执行

        // 拿到当前工程对象
        Project project = e.getData(PlatformDataKeys.PROJECT);


        // --- 对话框 ---
        String name = Messages.showInputDialog(project, "请输入内容", "标题", Messages.getQuestionIcon());
        Messages.showMessageDialog(project, "提示框", "标题", Messages.getInformationIcon());


        // --- 获取用户选择的文本 ---
        Editor editor = e.getData(PlatformDataKeys.EDITOR); // 获取编辑器对象
        SelectionModel model = editor.getSelectionModel(); // 获取用户选择对象模型
        String selectText = model.getSelectedText(); // 获取模型中的文本


        // --- 网络请求 ---
        // 可以直接使用第三方包


        // --- 线程 ---
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 在子线程中不能更新ui

                ApplicationManager.getApplication().invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        // 这里是渲染线程, 可以更新UI
                    }
                });
            }
        }).start();


        // --- 弹窗气泡 ---
        JBPopupFactory factory = JBPopupFactory.getInstance(); // 获取默认popup工厂
        BalloonBuilder builder = factory.createHtmlTextBalloonBuilder("内容", null, new JBColor(new Color(255, 255, 255), new Color(0, 0, 0)), null);// 颜色: 亮主题 / 暗主题
        builder.setFadeoutTime(5000) // 无操作停留时间
                .createBalloon() // 创建气泡
                .show(factory.guessBestPopupLocation(editor), Balloon.Position.below); // 显示位置


        // --- 文件/文件夹 ---
        // 检索文件
        PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, "plugin" + ".xml", GlobalSearchScope.allScope(project)); // 从整个项目搜索 plugin.xml 文件
        if (psiFiles.length <= 0) return;
        XmlFile xmlFile = (XmlFile) psiFiles[0];

        // 检索元素
        xmlFile.accept(new XmlRecursiveElementVisitor(){ // 遍历元素
            @Override
            public void visitElement(PsiElement element) {
                super.visitElement(element);

                if (element instanceof XmlTag) {
                    XmlTag tag = (XmlTag) element;
                    String name = tag.getName(); // tag name

                    if (name.equalsIgnoreCase("include")) {
                        XmlAttribute layout = tag.getAttribute("layout", null);

                        PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, layout.getValue() + ".xml", GlobalSearchScope.allScope(project)); // 搜索指定文件
                        XmlFile include = (XmlFile) psiFiles[0];

                        XmlAttribute id = tag.getAttribute("android:id", null);  // 获取id字段属性
                        String idValue = id.getValue(); // 获取字段id值
                    }
                }
            }
        });

        // 获取目标Class文件
        int offset = editor.getCaretModel().getOffset(); // 光标位置
        PsiElement element = xmlFile.findElementAt(offset); // 对应文件
        PsiClass clazz = PsiTreeUtil.getParentOfType(element, PsiClass.class);
        clazz = clazz instanceof SyntheticElement ? null : clazz;

        // 根据类名查找类
        PsiClass activityClass = JavaPsiFacade.getInstance(project).findClass("android.app.Activity", new EverythingGlobalScope(project));
        if (clazz.isInheritor(activityClass, true)){ } // 判断获取的类是否继承了 android.app.Activity

        // 根据方法名查找方法
        PsiMethod[] methods = clazz.findMethodsByName("onCreate", false);


        PsiElementFactory factoryE = JavaPsiFacade.getElementFactory(project); // Java元素工厂

        // 获取Class元素
        String className = clazz.getName(); // 获取类名

        PsiField[] fields = clazz.getFields(); // 获取字段
        String fieldName = fields[0].getName(); // 字段名

        // 添加到class
        String fromText = "插入的内容";
        clazz.add(factoryE.createFieldFromText(fromText, clazz)); // 插入内容到类上方
        clazz.add(factoryE.createMethodFromText(fromText, clazz)); // 插入内容到类里面

        // 代码块
        PsiCodeBlock methodBody = methods[0].getBody();
        methodBody.add(factoryE.createStatementFromText("内容", methods[0])); // 往 methods[0] 方法里的 methodBody(如: initView()) 代码块里写入 内容


        // 检索方法内代码
        for (PsiStatement psiStatement : methods[0].getBody().getStatements()) {
            // 方法调用的代码
            if (psiStatement.getFirstChild() instanceof PsiMethodCallExpression) { // psiStatement.getFirstChild() 获取第一个孩子
                // 查找setContentView
                PsiReferenceExpression methodExpression = ((PsiMethodCallExpression) psiStatement.getFirstChild()).getMethodExpression();
                String methodCode = methodExpression.getText(); // 如方法内写了 "setContentView" 代码

                // 写入 代码 到某元素 之前/后
                methods[0].getBody().addAfter(factoryE.createStatementFromText("内容", clazz), psiStatement);  // 将 内容 写入到 psiStatement 之后
            }

            // 方法return的代码
            if (psiStatement instanceof PsiReturnStatement) {
                PsiReturnStatement returnStatement = (PsiReturnStatement) psiStatement;
                String returnValue = returnStatement.getReturnValue().getText(); // return的值
            }
        }

        // 写入Java文件 (异步执行)
        JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project);
        styleManager.optimizeImports(psiFiles[0]); // 选择文件
        styleManager.shortenClassReferences(clazz); // 选择类
        new ReformatCodeProcessor(project, clazz.getContainingFile(), null, false).runWithoutProgress();

    }
}

快捷键

  • Select Template: Ctrl+Alt+J
  • 打开代码模板: Ctrl+J

常用快捷键 (太常用也省略, 如 Ctrl + Y)

  • Alt + Enter: 智能辅助
    • 接口类: 生成 / 跳转 接口实现类
    • 接口实现类: 接口类 中添加新方法
    • 生成单元测试类
    • 移除未使用变量 / 对象等
    • 属性: set, get, 构造方法
    • 方法名 / 变量名: 添加doc
    • 作用域: 修改作用域
    • 方法: 生成返回值
    • 对象: 导包
  • Ctrl + R: 替换
  • Ctrl + W: 递进式选择代码块
  • Ctrl + E: 最近打开的文件历史记录
  • Ctrl + G: 跳到指定行
  • Ctrl + P: 方法参数提示显示
  • Ctrl + Q: 显示文档内容
  • Ctrl + U: 方法/类 在父类中的位置
  • Ctrl + B: 跳到定义处
    • F4: 同
  • Ctrl + H: 显示当前类的层次结构
  • Ctrl + +/-: 展开/折叠 代码
    • Ctrl + Shift + +/-: 展开/折叠 所有代码
  • Ctrl + [/]: 花括号 开始/结束 位置
    • Ctrl + Shift + [/]: 光标所在位置到中括号位置
  • Ctrl/Shift + F4: 关闭 / 新窗口打开 当前编辑文件
  • Ctrl + F12: 当前文件结构层
  • Alt + Insert: 代码自动生成
  • Alt + ↑↓←→: 切换窗口 / 跳转方法名
  • F2: 跳转到错误位置
  • Shift + F9/F10: debug / run
  • Shift + F11: 书签
    • F11: 设置书签
  • Shift + 左键单击: 关闭当前打开文件
  • Shift + 滚轮前后滚动: 横向滚动轴滚动
  • Ctrl + Alt + T: 对选中的代码弹出环绕选项弹出层
  • Ctrl + Alt + C/F/V/M: 重构: 提取常量 / 成员变量 / 变量 / 代码
  • Ctrl + Alt + F7: 显示使用的地方
  • Ctrl + Alt + Enter: 光标所在行上空出一行
  • Ctrl + Alt + Home: 弹出跟当前文件有关联的文件弹出层
  • Ctrl + Alt + [/]: 切换项目窗口
  • Ctrl + Shift + N: 通过文件名打开文件
  • Ctrl + Shift + U: 大小写
  • Ctrl + Shift + V: 拷贝缓存
  • Ctrl + Shift + Space: 智能代码提示
  • Ctrl + Shift + Enter: 自动结束代码
  • Ctrl + Shift + Backspace: 退回到上次修改的地方
  • Ctrl + Shift + 左键单击: 直接打开变量类
  • Ctrl + Shift + ↑↓: 调整 代码/方法 顺序
    • Alt + Shift + ↑↓: 只调整 代码 顺序
  • Alt + Shift + C: 查看最近操作项目的变化情况列表
  • Alt + shift + ←/→: 切换布局界面的 design / text 视图
  • Shift*2: Search Everywhere

常用Debug快捷键

  • Shift + F9: debug
  • Ctrl + F8: 设置断点
  • F9: 继续执行到下个断点
  • F8: 单步执行
  • Alt+F8: 查询变量
  • Alt+F9: 查看当前行结果
  • Alt+F10: 跳到当前断点处
  • Ctrl+Shift+F8: 查看所有断点

AndroidStudio Gradle第三依赖统一管理

  • 步骤:

    • 1.在项目下创建 luzhuo.gradle文件

      ext {
      
          android = [
                  compileSdkVersion: 26, // 24:7, 25:7.1.1, 26:8.0
                  minSdkVersion : 14,
                  targetSdkVersion : 26,
                  versionCode  : 1,
                  versionName  : "1.0"
          ]
      
          // 包版本管理
          dependVersion = [
                  support: "26.1.0"
          ]
      
          dependencies = [
      
                  // ------------- Android -------------
                  supportV4: "com.android.support:support-v4:${dependVersion.support}",  // 无须依赖
                  appcompatV7: "com.android.support:appcompat-v7:${dependVersion.support}",  // 必须依赖
      
                  design: "com.android.support:design:${dependVersion.support}",
      
      
                  // --- 测试 ---
                  junit: "junit:junit:4.12",
                  espresso: "com.android.support.test.espresso:espresso-core:3.0.2",
                  runner: "com.android.support.test:runner:1.0.2",
      
                  // ------------- ButterKnife -------------
                  butterknife: 'com.jakewharton:butterknife:8.2.1',
                  butterknifeCompiler: 'com.jakewharton:butterknife-compiler:8.2.1',
          ]
      }
      
    • 2.使用:

      • 在project中的build.gradle中首行添加: apply from: "luzhuo.gradle"
      • 改造app的build.gradle文件:

        apply plugin: 'com.android.application'
        
        android {
            compileSdkVersion rootProject.ext.android.compileSdkVersion
            defaultConfig {
                applicationId "me.luzhuo.livetemplates"
                minSdkVersion rootProject.ext.android.minSdkVersion
                targetSdkVersion rootProject.ext.android.targetSdkVersion
                versionCode rootProject.ext.android.versionCode
                versionName rootProject.ext.android.versionName
                testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            }
            buildTypes {
                release {
                    minifyEnabled false
                    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                }
            }
        }
        
        dependencies {
            implementation fileTree(dir: 'libs', include: ['*.jar'])
            implementation rootProject.ext.dependencies.appcompatV7
            implementation rootProject.ext.dependencies.constraint
            testImplementation rootProject.ext.dependencies.junit
            androidTestImplementation rootProject.ext.dependencies.runner
            androidTestImplementation rootProject.ext.dependencies.espresso
        
            implementation rootProject.ext.dependencies.butterknife
            annotationProcessor rootProject.ext.dependencies.butterknifeCompiler
        }
        

你可能感兴趣的:(android)