├── .gitignore ├── README.md ├── ResTools ├── .gitignore ├── build.gradle └── src │ └── main │ ├── groovy │ └── com │ │ └── github │ │ └── better │ │ └── restools │ │ ├── MyClass.java │ │ ├── ResToolsConfiguration.java │ │ ├── ResToolsPlugin.groovy │ │ ├── Tools.groovy │ │ ├── base │ │ ├── BaseFolderResReplace.groovy │ │ └── BaseReplace.groovy │ │ ├── folder │ │ ├── AnimReplace.groovy │ │ ├── ColorReplace.groovy │ │ ├── DrawableReplace.groovy │ │ ├── LayoutReplace.groovy │ │ ├── MenuReplace.groovy │ │ ├── MipmapReplace.groovy │ │ ├── RawReplace.groovy │ │ └── XmlReplace.groovy │ │ └── values │ │ └── ValuesReplace.groovy │ └── resources │ └── META-INF │ └── gradle-plugins │ └── plugin.resTools.properties ├── ResourceTools ├── .gitignore ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── github │ │ └── better │ │ └── android │ │ └── tools │ │ ├── ResToolsConfig.kt │ │ ├── Wrapper.kt │ │ ├── base │ │ ├── BaseFolderResReplace.kt │ │ └── BaseReplace.kt │ │ ├── folder │ │ ├── AnimReplace.kt │ │ ├── AnimatorReplace.kt │ │ ├── ColorReplace.kt │ │ ├── DrawableReplace.kt │ │ ├── LayoutReplace.kt │ │ ├── MenuReplace.kt │ │ ├── MipmapReplace.kt │ │ ├── RawReplace.kt │ │ └── XmlReplace.kt │ │ └── values │ │ └── ValuesReplace.kt │ └── test │ └── kotlin │ ├── better │ └── Test1.kt │ └── resTools │ └── ResToolsTest.kt ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── better │ │ └── restools │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── better │ │ │ └── restools │ │ │ └── demo │ │ │ └── MainActivity.kt │ └── res │ │ ├── anim │ │ ├── better_actionsheet_dialog_in.xml │ │ └── better_actionsheet_dialog_out.xml │ │ ├── drawable-v24 │ │ └── better_better_ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── better_alertdialog_left_selector.xml │ │ └── better_better_ic_launcher_background.xml │ │ ├── layout │ │ ├── better_better_aaa.xml │ │ ├── better_better_activity_main.xml │ │ └── better_better_b.xml │ │ ├── mipmap-xxhdpi │ │ ├── better_better_ic_launcher.png │ │ └── better_better_ic_launcher_round.png │ │ ├── values-en │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── github │ └── better │ └── restools │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .idea/ 12 | gradle/ 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AndroidResourceTools 3 | 4 | 因为模块化开发时,android 的资源名称(除id外)是不能重名的,如果手动修改,很繁琐; 5 | 本插件的目的:**重命名Android 资源名称与资源文件名;方便模块化开发;** 6 | 7 | 8 | # 效果展示 9 | 10 | 11 | ![效果展示](https://github.com/zhaoyubetter/MarkdownPhotos/raw/master/img/plugin/res_demo.gif) 12 | 13 | # 使用说明 14 | 15 | 1. 在项目的根目录`build.gradle`,添加依赖如下 16 | 17 | ```java 18 | dependencies { 19 | ... 20 | classpath 'com.github.better.restools:ResTools:1.0.1' 21 | } 22 | ``` 23 | 24 | 2. 在对应的 module 下 `build.gradle`下,apply 插件,如下: 25 | 26 | ```java 27 | apply plugin: 'com.android.application' 28 | apply plugin: 'plugin.resTools' // 资源重命名插件 29 | 30 | // 配置插件dsl 31 | resConfig { 32 | new_prefix = 'better_' // 资源前缀 33 | old_prefix = '' // 老前缀,可为''空字符串 34 | // === below use default 35 | // resFolderPath 资源目录 36 | // srcFolderPath 源代码目录 37 | // manifestFilePath 清单文件目录 38 | } 39 | ``` 40 | 41 | 3. clean 工程后,可以发现,在对应的 module 下,可发现 `resourcetools` task 组,展开,点击「replaceResName」 执行; 42 | 43 | # 特别说明(特别重要) 44 | 45 | >1. 因是直接替换文件,千万不要在主分支,主开发分支使用,建议使用新分支,测试ok后,合并; 46 | >2. 目前插件没有加入文件事务处理,即:不能实现要么全部成功,要么全部失败; 47 | 48 | 49 | ## `1.0.1` 版本支持的资源如下: 50 | 51 | 通过分析Android工程,分析得出Android App 工程的资源分为2大类: 52 | 53 | - 文件夹类型; 54 | - values类型(**目前values类型,尚不支持 attrs 与 styles**); 55 | 56 | | 资源类型 | 源代码中 | xml引用中 |备注| 57 | | :--- | ------ | :---- |:--| 58 | | layout | R.layout.XXX
import kotlinx.android.synthetic.main.xxx.* | @layout/XXX |布局文件
kotlin导入布局时,重名支持| 59 | | drawable | R.drawable.XXX | @drawable/XXX || 60 | | anim | R.anim.XXX | @anim/XXX || 61 | | color | R.color.XXX | @color/XXX || 62 | | mipmap | R.mipmap.XXX | @mipmap.XXX || 63 | | menu | R.menu.XXX | |Xml 暂无引用| 64 | | raw | R.raw.XXX | |Xml 暂无引用| 65 | | xml | R.xml.XXX | |Xml 暂无引用| 66 | | | | || 67 | | string | R.string.XXX | @string/XXX || 68 | | color | R.color.XXX | @color/XXX || 69 | | dimens | R.dimen.XXX | @dimen/XXX || 70 | | arrays | R.array.XXX | @array/XXX || 71 | | style | R.style.XXX | @style/XXX |需要考虑到parent,比较复杂| 72 | | attr | R.attr.XXX | |需要特殊处理,主要在自定义属性上| 73 | 74 | 75 | # TODO 76 | 1. 文件的事务操作有待支持; 77 | 2. Attrs 有待支持; 78 | 3. style有待支持; 79 | 4. 其他资源类型有待支持; 80 | 5. 如果资源名称与Android自带资源名重复时,会发生替换问题,此问题,有待修正; -------------------------------------------------------------------------------- /ResTools/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ResTools/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'groovy' 3 | apply plugin: 'com.novoda.bintray-release' 4 | 5 | 6 | def group = "com.github.better.restools" //project属性 7 | def VERSION = "1.0.1" //project属性 8 | 9 | dependencies { 10 | implementation gradleApi()//gradle sdk 11 | implementation localGroovy()//groovy sdk 12 | implementation fileTree(dir: 'libs', include: ['*.jar']) 13 | } 14 | 15 | sourceSets { 16 | main.java.srcDirs += 'src/main/java' 17 | main.java.srcDirs += 'src/main/groovy' 18 | } 19 | 20 | // ====== to jcenter 21 | // gradle clean build generatePomFileForMavenPublication bintrayUpload -PbintrayUser=zhaoyubetter -PbintrayKey= -PdryRun=false 22 | 23 | publish { 24 | userOrg = 'zhaoyubetter'//bintray.com用户名 25 | groupId = group //jcenter上的路径 26 | artifactId = 'ResTools'//项目名称 27 | publishVersion = VERSION //版本号 28 | desc = 'Android resource: change resouces name.'//描述 29 | website = 'https://github.com/zhaoyubetter/AndroidResourceTools' 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/MyClass.java: -------------------------------------------------------------------------------- 1 | package com.github.better.restools; 2 | 3 | /* 4 | import com.github.better.restools.folder.AnimReplace; 5 | import com.github.better.restools.folder.ColorReplace; 6 | import com.github.better.restools.folder.DrawableReplace; 7 | import com.github.better.restools.folder.LayoutReplace; 8 | import com.github.better.restools.folder.MenuReplace; 9 | import com.github.better.restools.folder.MipmapReplace; 10 | import com.github.better.restools.folder.RawReplace; 11 | import com.github.better.restools.folder.XmlReplace; 12 | import com.github.better.restools.values.ValuesReplace; 13 | 14 | import java.io.IOException; 15 | import java.util.HashSet; 16 | import java.util.Set; 17 | 18 | 19 | public class MyClass { 20 | public static void main(String[] args) throws IOException { 21 | String sourcePath = "/Users/zhaoyu1/Documents/app名称/app/src/main/java"; 22 | String resPath = "//Users/zhaoyu1/Documents/app名称/app/src/main/res"; 23 | String manifestFilePath = "/Users/zhaoyu1/Documents/app名称/app/src/main/AndroidManifest.xml"; 24 | 25 | final ResToolsConfiguration config = new ResToolsConfiguration("better_", "", sourcePath, resPath, manifestFilePath); 26 | 27 | // // 1.test layout 28 | LayoutReplace layoutReplace = new LayoutReplace(config); 29 | layoutReplace.replaceThis(); 30 | // 31 | // // 2.test drawable 32 | DrawableReplace drawableReplace = new DrawableReplace(config); 33 | drawableReplace.replaceThis(); 34 | // 35 | // 36 | // // 3. test color 37 | ColorReplace colorReplace = new ColorReplace(config); 38 | colorReplace.replaceThis(); 39 | // 40 | // 41 | // // 4. test Anim 42 | AnimReplace anim = new AnimReplace(config); 43 | anim.replaceThis(); 44 | // 45 | // // 5. test menu 46 | MenuReplace menuReplace = new MenuReplace(config); 47 | menuReplace.replaceThis(); 48 | // 49 | // // 6. test mipmap == need test more time 50 | MipmapReplace mipmapReplace = new MipmapReplace(config); 51 | mipmapReplace.replaceThis(); 52 | // 53 | // // 7.test raw 54 | RawReplace rawReplace = new RawReplace(config); 55 | rawReplace.replaceThis(); 56 | // 57 | // // 8.test xml 58 | XmlReplace xmlReplace = new XmlReplace(config); 59 | xmlReplace.replaceThis(); 60 | 61 | 62 | // ===9.values test not support attrs 63 | ValuesReplace valuesReplace = new ValuesReplace(config); 64 | Set set = new HashSet<>(); 65 | set.add(ValuesReplace.ValuesType.string); 66 | valuesReplace.replaceValues(ValuesReplace.ALL_VALUES_TYPES); 67 | 68 | 69 | testString(); 70 | 71 | } 72 | 73 | 74 | private static void testString() { 75 | // String str = " 正在为您寻找车辆"; 76 | //// String str = " 正在为您寻找车辆"; 77 | // String regex ="()"; 78 | // 79 | // Pattern pattern = Pattern.compile(regex); 80 | // Matcher matcher = pattern.matcher(str); 81 | // while(matcher.find()) { 82 | // 83 | // System.out.println(matcher.group(1)); 84 | // System.out.println(matcher.group(2)); 85 | // System.out.println(matcher.group(3)); 86 | // } 87 | 88 | 89 | } 90 | } 91 | */ -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/ResToolsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.better.restools; 2 | 3 | public class ResToolsConfiguration { 4 | /** 5 | * 新的资源前缀 6 | */ 7 | public String new_prefix = ""; 8 | /** 9 | * 老的资源前缀,取值说明如下: 10 | *

11 | * 1. 使用空字符串: 12 | * 比如:new_prefix 为 'mae_', module_main_activity.xml 会被替换成 mae_module_main_activity.xml 13 | * 2. 取值字符串时: 14 | * 比如:old_prefix='module_', new_prefix='mae_', module_main_activity.xml 会被替换成 mae_main_activity.xml 15 | */ 16 | public String old_prefix = ""; 17 | 18 | /** 19 | * 源代码目录 20 | */ 21 | public String srcFolderPath = ""; 22 | 23 | /** 24 | * 资源文件目录 25 | */ 26 | public String resFolderPath = ""; 27 | 28 | /** 29 | * 清单文件路径 30 | */ 31 | public String manifestFilePath = ""; 32 | 33 | 34 | public ResToolsConfiguration(String new_prefix, String old_prefix, String srcFolderPath, String resFolderPath, String manifestFilePath) { 35 | this.new_prefix = new_prefix; 36 | this.old_prefix = old_prefix; 37 | this.srcFolderPath = srcFolderPath; 38 | this.resFolderPath = resFolderPath; 39 | this.manifestFilePath = manifestFilePath; 40 | } 41 | 42 | public ResToolsConfiguration() { 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/ResToolsPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools 2 | 3 | import com.github.better.restools.folder.AnimReplace 4 | import com.github.better.restools.folder.ColorReplace 5 | import com.github.better.restools.folder.DrawableReplace 6 | import com.github.better.restools.folder.LayoutReplace 7 | import com.github.better.restools.folder.MenuReplace 8 | import com.github.better.restools.folder.MipmapReplace 9 | import com.github.better.restools.folder.RawReplace 10 | import com.github.better.restools.folder.XmlReplace 11 | import com.github.better.restools.values.ValuesReplace 12 | import com.github.better.restools.ResToolsConfiguration 13 | import org.gradle.api.Plugin 14 | import org.gradle.api.Project 15 | 16 | /** 17 | * zhaoyu1 18 | */ 19 | class ResToolsPlugin implements Plugin { 20 | 21 | static final String APP = "com.android.application" 22 | static final String LIBRARY = "com.android.library" 23 | 24 | @Override 25 | void apply(Project project) { 26 | if (!(project.plugins.hasPlugin(APP) || project.plugins.hasPlugin(LIBRARY))) { 27 | throw new IllegalArgumentException( 28 | 'ResTools gradle plugin can only be applied to android projects.') 29 | } 30 | 31 | // config 32 | project.extensions.create('resConfig', ResToolsConfiguration.class) 33 | 34 | // === Create Task 35 | project.tasks.create(["name": "replaceResName", "group": "resourceTools"]) << { 36 | if (!project.android) { 37 | throw new IllegalStateException('Must apply \'com.android.application\' or \'com.android.library\' first!') 38 | } 39 | 40 | if (project.resConfig == null) { // check config 41 | throw new IllegalArgumentException( 42 | 'ResTools gradle plugin "resConfig DSL" config can not be null.') 43 | } 44 | 45 | // === System default 46 | String sourceFolder = project.android.sourceSets.main.java.srcDirs[0].getAbsolutePath() 47 | String resFolder = project.android.sourceSets.main.res.srcDirs[0].getAbsolutePath() 48 | String manifestFilePath = project.android.sourceSets.main.manifest.srcFile.getAbsolutePath() 49 | 50 | long startTime = System.currentTimeMillis() // startTime 51 | 52 | // === User settings 53 | def config = project.resConfig 54 | if (config.new_prefix == null || config.new_prefix.trim().length() == 0) { 55 | throw new IllegalArgumentException( 56 | 'the [new_prefix] can not be null (必须配置新的前缀)') 57 | } 58 | if (config.srcFolderPath != null && config.srcFolderPath.trim().length() > 0) { 59 | sourceFolder = config.srcFolderPath 60 | } 61 | if (config.resFolderPath != null && config.resFolderPath.trim().length() > 0) { 62 | resFolder = config.resFolderPath 63 | } 64 | if (config.manifestFilePath != null && config.manifestFilePath.trim().length() > 0) { 65 | manifestFilePath = config.manifestFilePath 66 | } 67 | 68 | // === print all settings 69 | 70 | println(">>>>>> old_prefix: ${config.old_prefix}") 71 | println(">>>>>> new_prefix: ${config.new_prefix}") 72 | println(">>>>>> srcFolder : ${sourceFolder}") 73 | println(">>>>>> resFolder : ${resFolder}") 74 | println(">>>>>> AndroidManifest.xml file path : ${manifestFilePath}") 75 | 76 | // === do work 77 | println "++++++++++++++++++++++ Start replace Android resources..." 78 | 79 | ResToolsConfiguration workConfig = new ResToolsConfiguration( 80 | config.new_prefix, 81 | config.old_prefix, 82 | sourceFolder, 83 | resFolder, 84 | manifestFilePath 85 | ) 86 | doWork(workConfig) 87 | 88 | println("++++++++++++++++++++++ Finish replace resouces name, Total time: ${(System.currentTimeMillis() - startTime) / 1000} ") 89 | } 90 | } 91 | 92 | private def doWork(ResToolsConfiguration config) { 93 | // 1. layout 94 | LayoutReplace layoutReplace = new LayoutReplace(config); 95 | layoutReplace.replaceThis(); 96 | 97 | // // 2. drawable 98 | DrawableReplace drawableReplace = new DrawableReplace(config); 99 | drawableReplace.replaceThis(); 100 | // 101 | // 102 | // // 3. color 103 | ColorReplace colorReplace = new ColorReplace(config); 104 | colorReplace.replaceThis(); 105 | // 106 | // 107 | // // 4. Anim 108 | AnimReplace anim = new AnimReplace(config); 109 | anim.replaceThis(); 110 | // 111 | // // 5. menu 112 | MenuReplace menuReplace = new MenuReplace(config); 113 | menuReplace.replaceThis(); 114 | // 115 | // // 6. mipmap 116 | MipmapReplace mipmapReplace = new MipmapReplace(config); 117 | mipmapReplace.replaceThis(); 118 | // 119 | // // 7. raw 120 | RawReplace rawReplace = new RawReplace(config); 121 | rawReplace.replaceThis(); 122 | // 123 | // // 8. xml 124 | XmlReplace xmlReplace = new XmlReplace(config); 125 | xmlReplace.replaceThis(); 126 | 127 | ////////////// all values types //////////////////// 128 | // === 9. values test not support attrs 129 | ValuesReplace valuesReplace = new ValuesReplace(config); 130 | valuesReplace.replaceValues(ValuesReplace.ALL_VALUES_TYPES); 131 | } 132 | } -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/Tools.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools 2 | 3 | 4 | class Tools { 5 | 6 | final static OUTPUT = "out/android/export/" 7 | final static USER_CURRENT_DIR = System.getProperty("user.dir") 8 | 9 | static void checkDir(String moduleDir, String appResDir) { 10 | File moduleDirFile = new File(moduleDir) 11 | if (!moduleDirFile.exists() || !moduleDirFile.isDirectory()) { 12 | throw new RuntimeException("the Aodule ${moduleDir} Directory is wrong!") 13 | } 14 | // app 主工程资源目录,默认values 15 | File tappResDir = new File(appResDir) 16 | if (!tappResDir.exists() || !tappResDir.isDirectory()) { 17 | throw new RuntimeException("the App ${appResDir} Res Directory is wrong!") 18 | } 19 | } 20 | 21 | // 文件夹名称过滤 22 | final static class DirNamePrefixFilter implements FilenameFilter { 23 | def prefix = "" 24 | 25 | DirNamePrefixFilter(String prefix) { 26 | this.prefix = prefix 27 | } 28 | 29 | @Override 30 | boolean accept(File dir, String name) { 31 | return dir.isDirectory() && name.startsWith(prefix) 32 | } 33 | } 34 | 35 | final static class FileNameSuffixFilter implements FileFilter { 36 | def suffix = "" 37 | 38 | def FileNameSuffixFilter(suffix) { 39 | this.suffix = suffix 40 | } 41 | 42 | @Override 43 | boolean accept(File pathname) { 44 | return pathname.name.endsWith(suffix) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/base/BaseFolderResReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.base 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | 5 | /** 6 | * 资源Base 7 | * @auther better 8 | */ 9 | abstract class BaseFolderResReplace extends BaseReplace { 10 | 11 | BaseFolderResReplace(ResToolsConfiguration config) { 12 | super(config) 13 | } 14 | 15 | /** 16 | * 获取资源类型名称 17 | * @return 18 | */ 19 | abstract String getResTypeName() 20 | 21 | /** 22 | * 匹配源代码的正则表达式 23 | * @return 24 | */ 25 | abstract String getJavaRegex() 26 | 27 | /** 28 | * 匹配xml中的正则表达式 29 | * @return 30 | */ 31 | abstract String getXmlRegex() 32 | 33 | /** 34 | * 获取特定类型的资源名集合,比如: 35 | * drawable,需要注意,必须包含 drawable-xx的 36 | * @return 37 | */ 38 | abstract Set getResNameSet() 39 | 40 | /** 41 | * 模板方法 42 | * @throws IOException 43 | */ 44 | final void replaceThis() throws IOException { 45 | println("***** $resTypeName ***** do $resTypeName start...") 46 | 47 | def java_regx = ~getJavaRegex() 48 | def xml_regx = ~getXmlRegex() 49 | Set resNameSet = getResNameSet() 50 | 51 | // 1.源代码目录部分 52 | replaceSrc(resNameSet, java_regx) 53 | // 2.资源目录部分 54 | replaceRes(resNameSet, xml_regx) 55 | 56 | println("***** $resTypeName ***** do $resTypeName finish") 57 | } 58 | 59 | abstract void replaceSrc(Set resNameSet, java_regx) throws IOException 60 | 61 | abstract void replaceRes(Set resNameSet, xml_regx) throws IOException 62 | 63 | 64 | /** 65 | * 文件重命名,处理各种文件的重命名 66 | * @param file 67 | * @param resNameSet 资源名 68 | * @param dir_filter 69 | * @param resTypeName 资源类型名 70 | */ 71 | protected void renameFile(File file, Set resNameSet, dir_filter, resTypeName) { 72 | File[] dirs = file.listFiles(dir_filter) 73 | dirs?.each { dir -> 74 | dir?.eachFile { it -> 75 | String fileName = it.name 76 | if (it.name.lastIndexOf('.' ) > -1) { 77 | // 可能是 png .9 图片 78 | fileName = it.name.substring(0, it.name.lastIndexOf(".")) 79 | if (fileName.endsWith(".9")) { // 可能有.9 80 | fileName = fileName.substring(0, fileName.lastIndexOf(".")) 81 | } 82 | } 83 | 84 | // 只替换指定的资源 85 | if (resNameSet.contains(fileName)) { 86 | String oldName = it.name 87 | String newName = config.new_prefix + oldName 88 | if (oldName.startsWith(config.old_prefix)) { 89 | newName = config.new_prefix + oldName.substring(config.old_prefix.length()) 90 | } 91 | 92 | File newFile = new File(it.getParent(), newName) 93 | if (newFile.exists()) { 94 | newFile.delete() 95 | } 96 | 97 | if (!it.renameTo(newFile)) { 98 | println("--------------- $resTypeName ${it.name} 重命名失败!,请手动修改成:${newFile.name}") 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | /* 106 | void export(String moduleFolder, String appResFolder) { 107 | Tools.checkDir(moduleFolder, appResFolder) 108 | 109 | File moduleDirFile = new File(moduleFolder) 110 | // === 1.layout name map from module. (XXX to R.xxx.XXX) 111 | Map moduleMap = new HashMap<>() 112 | moduleMap(moduleDirFile, moduleDirFile, moduleMap) 113 | // === 2. layout name map from 'MainGo App Res' 114 | File appResDirFile = new File(appResFolder) 115 | // folderName to layout filename list 116 | Map> dirFileMap = getAppLayoutMap(appResDirFile, moduleLayoutMap) 117 | // === 3.according to 'moduleLayoutMap' key, find all layout from 'main app res' and copy layout.xml file from dirs 118 | export(dirFileMap, appResDir) 119 | }*/ 120 | } 121 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/base/BaseReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.base 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | 5 | import java.util.regex.Matcher 6 | 7 | abstract class BaseReplace { 8 | /** 9 | * 源代码路径 10 | */ 11 | protected File srcDir 12 | /** 13 | * 资源文件路径 14 | */ 15 | protected File resDir 16 | 17 | /** 18 | * AndroidManifest 19 | */ 20 | protected File manifestFile 21 | 22 | protected ResToolsConfiguration config 23 | 24 | BaseReplace(ResToolsConfiguration config) { 25 | this.config = config 26 | this.srcDir = new File(config.srcFolderPath) 27 | this.resDir = new File(config.resFolderPath) 28 | this.manifestFile = new File(config.manifestFilePath) 29 | } 30 | 31 | // ====== 源代码中的部分 start ============================================= 32 | /** 33 | * 替换 src 源代码目录中的资源名 34 | * @param file 源代码目录 35 | * @param set 当前module下所有资源名 set 36 | * @param java_regx 匹配正则 37 | */ 38 | protected void replaceSrcDir(File file, Set set, java_regx) { 39 | if (file.exists()) { 40 | if (file.isDirectory()) { 41 | file.eachFile { it -> replaceSrcDir(it, set, java_regx) } 42 | } else { 43 | if (file.name.endsWith(".java") || file.name.endsWith(".kt")) { // only .java or .kt files 44 | handleSrcFile(file, set, java_regx) 45 | } 46 | } 47 | } 48 | } 49 | 50 | private void handleSrcFile(File file, Set set, regex) { 51 | String fileContent = file.getText() // every file is a text file 52 | StringBuffer sb = new StringBuffer() // result content 53 | Matcher matcher = fileContent =~ regex 54 | while (matcher.find()) { 55 | String oldResName = matcher.group(6) // the old res name 56 | if (set.contains(oldResName)) { // 本模块中包含的资源名,才替换 57 | String newResName = config.new_prefix + oldResName 58 | if (oldResName.startsWith(config.old_prefix)) { // 替换掉旧的前缀 59 | newResName = config.new_prefix + oldResName.substring(config.old_prefix.length()) 60 | } 61 | matcher.appendReplacement(sb, "\$1$newResName") // 拼接 保留$1分组,替换$6分组 62 | } 63 | } 64 | // 修改了文件时,才写入文件 65 | if (sb.length() > 0) { 66 | matcher.appendTail(sb) // 添加结尾 67 | file.write(sb.toString()) // 写回文件 68 | } 69 | } 70 | // ====== 源代码中的部分 end ============================================= 71 | 72 | // ====== 资源文件部分公用方法 start ===================== 73 | // def xml_regx = ~/(@XXX\/)(w+)"/ xxx 表示各种资源,如:layout、drawable 等 74 | /** 75 | * 操作资源 76 | * @param file 77 | * @param set 78 | * @param regex 79 | * @param valuesType 是否是 values 类型资源(values-xx, values-en这种) 80 | * values 类型资源时,需要保留 $3 分组 81 | * @return 82 | */ 83 | def handleResFile(File file, Set set, regex, valuesType = false) { 84 | boolean hasUpdate = false // 是否有修改 85 | StringBuilder sb = new StringBuilder() // 文件内容 86 | file.each { line -> 87 | Matcher matcher = line =~ regex 88 | StringBuffer tSb = new StringBuffer() 89 | while (matcher.find()) { 90 | String oldResName = matcher.group(2) 91 | if (set.contains(oldResName)) { 92 | String newResName = config.new_prefix + oldResName 93 | if (oldResName.startsWith(config.old_prefix)) { 94 | newResName = config.new_prefix + oldResName.substring(config.old_prefix.length()) 95 | } 96 | 97 | if (valuesType) { 98 | matcher.appendReplacement(tSb, "\$1$newResName\$3") // 拼接 保留$1分组,替换组2,保留组3 99 | } else { 100 | matcher.appendReplacement(tSb, "\$1$newResName") // 拼接 保留$1分组,替换组2 101 | } 102 | } 103 | } 104 | if (tSb.length() > 0) { // 如果包含了,则重新赋值line,并拼接余下部分 105 | matcher.appendTail(tSb) 106 | hasUpdate = true 107 | line = tSb.toString() 108 | } 109 | 110 | sb.append(line).append("\r\n") 111 | } 112 | 113 | // 有修改了,才重新写入文件 114 | if (hasUpdate) { 115 | file.write(sb.toString()) 116 | } 117 | } 118 | // ====== 资源文件部分公用方法 end ===================== 119 | 120 | /** 121 | * res目录下的资源 - 替换名称 122 | * @param file 123 | * @param set 124 | * @param regx 125 | * @param dir_filter null no filter 126 | * @param valuesType 是否是 values 文件夹 类型(如:values,values-en) 127 | */ 128 | protected def replaceResDir(File file, Set set, regx, dir_filter, valuesType = false) { 129 | File[] dirs = file.listFiles(dir_filter as FilenameFilter) 130 | dirs?.each { dir -> 131 | if (dir != null && dir.isDirectory()) { 132 | dir.eachFile { it -> 133 | if (it.name.endsWith(".xml")) { // 只在xml有引用 134 | handleResFile(it, set, regx, valuesType) 135 | } 136 | } 137 | } 138 | } 139 | 140 | // 清单文件 manifest file 141 | if (manifestFile != null) { 142 | handleResFile(manifestFile, set, regx) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/folder/AnimReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.folder 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseFolderResReplace 6 | 7 | /** 8 | * anim 9 | */ 10 | class AnimReplace extends BaseFolderResReplace { 11 | 12 | private def final DIR_FILTER = new Tools.DirNamePrefixFilter("anim") 13 | private def final RES_TYPE_NAME = "anim" 14 | 15 | AnimReplace(ResToolsConfiguration config) { 16 | super(config) 17 | } 18 | 19 | @Override 20 | String getResTypeName() { 21 | return RES_TYPE_NAME 22 | } 23 | 24 | @Override 25 | String getJavaRegex() { 26 | return "(R(\\s*?)\\.(\\s*?)anim(\\s*?)\\.(\\s*?))(\\w+)" 27 | } 28 | 29 | @Override 30 | String getXmlRegex() { 31 | return "(@anim/)(\\w+)" 32 | } 33 | 34 | @Override 35 | Set getResNameSet() { 36 | Set resNameSet = new HashSet<>() 37 | // 1.获取所有drawable开头的文件夹 38 | File[] dirs = resDir.listFiles(DIR_FILTER) 39 | // 2.获取drawable名字并存储 40 | dirs?.each { dir -> 41 | dir.eachFile { it -> 42 | String fileName = it.name.substring(0, it.name.lastIndexOf(".")) 43 | resNameSet.add(fileName) 44 | } 45 | } 46 | return resNameSet 47 | } 48 | 49 | @Override 50 | void replaceSrc(Set resNameSet, Object java_regx) throws IOException { 51 | println("---------- $RES_TYPE_NAME ----- replace source folder start...") 52 | replaceSrcDir(srcDir, resNameSet, java_regx) 53 | println("---------- $RES_TYPE_NAME ----- replace source folder end") 54 | } 55 | 56 | @Override 57 | void replaceRes(Set resNameSet, Object xml_regx) throws IOException { 58 | // 1.替换文件内容 59 | println("---------- $RES_TYPE_NAME ----- replace res folder start...") 60 | replaceResDir(resDir, resNameSet, xml_regx, null) 61 | println("---------- $RES_TYPE_NAME ----- replace res folder end") 62 | 63 | // 2.修改文件名 64 | println("---------- $RES_TYPE_NAME ----- rename start...") 65 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 66 | println("---------- $RES_TYPE_NAME ----- rename end") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/folder/ColorReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.folder 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseFolderResReplace 6 | 7 | /** 8 | * color 9 | */ 10 | class ColorReplace extends BaseFolderResReplace { 11 | 12 | private def final DIR_FILTER = new Tools.DirNamePrefixFilter("color") 13 | private def final RES_TYPE_NAME = "color" 14 | 15 | ColorReplace(ResToolsConfiguration config) { 16 | super(config) 17 | } 18 | 19 | @Override 20 | String getResTypeName() { 21 | return RES_TYPE_NAME 22 | } 23 | 24 | @Override 25 | String getJavaRegex() { 26 | return "(R(\\s*?)\\.(\\s*?)color(\\s*?)\\.(\\s*?))(\\w+)" 27 | } 28 | 29 | @Override 30 | String getXmlRegex() { 31 | return "(@color/)(\\w+)" 32 | } 33 | 34 | @Override 35 | Set getResNameSet() { 36 | Set resNameSet = new HashSet<>() 37 | // 1.获取所有drawable开头的文件夹 38 | File[] dirs = resDir.listFiles(DIR_FILTER) 39 | // 2.获取drawable名字并存储 40 | dirs?.each { dir -> 41 | dir.eachFile { it -> 42 | String fileName = it.name.substring(0, it.name.lastIndexOf(".")) 43 | resNameSet.add(fileName) 44 | } 45 | } 46 | return resNameSet 47 | } 48 | 49 | @Override 50 | void replaceSrc(Set resNameSet, Object java_regx) throws IOException { 51 | println("---------- $RES_TYPE_NAME ----- replace source folder start...") 52 | replaceSrcDir(srcDir, resNameSet, java_regx) 53 | println("---------- $RES_TYPE_NAME ----- replace source folder end") 54 | } 55 | 56 | @Override 57 | void replaceRes(Set resNameSet, Object xml_regx) throws IOException { 58 | // 1.替换文件内容 59 | println("---------- $RES_TYPE_NAME ----- replace res folder start...") 60 | replaceResDir(resDir, resNameSet, xml_regx, null) 61 | println("---------- $RES_TYPE_NAME ----- replace res folder end") 62 | 63 | // 2.修改文件名 64 | println("---------- $RES_TYPE_NAME ----- rename start...") 65 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 66 | println("---------- $RES_TYPE_NAME ----- rename end") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/folder/DrawableReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.folder 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseFolderResReplace 6 | 7 | /** 8 | * drawable 9 | */ 10 | class DrawableReplace extends BaseFolderResReplace { 11 | 12 | private def final static DIR_FILTER = new Tools.DirNamePrefixFilter("drawable") 13 | private def final static RES_TYPE_NAME = "drawable" 14 | 15 | DrawableReplace(ResToolsConfiguration config) { 16 | super(config) 17 | } 18 | 19 | @Override 20 | String getResTypeName() { 21 | return RES_TYPE_NAME 22 | } 23 | 24 | @Override 25 | String getJavaRegex() { 26 | return "(R(\\s*?)\\.(\\s*?)drawable(\\s*?)\\.(\\s*?))(\\w+)" 27 | } 28 | 29 | @Override 30 | String getXmlRegex() { 31 | return "(@drawable/)(\\w+)" 32 | } 33 | 34 | @Override 35 | Set getResNameSet() { 36 | Set drawableNameSet = new HashSet<>() 37 | // 1.获取所有drawable开头的文件夹 38 | File[] dirs = resDir.listFiles(DIR_FILTER) 39 | // 2.获取drawable名字并存储 40 | dirs?.each { dir -> 41 | dir.eachFile { it -> 42 | String fileName = it.name.substring(0, it.name.lastIndexOf(".")) 43 | if (fileName.endsWith(".9")) { // 可能有.9 44 | fileName = fileName.substring(0, fileName.lastIndexOf(".")) 45 | } 46 | drawableNameSet.add(fileName) 47 | } 48 | } 49 | 50 | return drawableNameSet 51 | } 52 | 53 | @Override 54 | void replaceSrc(Set resNameSet, Object java_regx) throws IOException { 55 | println("---------- $RES_TYPE_NAME ----- replace source folder start...") 56 | replaceSrcDir(srcDir, resNameSet, java_regx) 57 | println("---------- $RES_TYPE_NAME ----- replace source folder end") 58 | } 59 | 60 | @Override 61 | void replaceRes(Set resNameSet, Object xml_regx) throws IOException { 62 | // 1.替换文件内容 63 | println("---------- $RES_TYPE_NAME ----- replace res folder start...") 64 | replaceResDir(resDir, resNameSet, xml_regx, null) 65 | println("---------- $RES_TYPE_NAME ----- replace res folder end") 66 | 67 | 68 | // 2.修改文件名 69 | println("---------- $RES_TYPE_NAME ----- rename start...") 70 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 71 | println("---------- $RES_TYPE_NAME ----- rename end") 72 | 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/folder/LayoutReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.folder 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseFolderResReplace 6 | 7 | import java.util.regex.Matcher 8 | 9 | 10 | /** 11 | * layout 布局资源 12 | */ 13 | public class LayoutReplace extends BaseFolderResReplace { 14 | 15 | private def final DIR_FILTER = new Tools.DirNamePrefixFilter("layout") 16 | private def final RES_TYPE_NAME = "layout" 17 | 18 | LayoutReplace(ResToolsConfiguration config) { 19 | super(config) 20 | } 21 | 22 | @Override 23 | String getResTypeName() { 24 | return RES_TYPE_NAME 25 | } 26 | 27 | @Override 28 | String getJavaRegex() { 29 | // group 6 为名字 30 | return "(R(\\s*?)\\.(\\s*?)layout(\\s*?)\\.(\\s*?))(\\w+)" 31 | } 32 | 33 | @Override 34 | String getXmlRegex() { 35 | // group 2为名字 36 | return "(@layout/)(\\w+)" 37 | } 38 | 39 | @Override 40 | Set getResNameSet() { 41 | Set layoutNameSet = new HashSet<>() 42 | // 1.获取所有layout开头的文件夹 43 | File[] layoutDirs = resDir.listFiles(DIR_FILTER) 44 | // 2.获取layout名字并存储 45 | layoutDirs?.each { layoutDir -> 46 | layoutDir.eachFile { it -> 47 | layoutNameSet.add(it.name.substring(0, it.name.lastIndexOf("."))) 48 | } 49 | } 50 | return layoutNameSet 51 | } 52 | 53 | @Override 54 | void replaceSrc(Set resNameSet, java_regx) throws IOException { 55 | println("---------- layout ----- replace source folder start...") 56 | replaceSrcDir(srcDir, resNameSet, java_regx) 57 | 58 | // 替换kt源代码文件 layout 文件 59 | replaceKtSrcFile(srcDir, resNameSet) 60 | 61 | println("---------- layout ----- replace source folder end") 62 | } 63 | 64 | 65 | @Override 66 | void replaceRes(Set resNameSet, xml_regx) throws IOException { 67 | println("---------- layout ----- replace res folder start...") 68 | // 1.替换文件内容,经验证,xml文件夹的小部分布局,可能引用到了layout文件,所以filter 传 null 69 | replaceResDir(resDir, resNameSet, xml_regx, null) 70 | println("---------- layout ----- replace res folder end") 71 | 72 | // 2.修改文件名 73 | println("---------- layout ----- rename start...") 74 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 75 | println("---------- layout ----- rename end") 76 | } 77 | 78 | 79 | /** 80 | * kt 文件特殊处理,layout 导入包时,需要重新替换,如: 81 | * import kotlinx.android.synthetic.main.activity_main.* 82 | * 替换成: 83 | * import kotlinx.android.synthetic.main.new_prefix_activity_main.* 84 | * @param file 85 | * @param set 86 | */ 87 | private void replaceKtSrcFile(File file, Set set) { 88 | if (file.exists()) { 89 | if (file.isDirectory()) { 90 | file.eachFile { it -> replaceKtSrcFile(it, set) } 91 | } else { 92 | if (file.name.endsWith(".kt")) { // kt 93 | handleKtSrcFileLayout(file, set) 94 | } 95 | } 96 | } 97 | } 98 | 99 | private void handleKtSrcFileLayout(file, set) { 100 | def regex = "(synthetic.main\\.)(\\w+)(\\.)" 101 | String fileContent = file.getText() // every file is a text file 102 | StringBuffer sb = new StringBuffer() // result content 103 | Matcher matcher = fileContent =~ regex 104 | while (matcher.find()) { 105 | String oldResName = matcher.group(2) // the old res name 106 | if (set.contains(oldResName)) { // 本模块中包含的资源名,才替换 107 | String newResName = config.new_prefix + oldResName 108 | if (oldResName.startsWith(config.old_prefix)) { // 替换掉旧的前缀 109 | newResName = config.new_prefix + oldResName.substring(config.old_prefix.length()) 110 | } 111 | matcher.appendReplacement(sb, "\$1$newResName\$3") // 拼接 保留$1$3分组,替换$2分组 112 | } 113 | } 114 | // 修改了文件时,才写入文件 115 | if (sb.length() > 0) { 116 | matcher.appendTail(sb) // 添加结尾 117 | file.write(sb.toString()) // 写回文件 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/folder/MenuReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.folder 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseFolderResReplace 6 | 7 | 8 | /** 9 | * menu 10 | * @menu/xxx 暂时没有,所以res先不处理 11 | */ 12 | class MenuReplace extends BaseFolderResReplace { 13 | 14 | private def final DIR_FILTER = new Tools.DirNamePrefixFilter("menu") 15 | private def final RES_TYPE_NAME = "menu" 16 | 17 | MenuReplace(ResToolsConfiguration config) { 18 | super(config) 19 | } 20 | 21 | @Override 22 | String getResTypeName() { 23 | return RES_TYPE_NAME 24 | } 25 | 26 | @Override 27 | String getJavaRegex() { 28 | return "(R(\\s*?)\\.(\\s*?)menu(\\s*?)\\.(\\s*?))(\\w+)" 29 | } 30 | 31 | @Override 32 | String getXmlRegex() { 33 | return "(@menu/)(\\w+)" 34 | } 35 | 36 | @Override 37 | Set getResNameSet() { 38 | Set resNameSet = new HashSet<>() 39 | // 1.获取所有drawable开头的文件夹 40 | File[] dirs = resDir.listFiles(DIR_FILTER) 41 | // 2.获取drawable名字并存储 42 | dirs?.each { dir -> 43 | dir.eachFile { it -> 44 | String fileName = it.name.substring(0, it.name.lastIndexOf(".")) 45 | resNameSet.add(fileName) 46 | } 47 | } 48 | return resNameSet 49 | } 50 | 51 | @Override 52 | void replaceSrc(Set resNameSet, Object java_regx) throws IOException { 53 | println("---------- $RES_TYPE_NAME ----- replace source folder start...") 54 | replaceSrcDir(srcDir, resNameSet, java_regx) 55 | println("---------- $RES_TYPE_NAME ----- replace source folder end") 56 | } 57 | 58 | @Override 59 | void replaceRes(Set resNameSet, Object xml_regx) throws IOException { 60 | println("---------- $RES_TYPE_NAME ----- $RES_TYPE_NAME not used in the res folder now, so replace res not implemented.") 61 | // 1.替换文件内容 62 | 63 | // 2.修改文件名 64 | println("---------- $RES_TYPE_NAME ----- rename start...") 65 | renameFile(resDir,resNameSet, DIR_FILTER, RES_TYPE_NAME) 66 | println("---------- $RES_TYPE_NAME ----- rename end") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/folder/MipmapReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.folder 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseFolderResReplace 6 | 7 | 8 | /** 9 | * mipmap 10 | */ 11 | class MipmapReplace extends BaseFolderResReplace { 12 | 13 | private def final static DIR_FILTER = new Tools.DirNamePrefixFilter("mipmap") 14 | private def final static String RES_TYPE_NAME = "mipmap" 15 | 16 | MipmapReplace(ResToolsConfiguration config) { 17 | super(config) 18 | } 19 | @Override 20 | String getResTypeName() { 21 | return RES_TYPE_NAME 22 | } 23 | 24 | @Override 25 | String getJavaRegex() { 26 | return "(R(\\s*?)\\.(\\s*?)mipmap(\\s*?)\\.(\\s*?))(\\w+)" 27 | } 28 | 29 | @Override 30 | String getXmlRegex() { 31 | return "(@mipmap/)(\\w+)" 32 | } 33 | 34 | @Override 35 | Set getResNameSet() { 36 | Set drawableNameSet = new HashSet<>() 37 | // 1.获取所有drawable开头的文件夹 38 | File[] dirs = resDir.listFiles(DIR_FILTER) 39 | // 2.获取drawable名字并存储 40 | dirs?.each { dir -> 41 | dir.eachFile { it -> 42 | String fileName = it.name.substring(0, it.name.lastIndexOf(".")) 43 | if (fileName.endsWith(".9")) { // 可能有.9 44 | fileName = fileName.substring(0, fileName.lastIndexOf(".")) 45 | } 46 | drawableNameSet.add(fileName) 47 | } 48 | } 49 | 50 | return drawableNameSet 51 | } 52 | 53 | @Override 54 | void replaceSrc(Set resNameSet, Object java_regx) throws IOException { 55 | println("---------- $RES_TYPE_NAME ----- replace source folder start...") 56 | replaceSrcDir(srcDir, resNameSet, java_regx) 57 | println("---------- $RES_TYPE_NAME ----- replace source folder end") 58 | } 59 | 60 | @Override 61 | void replaceRes(Set resNameSet, Object xml_regx) throws IOException { 62 | // 1.替换文件内容 63 | println("---------- $RES_TYPE_NAME ----- replace res folder start...") 64 | replaceResDir(resDir, resNameSet, xml_regx, null) 65 | println("---------- $RES_TYPE_NAME ----- replace res folder end") 66 | 67 | // 2.修改文件名 68 | println("---------- $RES_TYPE_NAME ----- rename start...") 69 | renameFile(resDir,resNameSet, DIR_FILTER, RES_TYPE_NAME) 70 | println("---------- $RES_TYPE_NAME ----- rename end") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/folder/RawReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.folder 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseFolderResReplace 6 | 7 | 8 | /** 9 | * raw 10 | * @raw / x x x 暂时没有,所以res先不处理 11 | */ 12 | class RawReplace extends BaseFolderResReplace { 13 | 14 | private def final DIR_FILTER = new Tools.DirNamePrefixFilter("raw") 15 | private def final RES_TYPE_NAME = "raw" 16 | 17 | RawReplace(ResToolsConfiguration config) { 18 | super(config) 19 | } 20 | 21 | @Override 22 | String getResTypeName() { 23 | return RES_TYPE_NAME 24 | } 25 | 26 | @Override 27 | String getJavaRegex() { 28 | return "(R(\\s*?)\\.(\\s*?)raw(\\s*?)\\.(\\s*?))(\\w+)" 29 | } 30 | 31 | @Override 32 | String getXmlRegex() { 33 | return "(@raw/)(\\w+)" 34 | } 35 | 36 | @Override 37 | Set getResNameSet() { 38 | Set resNameSet = new HashSet<>() 39 | // 1.获取所有drawable开头的文件夹 40 | File[] dirs = resDir.listFiles(DIR_FILTER) 41 | // 2.获取drawable名字并存储 42 | dirs?.each { dir -> 43 | dir.eachFile { it -> 44 | // may be no file suffixes 45 | if(it.name.lastIndexOf('.') > -1) { 46 | String fileName = it.name.substring(0, it.name.lastIndexOf(".")) 47 | resNameSet.add(fileName) 48 | } else { 49 | resNameSet.add(it.name) 50 | } 51 | } 52 | } 53 | return resNameSet 54 | } 55 | 56 | @Override 57 | void replaceSrc(Set resNameSet, Object java_regx) throws IOException { 58 | println("---------- $RES_TYPE_NAME ----- replace source folder start...") 59 | replaceSrcDir(srcDir, resNameSet, java_regx) 60 | println("---------- $RES_TYPE_NAME ----- replace source folder end") 61 | } 62 | 63 | @Override 64 | void replaceRes(Set resNameSet, Object xml_regx) throws IOException { 65 | // 1.替换文件内容 66 | println("---------- $RES_TYPE_NAME ----- $RES_TYPE_NAME not used in the res folder now, so replace res not implemented.") 67 | 68 | 69 | // 2.修改文件名 70 | println("---------- $RES_TYPE_NAME ----- rename start...") 71 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 72 | println("---------- $RES_TYPE_NAME ----- rename end") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/folder/XmlReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.folder 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseFolderResReplace 6 | 7 | /** 8 | * xml 9 | * @xml / x x x 暂时没有,所以res文件夹先不处理 10 | */ 11 | class XmlReplace extends BaseFolderResReplace { 12 | 13 | private def final DIR_FILTER = new Tools.DirNamePrefixFilter("xml") 14 | private def final RES_TYPE_NAME = "xml" 15 | 16 | XmlReplace(ResToolsConfiguration config) { 17 | super(config) 18 | } 19 | @Override 20 | String getResTypeName() { 21 | return RES_TYPE_NAME 22 | } 23 | 24 | @Override 25 | String getJavaRegex() { 26 | return "(R(\\s*?)\\.(\\s*?)xml(\\s*?)\\.(\\s*?))(\\w+)" 27 | } 28 | 29 | @Override 30 | String getXmlRegex() { 31 | return "(@xml/)(\\w+)" 32 | } 33 | 34 | @Override 35 | Set getResNameSet() { 36 | Set resNameSet = new HashSet<>() 37 | // 1.获取所有drawable开头的文件夹 38 | File[] dirs = resDir.listFiles(DIR_FILTER) 39 | // 2.获取drawable名字并存储 40 | dirs?.each { dir -> 41 | dir.eachFile { it -> 42 | String fileName = it.name.substring(0, it.name.lastIndexOf(".")) 43 | resNameSet.add(fileName) 44 | } 45 | } 46 | return resNameSet 47 | } 48 | 49 | @Override 50 | void replaceSrc(Set resNameSet, Object java_regx) throws IOException { 51 | println("---------- $RES_TYPE_NAME ----- replace source folder start...") 52 | replaceSrcDir(srcDir, resNameSet, java_regx) 53 | println("---------- $RES_TYPE_NAME ----- replace source folder end") 54 | } 55 | 56 | @Override 57 | void replaceRes(Set resNameSet, Object xml_regx) throws IOException { 58 | // 1.替换文件内容 59 | println("---------- $RES_TYPE_NAME ----- replace res folder start...") 60 | replaceResDir(resDir, resNameSet, xml_regx, DIR_FILTER) // only replace xml folder 61 | println("---------- $RES_TYPE_NAME ----- replace res folder end") 62 | 63 | 64 | // 2.修改文件名 65 | println("---------- $RES_TYPE_NAME ----- rename start...") 66 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 67 | println("---------- $RES_TYPE_NAME ----- rename end") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ResTools/src/main/groovy/com/github/better/restools/values/ValuesReplace.groovy: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.values 2 | 3 | import com.github.better.restools.ResToolsConfiguration 4 | import com.github.better.restools.Tools 5 | import com.github.better.restools.base.BaseReplace 6 | 7 | import java.util.regex.Matcher 8 | 9 | /** 10 | - string 11 | - string_arrays 12 | - color 13 | - dimens 14 | - bool 15 | - integer 16 | - attr not support 17 | - style not support well need repaired 18 | */ 19 | class ValuesReplace extends BaseReplace { 20 | 21 | private def final DIR_FILTER = new Tools.DirNamePrefixFilter("values") 22 | public static final Set ALL_VALUES_TYPES 23 | 24 | static { 25 | ALL_VALUES_TYPES = new LinkedHashSet<>() 26 | ALL_VALUES_TYPES.add(ValuesType.string) 27 | ALL_VALUES_TYPES.add(ValuesType.string_arrays) 28 | ALL_VALUES_TYPES.add(ValuesType.color) 29 | ALL_VALUES_TYPES.add(ValuesType.bool) 30 | ALL_VALUES_TYPES.add(ValuesType.integer) 31 | ALL_VALUES_TYPES.add(ValuesType.dimens) 32 | 33 | // not support now 34 | ALL_VALUES_TYPES.add(ValuesType.attr) 35 | ALL_VALUES_TYPES.add(ValuesType.style) 36 | } 37 | 38 | public ValuesReplace(ResToolsConfiguration config) { 39 | super(config) 40 | } 41 | 42 | /** 43 | * values 类型 44 | */ 45 | public static enum ValuesType { 46 | /* 47 | appName 50 | appName 51 | */ 52 | 53 | string("()", 54 | "(R(\\s*?)\\.(\\s*?)string(\\s*?)\\.(\\s*?))(\\w+)", 55 | "(@string/)(\\w+)" 56 | ), 57 | 58 | string_arrays("()", 59 | "(R(\\s*?)\\.(\\s*?)array(\\s*?)\\.(\\s*?))(\\w+)", 60 | "()" 61 | ), 62 | 63 | color("()", 64 | "(R(\\s*?)\\.(\\s*?)color(\\s*?)\\.(\\s*?))(\\w+)", 65 | "(@color/)(\\w+)" 66 | ), 67 | 68 | dimens("()", 69 | "(R(\\s*?)\\.(\\s*?)dimen(\\s*?)\\.(\\s*?))(\\w+)", 70 | "(@dimen/)(\\w+)" 71 | ), 72 | 73 | bool("()", 74 | "(R(\\s*?)\\.(\\s*?)bool(\\s*?)\\.(\\s*?))(\\w+)", 75 | "(@bool/)(\\w+)" 76 | ), 77 | 78 | integer("()", 79 | "(R(\\s*?)\\.(\\s*?)integer(\\s*?)\\.(\\s*?))(\\w+)", 80 | "(@integer/)(\\w+)" 81 | ), 82 | 83 | attr("", 84 | "", 85 | "" 86 | ), 87 | 88 | style("()", 89 | "(R(\\s*?)\\.(\\s*?)style(\\s*?)\\.(\\s*?))(\\w+)", 90 | "(@style/)(.+?\\\")" 91 | ) 92 | 93 | /** 94 | * 源代码中 95 | * like: R.string.XXX 96 | */ 97 | String java_Regx 98 | /** 99 | * xml 中 100 | * like: XXX 101 | */ 102 | String xml_Regx 103 | /** 104 | * xml 引用中 105 | * like: @string/empty_message 106 | */ 107 | String xml_ref_regex 108 | 109 | ValuesType(String xmlRegx, String javaRegx, String xml_ref_regex) { 110 | this.xml_Regx = xmlRegx 111 | this.java_Regx = javaRegx 112 | this.xml_ref_regex = xml_ref_regex 113 | } 114 | } 115 | 116 | void replaceValues(Set set) { 117 | set.forEach { 118 | switch (it) { 119 | case ValuesType.string: 120 | strings(it) 121 | break 122 | case ValuesType.string_arrays: 123 | arrays(it) 124 | break 125 | case ValuesType.color: 126 | color(it) 127 | break 128 | case ValuesType.dimens: 129 | dimens(it) 130 | break 131 | case ValuesType.bool: 132 | bool(it) 133 | break 134 | case ValuesType.integer: 135 | integer(it) 136 | break 137 | case ValuesType.style: // need implement 138 | style(it) 139 | break 140 | case ValuesType.attr: // need implement 141 | attr(it) 142 | break 143 | } 144 | } 145 | } 146 | 147 | // 字符串 148 | private def strings(ValuesType valueType) { 149 | println("------------ replace strings resource (处理strings资源)") 150 | def stringNameSet = getValueNameSet(valueType.xml_Regx) 151 | def java_regx = ~valueType.java_Regx 152 | def xml_regx = ~valueType.xml_Regx 153 | def xml_ref_regx = ~valueType.xml_ref_regex 154 | // 修改源码中的 155 | replaceSrcDir(srcDir, stringNameSet, java_regx) 156 | // 修改xml中的名称 157 | replaceResDir(resDir, stringNameSet, xml_regx, null, true) 158 | // 修改xml中的引用 159 | replaceResDir(resDir, stringNameSet, xml_ref_regx, null) 160 | } 161 | 162 | // string-array 163 | private def arrays(ValuesType valueType) { 164 | println("------------ replace string-array resource (处理 string-array 资源)") 165 | def nameSet = getValueNameSet(valueType.xml_Regx) 166 | def java_regx = ~valueType.java_Regx 167 | def xml_regx = ~valueType.xml_Regx 168 | def xml_ref_regx = ~valueType.xml_ref_regex // array 引用没有实现 169 | 170 | // 修改源码中的 171 | replaceSrcDir(srcDir, nameSet, java_regx) 172 | // 修改xml中的名称 173 | replaceResDir(resDir, nameSet, xml_regx, null, true) 174 | } 175 | 176 | // color 177 | private def color(ValuesType valueType) { 178 | println("------------ replace color resource (处理 color 资源)") 179 | def nameSet = getValueNameSet(valueType.xml_Regx) 180 | def java_regx = ~valueType.java_Regx 181 | def xml_regx = ~valueType.xml_Regx 182 | def xml_ref_regx = ~valueType.xml_ref_regex 183 | 184 | replaceSrcDir(srcDir, nameSet, java_regx) 185 | // 修改xml中的名称 186 | replaceResDir(resDir, nameSet, xml_regx, null, true) 187 | // 修改xml中的引用 188 | replaceResDir(resDir, nameSet, xml_ref_regx, null) 189 | } 190 | 191 | // dimens 192 | private def dimens(ValuesType valueType) { 193 | println("------------ replace dimens resource (处理 dimens 资源") 194 | def nameSet = getValueNameSet(valueType.xml_Regx) 195 | def java_regx = ~valueType.java_Regx 196 | def xml_regx = ~valueType.xml_Regx 197 | def xml_ref_regx = ~valueType.xml_ref_regex 198 | 199 | replaceSrcDir(srcDir, nameSet, java_regx) 200 | // 修改xml中的名称 201 | replaceResDir(resDir, nameSet, xml_regx, null, true) 202 | // 修改xml中的引用 203 | replaceResDir(resDir, nameSet, xml_ref_regx, null) 204 | } 205 | 206 | // bool 207 | private def bool(ValuesType valueType) { 208 | println("------------ replace bool resource (处理 bool 资源)") 209 | 210 | def nameSet = getValueNameSet(valueType.xml_Regx) 211 | def java_regx = ~valueType.java_Regx 212 | def xml_regx = ~valueType.xml_Regx 213 | def xml_ref_regx = ~valueType.xml_ref_regex 214 | 215 | replaceSrcDir(srcDir, nameSet, java_regx) 216 | // 修改xml中的名称 217 | replaceResDir(resDir, nameSet, xml_regx, null, true) 218 | // 修改xml中的引用 219 | replaceResDir(resDir, nameSet, xml_ref_regx, null) 220 | } 221 | 222 | // integer 223 | private def integer(ValuesType valueType) { 224 | println("------------ replace integer resource (处理 integer 资源)") 225 | 226 | def nameSet = getValueNameSet(valueType.xml_Regx) 227 | def java_regx = ~valueType.java_Regx 228 | def xml_regx = ~valueType.xml_Regx 229 | def xml_ref_regx = ~valueType.xml_ref_regex 230 | 231 | replaceSrcDir(srcDir, nameSet, java_regx) 232 | // 修改xml中的名称 233 | replaceResDir(resDir, nameSet, xml_regx, null, true) 234 | // 修改xml中的引用 235 | replaceResDir(resDir, nameSet, xml_ref_regx, null) 236 | } 237 | 238 | private def style(ValuesType valueType) { 239 | // println("------------ 处理 style 资源") 240 | println("----> sorry style replace is not implement now ( 暂没有实现)") 241 | } 242 | 243 | private def attr(ValuesType valueType) { 244 | // println("------------ 处理 attr 资源") 245 | println("----> sorry attr replace is not implements now (暂没有实现)") 246 | } 247 | 248 | // ========================================================== 249 | 250 | /** 251 | * 读取xml资源,获取values资源名称 set 252 | * @param xmlRegex 253 | */ 254 | private def getValueNameSet(xmlRegex) { 255 | Set nameSet = new HashSet<>() // 字符串 256 | // 1.获取所有values开头的文件夹 257 | File[] dirs = resDir.listFiles(DIR_FILTER) 258 | // 2.遍历文件夹下各个资源文件xml后缀,获取资源名称 259 | dirs?.each { dir -> 260 | dir.eachFile { it -> 261 | if (it.name.endsWith(".xml")) { 262 | it.eachLine { line -> 263 | Matcher matcher = line =~ xmlRegex 264 | while (matcher.find()) { 265 | nameSet.add(matcher.group(2)) 266 | } 267 | } 268 | } 269 | } 270 | } 271 | return nameSet 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /ResTools/src/main/resources/META-INF/gradle-plugins/plugin.resTools.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.github.better.restools.ResToolsPlugin -------------------------------------------------------------------------------- /ResourceTools/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ResourceTools/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply plugin: 'java' 3 | 4 | repositories { 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | implementation fileTree(dir: 'libs', include: ['*.jar']) 10 | implementation group: 'junit', name: 'junit', version: '4.12' 11 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 12 | implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 13 | } 14 | 15 | compileKotlin { 16 | kotlinOptions { 17 | jvmTarget = "1.8" 18 | } 19 | } 20 | compileTestKotlin { 21 | kotlinOptions { 22 | jvmTarget = "1.8" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/ResToolsConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools 2 | 3 | /** 4 | * Created by better On 2019-07-07. 5 | */ 6 | data class ResToolsConfig( 7 | /** 8 | * new prefix 9 | */ 10 | val newPrefix: String = "", 11 | /** 12 | * old prefix 13 | */ 14 | val oldPrefix: String = "", 15 | /** 16 | * src folder 17 | */ 18 | val srcFolderPath: String = "", 19 | /** 20 | * resource folder 21 | */ 22 | val resFolderPath: String = "", 23 | /** 24 | * 25 | */ 26 | val manifestFilePath: String = "" 27 | ) -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/Wrapper.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools 2 | 3 | import java.util.logging.Level 4 | import java.util.logging.Logger 5 | 6 | /** 7 | * Created by better On 2019-07-07. 8 | */ 9 | inline fun T.debug(content: String) { 10 | val log = Logger.getLogger(T::class.simpleName) 11 | log.log(Level.INFO, content) 12 | } 13 | 14 | inline fun T.warn(content: String) { 15 | val log = Logger.getLogger(T::class.simpleName) 16 | log.log(Level.WARNING, content) 17 | } 18 | 19 | /** 20 | * 获取新名称 21 | * @param oldName 老资源名 22 | * return 新的名称 23 | */ 24 | fun ResToolsConfig.getNewName(oldName: String): String { 25 | // 需求:就是如果当前资源是以最新的前序开头就不要进行重命名 26 | if (oldName.startsWith(this.newPrefix)) { 27 | return oldName 28 | } 29 | // 替换掉老的前缀 30 | return this.newPrefix + if (oldName.startsWith(oldPrefix)) { 31 | oldName.substring(oldPrefix.length) 32 | } else { 33 | oldName 34 | } 35 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/base/BaseFolderResReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.base 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.debug 5 | import com.github.better.android.tools.getNewName 6 | import com.github.better.android.tools.warn 7 | import java.io.File 8 | import java.io.FileFilter 9 | import java.io.FilenameFilter 10 | import java.io.IOException 11 | 12 | /** 13 | * Created by better On 2019-07-07. 14 | * 资源 Base 15 | */ 16 | abstract class BaseFolderResReplace(config: ResToolsConfig) : BaseReplace(config) { 17 | 18 | /** 19 | * 资源类型名称 20 | */ 21 | abstract val resTypeName: String 22 | 23 | /** 24 | * 匹配源代码的正则表达式 25 | */ 26 | abstract val javaRegex: String 27 | 28 | /** 29 | * 匹配xml中的正则表达式 30 | */ 31 | abstract val xmlRegex: String 32 | 33 | /** 34 | * 获取特定类型的资源名集合,比如: 35 | * drawable,需要注意,必须包含 drawable-xx 36 | * @return 37 | */ 38 | abstract fun getResNameSet(): Set 39 | 40 | /** 41 | * 替换源代码部分 42 | */ 43 | abstract fun replaceSrc(resNameSet: Set, regex: String) 44 | 45 | /** 46 | * 替换资源部分 47 | */ 48 | abstract fun replaceRes(resNameSet: Set, regex: String) 49 | 50 | /** 51 | * 模板方法 52 | */ 53 | @Throws(IOException::class) 54 | fun replaceThis() { 55 | debug("***** $resTypeName ***** do $resTypeName start...") 56 | getResNameSet().apply { 57 | if (this.isNotEmpty()) { 58 | // 1. 源代码部分 59 | replaceSrc(this, javaRegex) 60 | // 2. 资源目录部分 61 | replaceRes(this, xmlRegex) 62 | } 63 | } 64 | debug("***** $resTypeName ***** do $resTypeName finish...") 65 | } 66 | 67 | /** 68 | * 文件重命名,处理各种文件的重命名 69 | * @param file 70 | * @param resNameSet 资源名 71 | * @param dirFilter 72 | * @param resTypeName 资源类型名 73 | */ 74 | protected fun renameFile(file: File, 75 | resNameSet: Set, 76 | dirFilter: FilenameFilter, 77 | resTypeName: String) { 78 | val dirs = file.listFiles(dirFilter) 79 | dirs?.forEach { dir -> 80 | dir.listFiles().forEach { file -> 81 | var fileName = file.name.let { 82 | var fileName = it 83 | // 含有后缀名的文件 84 | if (it.lastIndexOf('.') > -1) { 85 | fileName = fileName.substringBeforeLast('.') 86 | // 是否 .9 文件 87 | if (fileName.endsWith(".9")) { 88 | fileName = fileName.substringBeforeLast('.') 89 | } 90 | } 91 | fileName 92 | } 93 | 94 | // 只替换指定的资源 95 | if (resNameSet.contains(fileName)) { 96 | val oldName = file.name 97 | val newName =config.getNewName(oldName) 98 | 99 | // 重命名文件 100 | File(file.parent, newName).apply { 101 | if (!this.exists()) { 102 | this.delete() 103 | } 104 | 105 | if (!file.renameTo(this)) { 106 | warn("--------------- $resTypeName ${file.name} 重命名失败!,请手动修改成:${this.name}") 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/base/BaseReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.base 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.getNewName 5 | import java.io.File 6 | import java.io.FileFilter 7 | import java.io.FilenameFilter 8 | import java.lang.StringBuilder 9 | 10 | /** 11 | * Base 12 | */ 13 | abstract class BaseReplace(config: ResToolsConfig) { 14 | /** 15 | * src Dir 16 | */ 17 | protected val srcDir: File 18 | /** 19 | * resources Dir 20 | */ 21 | protected val resDir: File 22 | 23 | /** 24 | * android's manifestFile 25 | */ 26 | protected val manifestFile: File 27 | 28 | /** 29 | * config 30 | */ 31 | protected val config: ResToolsConfig = config 32 | 33 | init { 34 | this.srcDir = File(config.srcFolderPath) 35 | this.resDir = File(config.resFolderPath) 36 | this.manifestFile = File(config.manifestFilePath) 37 | } 38 | 39 | // ====== 源代码中的部分 start ============================================= 40 | /** 41 | * 替换 src 源代码目录中的资源名 ( java & kt ) 42 | * @param file 源代码目录 43 | * @param set 当前 module 下的所有资源名 44 | * @param regex regex 45 | */ 46 | protected fun replaceSrcDir(file: File, set: Set, regex: String) { 47 | if (!file.exists()) return 48 | 49 | if (file.isDirectory) { 50 | file.listFiles().forEach { replaceSrcDir(it, set, regex) } 51 | } else { 52 | if (file.name.endsWith(".java") || file.name.endsWith(".kt")) { 53 | handleSrcFile(file, set, regex) 54 | } 55 | } 56 | } 57 | 58 | private fun handleSrcFile(file: File, set: Set, regex: String) { 59 | val fileContent = file.readText() 60 | val sb = StringBuffer() 61 | val matcher = Regex(regex).toPattern().matcher(fileContent) 62 | while (matcher.find()) { 63 | val oldName = matcher.group(6) // oldName 64 | if (set.contains(oldName)) { // 本模块包含的资源名才替换 65 | val newName = config.getNewName(oldName) 66 | matcher.appendReplacement(sb, "\$1$newName") // 拼接: 保留$!分组,替换$6分组 67 | } 68 | } 69 | 70 | // sb 长度大于0时,才覆盖文件 71 | if (sb.isNotEmpty()) { 72 | matcher.appendTail(sb) // 添加结尾 73 | file.writeText(sb.toString()) // 写回文件 74 | } 75 | } 76 | // ====== 源代码中的部分 end ============================================= 77 | 78 | 79 | // ====== 资源文件部分公用方法 start ===================== 80 | // regex = ~/(@XXX\/)(w+)"/ xxx 表示各种资源,如:layout、drawable 等 81 | /** 82 | * @param isValuesDir 是否是 values 文件夹类型资源(如:values-xx, values-en这种) 83 | * values 类型资源时,需要保留 $3 分组 84 | */ 85 | fun handleResFile(file: File, set: Set, regex: String, isValuesDir: Boolean = false) { 86 | var hasUpdate = false 87 | val sb = StringBuilder() 88 | file.forEachLine { line -> 89 | var line = line 90 | val matcher = Regex(regex).toPattern().matcher(line) 91 | val tSb = StringBuffer() 92 | while (matcher.find()) { 93 | val oldName = matcher.group(2) 94 | if (set.contains(oldName)) { 95 | val newName = config.getNewName(oldName) 96 | 97 | if (isValuesDir) { 98 | matcher.appendReplacement(tSb, "\$1$newName\$3") // 拼接 保留$1分组,替换组2,保留组3 99 | } else { 100 | matcher.appendReplacement(tSb, "\$1$newName") // 拼接 保留$1分组,替换组2 101 | } 102 | } 103 | } 104 | 105 | if (tSb.isNotEmpty()) { 106 | matcher.appendTail(tSb) 107 | hasUpdate = true 108 | line = tSb.toString() 109 | } 110 | 111 | sb.append(line).append(System.lineSeparator()) 112 | } 113 | 114 | // 有修改了,才重新写入文件 115 | if (hasUpdate) { 116 | file.writeText(sb.toString()) 117 | } 118 | } 119 | // ====== 资源文件部分公用方法 end ===================== 120 | 121 | /** 122 | * res目录下的资源 - 替换名称 123 | * @param file 124 | * @param set 125 | * @param regex 126 | * @param dir_filter null no filter 127 | * @param isValuesDir 是否是 values 文件夹 类型(如:values,values-en) 128 | */ 129 | protected fun replaceResDir(file: File, set: Set, regex: String, 130 | dir_filter: FilenameFilter? = null, isValuesDir: Boolean = false) { 131 | val dirs = file.listFiles(dir_filter) 132 | dirs?.forEach { it -> 133 | if (it.isDirectory) { 134 | it.listFiles(FileFilter { 135 | it.name.endsWith(".xml") 136 | }).forEach { file -> 137 | handleResFile(file, set, regex, isValuesDir) 138 | } 139 | } 140 | } 141 | 142 | // 清单文件 manifest file 143 | if (manifestFile != null) { 144 | handleResFile(manifestFile, set, regex) 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/AnimReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.* 8 | 9 | /** 10 | * anim 11 | * 注意,需要过滤掉 animator 12 | * res/anim/xxx.xml 13 | * Created by better On 2019-07-07. 14 | */ 15 | class AnimReplace(config: ResToolsConfig) : BaseFolderResReplace(config) { 16 | 17 | /** 18 | * 资源名称 19 | */ 20 | private val RES_TYPE_NAME = "anim" 21 | /** 22 | * 需要过滤掉 animator 23 | */ 24 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) && !s.startsWith("animator") } 25 | 26 | override val resTypeName: String = RES_TYPE_NAME 27 | override val javaRegex: String = """(R(\s*?)\.(\s*?)anim(\s*?)\.(\s*?))(\w+)""" 28 | override val xmlRegex: String = """(@anim/)(\w+)""" 29 | 30 | /** 31 | * 过滤掉 animator 的文件夹 32 | * 获取 res/anim/下的所有文件,并将文件转换成集合 33 | * 如:[res/anim/better.xml,res/anim/cz.xml] 返回 [better, cz] 34 | */ 35 | override fun getResNameSet(): Set { 36 | val layoutNameSet = HashSet() 37 | // 1.获取所有layout开头的文件夹,并获取下面的所有的文件 38 | resDir.listFiles(DIR_FILTER)?.forEach { dir -> 39 | dir.listFiles().forEach { file -> 40 | layoutNameSet.add(file.name.substring(0, file.name.lastIndexOf("."))) 41 | } 42 | } 43 | return layoutNameSet 44 | } 45 | 46 | override fun replaceSrc(resNameSet: Set, regex: String) { 47 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 48 | replaceSrcDir(srcDir, resNameSet, javaRegex) 49 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 50 | } 51 | 52 | override fun replaceRes(resNameSet: Set, regex: String) { 53 | // 1.替换文件内容 54 | debug("---------- $RES_TYPE_NAME ----- replace res folder start...") 55 | replaceResDir(resDir, resNameSet, xmlRegex, null) 56 | debug("---------- $RES_TYPE_NAME ----- replace res folder end") 57 | 58 | // 2.修改文件名 59 | debug("---------- $RES_TYPE_NAME ----- rename start...") 60 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 61 | debug("---------- $RES_TYPE_NAME ----- rename end") 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/AnimatorReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.* 8 | 9 | /** 10 | * Animator 11 | * Created by better On 2019-07-07. 12 | */ 13 | class AnimatorReplace(config: ResToolsConfig) : BaseFolderResReplace(config) { 14 | 15 | /** 16 | * 资源名称 17 | */ 18 | private val RES_TYPE_NAME = "animator" 19 | /** 20 | * 需要过滤掉 animator 21 | */ 22 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) } 23 | 24 | override val resTypeName: String = RES_TYPE_NAME 25 | override val javaRegex: String = """(R(\s*?)\.(\s*?)animator(\s*?)\.(\s*?))(\w+)""" 26 | override val xmlRegex: String = """(@animator/)(\w+)""" 27 | 28 | /** 29 | * 过滤掉 animator 的文件夹 30 | * 获取 res/anim/下的所有文件,并将文件转换成集合 31 | * 如:[res/anim/better.xml,res/anim/cz.xml] 返回 [better, cz] 32 | */ 33 | override fun getResNameSet(): Set { 34 | val layoutNameSet = HashSet() 35 | // 1.获取所有layout开头的文件夹,并获取下面的所有的文件 36 | resDir.listFiles(DIR_FILTER)?.forEach { dir -> 37 | dir.listFiles().forEach { file -> 38 | layoutNameSet.add(file.name.substring(0, file.name.lastIndexOf("."))) 39 | } 40 | } 41 | return layoutNameSet 42 | } 43 | 44 | override fun replaceSrc(resNameSet: Set, regex: String) { 45 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 46 | replaceSrcDir(srcDir, resNameSet, javaRegex) 47 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 48 | } 49 | 50 | override fun replaceRes(resNameSet: Set, regex: String) { 51 | // 1.替换文件内容 52 | debug("---------- $RES_TYPE_NAME ----- replace res folder start...") 53 | replaceResDir(resDir, resNameSet, xmlRegex, null) 54 | debug("---------- $RES_TYPE_NAME ----- replace res folder end") 55 | 56 | // 2.修改文件名 57 | debug("---------- $RES_TYPE_NAME ----- rename start...") 58 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 59 | debug("---------- $RES_TYPE_NAME ----- rename end") 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/ColorReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.* 8 | 9 | /** 10 | * color 文件夹 11 | * Created by better On 2019-07-07. 12 | */ 13 | class ColorReplace (config: ResToolsConfig) : BaseFolderResReplace(config) { 14 | 15 | /** 16 | * 资源名称 17 | */ 18 | private val RES_TYPE_NAME = "color" 19 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) } 20 | 21 | override val resTypeName: String = RES_TYPE_NAME 22 | override val javaRegex: String = """(R(\s*?)\.(\s*?)color(\s*?)\.(\s*?))(\w+)""" 23 | override val xmlRegex: String = """(@color/)(\w+)""" 24 | 25 | override fun getResNameSet(): Set { 26 | val layoutNameSet = HashSet() 27 | // 1.获取所有layout开头的文件夹,并获取下面的所有的文件 28 | resDir.listFiles(DIR_FILTER)?.forEach { dir -> 29 | dir.listFiles().forEach { file -> 30 | layoutNameSet.add(file.name.substring(0, file.name.lastIndexOf("."))) 31 | } 32 | } 33 | return layoutNameSet 34 | } 35 | 36 | override fun replaceSrc(resNameSet: Set, regex: String) { 37 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 38 | replaceSrcDir(srcDir, resNameSet, javaRegex) 39 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 40 | } 41 | 42 | override fun replaceRes(resNameSet: Set, regex: String) { 43 | // 1.替换文件内容 44 | debug("---------- $RES_TYPE_NAME ----- replace res folder start...") 45 | replaceResDir(resDir, resNameSet, xmlRegex, null) 46 | debug("---------- $RES_TYPE_NAME ----- replace res folder end") 47 | 48 | // 2.修改文件名 49 | debug("---------- $RES_TYPE_NAME ----- rename start...") 50 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 51 | debug("---------- $RES_TYPE_NAME ----- rename end") 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/DrawableReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.* 8 | 9 | /** 10 | * Created by better On 2019-07-07. 11 | */ 12 | class DrawableReplace(config: ResToolsConfig) : BaseFolderResReplace(config) { 13 | 14 | /** 15 | * 资源名称 16 | */ 17 | private val RES_TYPE_NAME = "drawable" 18 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) } 19 | 20 | override val resTypeName: String = RES_TYPE_NAME 21 | override val javaRegex: String = """(R(\s*?)\.(\s*?)drawable(\s*?)\.(\s*?))(\w+)""" 22 | override val xmlRegex: String = """(@drawable/)(\w+)""" 23 | 24 | override fun getResNameSet(): Set { 25 | val drawableNameSet = HashSet() 26 | resDir.listFiles(DIR_FILTER)?.forEach {dir-> 27 | dir.listFiles().forEach { file-> 28 | var suffixName = file.name.substringBeforeLast(".") 29 | if (suffixName.endsWith(".9")) { 30 | suffixName = suffixName.substringBeforeLast(".") 31 | } 32 | drawableNameSet.add(suffixName) 33 | } 34 | } 35 | 36 | return drawableNameSet 37 | } 38 | 39 | override fun replaceSrc(resNameSet: Set, regex: String) { 40 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 41 | replaceSrcDir(srcDir, resNameSet, javaRegex) 42 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 43 | } 44 | 45 | override fun replaceRes(resNameSet: Set, regex: String) { 46 | // 1.替换文件内容 47 | debug("---------- $RES_TYPE_NAME ----- replace res folder start...") 48 | replaceResDir(resDir, resNameSet, xmlRegex, null) 49 | debug("---------- $RES_TYPE_NAME ----- replace res folder end") 50 | 51 | // 2.修改文件名 52 | debug("---------- $RES_TYPE_NAME ----- rename start...") 53 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 54 | debug("---------- $RES_TYPE_NAME ----- rename end") 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/LayoutReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import com.github.better.android.tools.getNewName 7 | import java.io.File 8 | import java.io.FilenameFilter 9 | import java.util.* 10 | import kotlin.collections.HashSet 11 | 12 | /** 13 | * Layout 14 | * Created by better On 2019-07-07. 15 | */ 16 | class LayoutReplace(config: ResToolsConfig) : BaseFolderResReplace(config) { 17 | 18 | /** 19 | * 资源名称 20 | */ 21 | private val RES_TYPE_NAME = "layout" 22 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) } 23 | 24 | override val resTypeName: String = RES_TYPE_NAME 25 | override val javaRegex: String = """(R(\s*?)\.(\s*?)layout(\s*?)\.(\s*?))(\w+)""" 26 | override val xmlRegex: String = """(@layout/)(\w+)""" 27 | 28 | override fun getResNameSet(): Set { 29 | val layoutNameSet = HashSet() 30 | // 1.获取所有layout开头的文件夹,并获取下面的所有的文件 31 | resDir.listFiles(DIR_FILTER)?.forEach { dir -> 32 | dir.listFiles().forEach { file -> 33 | layoutNameSet.add(file.name.substring(0, file.name.lastIndexOf("."))) 34 | } 35 | } 36 | return layoutNameSet 37 | } 38 | 39 | override fun replaceSrc(resNameSet: Set, regex: String) { 40 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 41 | replaceSrcDir(srcDir, resNameSet, javaRegex) 42 | 43 | // 替换kt源代码文件 layout 文件 44 | replaceKtSrcFile(srcDir, resNameSet) 45 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 46 | } 47 | 48 | override fun replaceRes(resNameSet: Set, regex: String) { 49 | // 1.替换文件内容 50 | debug("---------- $RES_TYPE_NAME ----- replace res folder start...") 51 | replaceResDir(resDir, resNameSet, xmlRegex, null) 52 | debug("---------- $RES_TYPE_NAME ----- replace res folder end") 53 | 54 | // 2.修改文件名 55 | debug("---------- $RES_TYPE_NAME ----- rename start...") 56 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 57 | debug("---------- $RES_TYPE_NAME ----- rename end") 58 | 59 | } 60 | 61 | /** 62 | * kt 文件特殊处理,layout 导入包时,需要重新替换,如: 63 | * import kotlinx.android.synthetic.main.activity_main.* 64 | * 替换成: 65 | * import kotlinx.android.synthetic.main.new_prefix_activity_main.* 66 | * @param file 67 | * @param set 68 | */ 69 | private fun replaceKtSrcFile(file: File, set: Set) { 70 | if (file.exists()) { 71 | if (file.isDirectory) { 72 | file.listFiles().forEach { 73 | replaceKtSrcFile(it, set) 74 | } 75 | } else { 76 | if (file.name.endsWith(".kt")) { // kt 77 | handleKtSrcFileLayout(file, set) 78 | } 79 | } 80 | } 81 | } 82 | 83 | private fun handleKtSrcFileLayout(file: File, set: Set) { 84 | val regex = "(synthetic.main\\.)(\\w+)(\\.)" 85 | val fileContent = file.readText() 86 | val sb = StringBuffer() 87 | val matcher = Regex(regex).toPattern().matcher(fileContent) 88 | while (matcher.find()) { 89 | val oldName = matcher.group(2) 90 | if (set.contains(oldName)) { 91 | val newName = config.getNewName(oldName) 92 | matcher.appendReplacement(sb, "\$1$newName\$3") // 拼接 保留$1$3分组,替换$2分组 93 | } 94 | } 95 | 96 | // 修改了文件时,才写入文件 97 | if (sb.isNotEmpty()) { 98 | matcher.appendTail(sb) // 添加结尾 99 | file.writeText(sb.toString()) // 写回文件 100 | } 101 | 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/MenuReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.* 8 | 9 | /** 10 | * Menu 文件夹 11 | * @menu/xxx 暂时没有,所以 res 先不处理 12 | * Created by better On 2019-07-07. 13 | */ 14 | class MenuReplace(config: ResToolsConfig) : BaseFolderResReplace(config) { 15 | 16 | /** 17 | * 资源名称 18 | */ 19 | private val RES_TYPE_NAME = "menu" 20 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) } 21 | 22 | override val resTypeName: String = RES_TYPE_NAME 23 | override val javaRegex: String = """(R(\s*?)\.(\s*?)menu(\s*?)\.(\s*?))(\w+)""" 24 | override val xmlRegex: String = """(@menu/)(\w+)""" 25 | 26 | override fun getResNameSet(): Set { 27 | val layoutNameSet = HashSet() 28 | // 1.获取所有layout开头的文件夹,并获取下面的所有的文件 29 | resDir.listFiles(DIR_FILTER)?.forEach { dir -> 30 | dir.listFiles().forEach { file -> 31 | layoutNameSet.add(file.name.substring(0, file.name.lastIndexOf("."))) 32 | } 33 | } 34 | return layoutNameSet 35 | } 36 | 37 | override fun replaceSrc(resNameSet: Set, regex: String) { 38 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 39 | replaceSrcDir(srcDir, resNameSet, javaRegex) 40 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 41 | } 42 | 43 | override fun replaceRes(resNameSet: Set, regex: String) { 44 | // 1.替换文件内容 45 | debug("---------- $RES_TYPE_NAME ----- $RES_TYPE_NAME not used in the res folder now, so replace res not implemented.") 46 | 47 | // 2.修改文件名 48 | debug("---------- $RES_TYPE_NAME ----- rename start...") 49 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 50 | debug("---------- $RES_TYPE_NAME ----- rename end") 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/MipmapReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.* 8 | 9 | /** 10 | * mipmap 文件夹 11 | * Created by better On 2019-07-07. 12 | */ 13 | class MipmapReplace(config: ResToolsConfig) : BaseFolderResReplace(config) { 14 | 15 | /** 16 | * 资源名称 17 | */ 18 | private val RES_TYPE_NAME = "mipmap" 19 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) } 20 | 21 | override val resTypeName: String = RES_TYPE_NAME 22 | override val javaRegex: String = """(R(\s*?)\.(\s*?)mipmap(\s*?)\.(\s*?))(\w+)""" 23 | override val xmlRegex: String = """(@mipmap/)(\w+)""" 24 | 25 | override fun getResNameSet(): Set { 26 | val drawableNameSet = HashSet() 27 | resDir.listFiles(DIR_FILTER)?.forEach {dir-> 28 | dir.listFiles().forEach { file-> 29 | var suffixName = file.name.substringBeforeLast(".") 30 | if (suffixName.endsWith(".9")) { 31 | suffixName = suffixName.substringBeforeLast(".") 32 | } 33 | drawableNameSet.add(suffixName) 34 | } 35 | } 36 | 37 | return drawableNameSet 38 | } 39 | 40 | override fun replaceSrc(resNameSet: Set, regex: String) { 41 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 42 | replaceSrcDir(srcDir, resNameSet, javaRegex) 43 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 44 | } 45 | 46 | override fun replaceRes(resNameSet: Set, regex: String) { 47 | // 1.替换文件内容 48 | debug("---------- $RES_TYPE_NAME ----- replace res folder start...") 49 | replaceResDir(resDir, resNameSet, xmlRegex, null) 50 | debug("---------- $RES_TYPE_NAME ----- replace res folder end") 51 | 52 | // 2.修改文件名 53 | debug("---------- $RES_TYPE_NAME ----- rename start...") 54 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 55 | debug("---------- $RES_TYPE_NAME ----- rename end") 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/RawReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.* 8 | 9 | /** 10 | * raw 11 | * @raw/xxx 暂时没有,所以res先不处理 12 | * Created by better On 2019-07-07. 13 | */ 14 | class RawReplace(config: ResToolsConfig) : BaseFolderResReplace(config) { 15 | 16 | /** 17 | * 资源名称 18 | */ 19 | private val RES_TYPE_NAME = "raw" 20 | /** 21 | * 需要过滤掉 animator 22 | */ 23 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) && !s.startsWith("animator") } 24 | 25 | override val resTypeName: String = RES_TYPE_NAME 26 | override val javaRegex: String = """(R(\s*?)\.(\s*?)raw(\s*?)\.(\s*?))(\w+)""" 27 | override val xmlRegex: String = """(@raw/)(\w+)""" 28 | 29 | /** 30 | * 过滤掉 animator 的文件夹 31 | * 获取 res/anim/下的所有文件,并将文件转换成集合 32 | * 如:[res/anim/better.xml,res/anim/cz.xml] 返回 [better, cz] 33 | */ 34 | override fun getResNameSet(): Set { 35 | val layoutNameSet = HashSet() 36 | // 1.获取所有layout开头的文件夹,并获取下面的所有的文件 37 | resDir.listFiles(DIR_FILTER)?.forEach { dir -> 38 | dir.listFiles().forEach { file -> 39 | layoutNameSet.add(file.name.substring(0, file.name.lastIndexOf("."))) 40 | } 41 | } 42 | return layoutNameSet 43 | } 44 | 45 | override fun replaceSrc(resNameSet: Set, regex: String) { 46 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 47 | replaceSrcDir(srcDir, resNameSet, javaRegex) 48 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 49 | } 50 | 51 | override fun replaceRes(resNameSet: Set, regex: String) { 52 | // 1.替换文件内容 53 | debug("---------- $RES_TYPE_NAME ----- $RES_TYPE_NAME not used in the res folder now, so replace res not implemented.") 54 | 55 | // 2.修改文件名 56 | debug("---------- $RES_TYPE_NAME ----- rename start...") 57 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 58 | debug("---------- $RES_TYPE_NAME ----- rename end") 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/folder/XmlReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.folder 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseFolderResReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.* 8 | 9 | /** 10 | * xml 11 | * @raw/xml 暂时没有,所以res先不处理 12 | * Created by better On 2019-07-07. 13 | */ 14 | class XmlReplace(config: ResToolsConfig) : BaseFolderResReplace(config) { 15 | 16 | /** 17 | * 资源名称 18 | */ 19 | private val RES_TYPE_NAME = "xml" 20 | /** 21 | * 需要过滤掉 animator 22 | */ 23 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith(RES_TYPE_NAME) && !s.startsWith("animator") } 24 | 25 | override val resTypeName: String = RES_TYPE_NAME 26 | override val javaRegex: String = """(R(\s*?)\.(\s*?)xml(\s*?)\.(\s*?))(\w+)""" 27 | override val xmlRegex: String = """(@xml/)(\w+)""" 28 | 29 | /** 30 | * 过滤掉 animator 的文件夹 31 | * 获取 res/anim/下的所有文件,并将文件转换成集合 32 | * 如:[res/anim/better.xml,res/anim/cz.xml] 返回 [better, cz] 33 | */ 34 | override fun getResNameSet(): Set { 35 | val layoutNameSet = HashSet() 36 | // 1.获取所有layout开头的文件夹,并获取下面的所有的文件 37 | resDir.listFiles(DIR_FILTER)?.forEach { dir -> 38 | dir.listFiles().forEach { file -> 39 | layoutNameSet.add(file.name.substring(0, file.name.lastIndexOf("."))) 40 | } 41 | } 42 | return layoutNameSet 43 | } 44 | 45 | override fun replaceSrc(resNameSet: Set, regex: String) { 46 | debug("---------- $RES_TYPE_NAME ----- replace source folder start...") 47 | replaceSrcDir(srcDir, resNameSet, javaRegex) 48 | debug("---------- $RES_TYPE_NAME ----- replace source folder end") 49 | } 50 | 51 | override fun replaceRes(resNameSet: Set, regex: String) { 52 | // 1.替换文件内容 53 | debug("---------- $RES_TYPE_NAME ----- $RES_TYPE_NAME not used in the res folder now, so replace res not implemented.") 54 | 55 | // 2.修改文件名 56 | debug("---------- $RES_TYPE_NAME ----- rename start...") 57 | renameFile(resDir, resNameSet, DIR_FILTER, RES_TYPE_NAME) 58 | debug("---------- $RES_TYPE_NAME ----- rename end") 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /ResourceTools/src/main/kotlin/com/github/better/android/tools/values/ValuesReplace.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.android.tools.values 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.base.BaseReplace 5 | import com.github.better.android.tools.debug 6 | import java.io.FilenameFilter 7 | import java.util.LinkedHashSet 8 | 9 | /** 10 | * values (包括 values-xx)下的 11 | - string 12 | - string_arrays 13 | - color 14 | - dimens 15 | - bool 16 | - integer 17 | - attr not support 18 | - style not support well need repaired 19 | * Created by better On 2019-07-07. 20 | */ 21 | class ValuesReplace(config: ResToolsConfig) : BaseReplace(config) { 22 | private val DIR_FILTER = FilenameFilter { file, s -> file.isDirectory && s.startsWith("values") } 23 | 24 | companion object { 25 | val ALL_VALUES_TYPES = LinkedHashSet().apply { 26 | this.add(ValuesType.string) 27 | this.add(ValuesType.string_arrays) 28 | this.add(ValuesType.color) 29 | this.add(ValuesType.bool) 30 | this.add(ValuesType.integer) 31 | this.add(ValuesType.dimens) 32 | 33 | // not support now 34 | this.add(ValuesType.attr) 35 | this.add(ValuesType.style) 36 | } 37 | } 38 | 39 | /** 40 | * 替换values下的资源名称 41 | */ 42 | fun replaceValues(set: Set) { 43 | set.forEach { 44 | when (it) { 45 | ValuesType.string -> strings(it) 46 | ValuesType.string_arrays -> arrays(it) 47 | ValuesType.color -> color(it) 48 | ValuesType.dimens -> dimens(it) 49 | ValuesType.bool -> bool(it) 50 | ValuesType.integer -> integer(it) 51 | 52 | ValuesType.style -> style(it) 53 | ValuesType.attr -> attr(it) 54 | } 55 | } 56 | } 57 | 58 | // 字符串 59 | private fun strings(valueType: ValuesType) { 60 | debug("------------ replace strings resource (处理strings资源)") 61 | val stringNameSet = getValueNameSet(valueType.xml_Regx) 62 | // 修改源码中的 63 | replaceSrcDir(srcDir, stringNameSet, valueType.java_Regx) 64 | // 修改xml中的名称 65 | replaceResDir(resDir, stringNameSet, valueType.xml_Regx, null, true) 66 | // 修改xml中的引用 67 | replaceResDir(resDir, stringNameSet, valueType.xml_ref_regex, null) 68 | } 69 | 70 | // string-array 71 | private fun arrays(valueType: ValuesType) { 72 | debug("------------ replace string-array resource (处理 string-array 资源)") 73 | val nameSet = getValueNameSet(valueType.xml_Regx) 74 | // 修改源码中的 75 | replaceSrcDir(srcDir, nameSet, valueType.java_Regx) 76 | // 修改xml中的名称 77 | replaceResDir(resDir, nameSet, valueType.xml_Regx, null, true) 78 | } 79 | 80 | // color 81 | private fun color(valueType: ValuesType) { 82 | debug("------------ replace color resource (处理 color 资源)") 83 | val nameSet = getValueNameSet(valueType.xml_Regx) 84 | replaceSrcDir(srcDir, nameSet, valueType.java_Regx) 85 | // 修改xml中的名称 86 | replaceResDir(resDir, nameSet, valueType.xml_Regx, null, true) 87 | // 修改xml中的引用 88 | replaceResDir(resDir, nameSet, valueType.xml_ref_regex, null) 89 | } 90 | 91 | // dimens 92 | private fun dimens(valueType: ValuesType) { 93 | debug("------------ replace dimens resource (处理 dimens 资源") 94 | val nameSet = getValueNameSet(valueType.xml_Regx) 95 | 96 | replaceSrcDir(srcDir, nameSet, valueType.java_Regx) 97 | // 修改xml中的名称 98 | replaceResDir(resDir, nameSet, valueType.xml_Regx, null, true) 99 | // 修改xml中的引用 100 | replaceResDir(resDir, nameSet, valueType.xml_ref_regex, null) 101 | } 102 | 103 | // bool 104 | private fun bool(valueType: ValuesType) { 105 | debug("------------ replace bool resource (处理 bool 资源)") 106 | val nameSet = getValueNameSet(valueType.xml_Regx) 107 | 108 | replaceSrcDir(srcDir, nameSet, valueType.java_Regx) 109 | // 修改xml中的名称 110 | replaceResDir(resDir, nameSet, valueType.xml_Regx, null, true) 111 | // 修改xml中的引用 112 | replaceResDir(resDir, nameSet, valueType.xml_ref_regex, null) 113 | } 114 | 115 | // integer 116 | private fun integer(valueType: ValuesType) { 117 | debug("------------ replace integer resource (处理 integer 资源)") 118 | val nameSet = getValueNameSet(valueType.xml_Regx) 119 | 120 | replaceSrcDir(srcDir, nameSet, valueType.java_Regx) 121 | // 修改xml中的名称 122 | replaceResDir(resDir, nameSet, valueType.xml_Regx, null, true) 123 | // 修改xml中的引用 124 | replaceResDir(resDir, nameSet, valueType.xml_ref_regex, null) 125 | } 126 | 127 | private fun style(valueType: ValuesType) { 128 | debug("----> sorry style replace is not implement now ( 暂没有实现)") 129 | } 130 | 131 | private fun attr(valueType: ValuesType) { 132 | debug("----> sorry attr replace is not implements now (暂没有实现)") 133 | } 134 | 135 | 136 | /** 137 | * 读取xml资源,获取values资源名称 set 138 | * @param xmlRegex 139 | */ 140 | private fun getValueNameSet(xmlRegex: String): Set { 141 | val nameSet = HashSet() // 字符串 142 | // 1.获取所有values开头的文件夹 143 | resDir.listFiles(DIR_FILTER)?.forEach { dir -> 144 | val pattern = Regex(xmlRegex).toPattern() 145 | // 2.遍历文件夹下各个资源文件xml后缀,获取资源名称 146 | dir.listFiles().forEach { file -> 147 | if (file.name.endsWith(".xml")) { 148 | file.readLines().forEach { line -> 149 | val matcher = pattern.matcher(line) 150 | while (matcher.find()) { 151 | nameSet.add(matcher.group(2)) 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | return nameSet 159 | } 160 | 161 | 162 | /** values 文件下的资料类型 163 | */ 164 | enum class ValuesType(xmlRegex: String, javaRegex: String, xml_ref_regex: String) { 165 | 166 | /* 167 | appName 169 | appName 170 | */ 171 | 172 | string("()", 173 | "(R(\\s*?)\\.(\\s*?)string(\\s*?)\\.(\\s*?))(\\w+)", 174 | "(@string/)(\\w+)" 175 | ), 176 | 177 | string_arrays("()", 178 | "(R(\\s*?)\\.(\\s*?)array(\\s*?)\\.(\\s*?))(\\w+)", 179 | "()" 180 | ), 181 | 182 | color("()", 183 | "(R(\\s*?)\\.(\\s*?)color(\\s*?)\\.(\\s*?))(\\w+)", 184 | "(@color/)(\\w+)" 185 | ), 186 | 187 | dimens("()", 188 | "(R(\\s*?)\\.(\\s*?)dimen(\\s*?)\\.(\\s*?))(\\w+)", 189 | "(@dimen/)(\\w+)" 190 | ), 191 | 192 | bool("()", 193 | "(R(\\s*?)\\.(\\s*?)bool(\\s*?)\\.(\\s*?))(\\w+)", 194 | "(@bool/)(\\w+)" 195 | ), 196 | 197 | integer("()", 198 | "(R(\\s*?)\\.(\\s*?)integer(\\s*?)\\.(\\s*?))(\\w+)", 199 | "(@integer/)(\\w+)" 200 | ), 201 | 202 | attr("", 203 | "", 204 | "" 205 | ), 206 | 207 | style("()", 208 | "(R(\\s*?)\\.(\\s*?)style(\\s*?)\\.(\\s*?))(\\w+)", 209 | "(@style/)(.+?\\\")" 210 | ); 211 | 212 | /** 213 | * 源代码中 214 | * like: R.string.XXX 215 | */ 216 | val java_Regx: String 217 | /** 218 | * xml 中 219 | * like: XXX 220 | */ 221 | val xml_Regx: String 222 | /** 223 | * xml 引用中 224 | * like: @string/empty_message 225 | */ 226 | val xml_ref_regex: String 227 | 228 | init { 229 | this.java_Regx = javaRegex 230 | this.xml_Regx = xmlRegex 231 | this.xml_ref_regex = xml_ref_regex 232 | } 233 | } 234 | } 235 | 236 | -------------------------------------------------------------------------------- /ResourceTools/src/test/kotlin/better/Test1.kt: -------------------------------------------------------------------------------- 1 | package better 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * Created by better On 2019-07-07. 7 | */ 8 | class Test1 { 9 | @Test 10 | fun test1() { 11 | val fileName = "better.9.png" 12 | println(fileName.substring(0, fileName.lastIndexOf("."))) 13 | println(fileName.substringBeforeLast(".")) 14 | } 15 | } -------------------------------------------------------------------------------- /ResourceTools/src/test/kotlin/resTools/ResToolsTest.kt: -------------------------------------------------------------------------------- 1 | package resTools 2 | 3 | import com.github.better.android.tools.ResToolsConfig 4 | import com.github.better.android.tools.folder.* 5 | import com.github.better.android.tools.values.ValuesReplace 6 | import org.junit.Test 7 | 8 | 9 | /** 10 | * @author zhaoyu1 2019-07-08 11 | **/ 12 | class ResToolsTest { 13 | 14 | private fun doWork(config: ResToolsConfig) { 15 | // 1. layout 16 | val layoutReplace = LayoutReplace(config) 17 | layoutReplace.replaceThis() 18 | // 19 | // // 2. drawable 20 | val drawableReplace = DrawableReplace(config) 21 | drawableReplace.replaceThis() 22 | // // 3. color 23 | val colorReplace = ColorReplace(config) 24 | colorReplace.replaceThis() 25 | // // 4. Anim 26 | val anim = AnimReplace(config) 27 | anim.replaceThis() 28 | // // 5. menu 29 | val menuReplace = MenuReplace(config) 30 | menuReplace.replaceThis() 31 | // // 6. mipmap 32 | val mipmapReplace = MipmapReplace(config) 33 | mipmapReplace.replaceThis() 34 | // // 7. raw 35 | val rawReplace = RawReplace(config) 36 | rawReplace.replaceThis() 37 | // // 8. xml 38 | val xmlReplace = XmlReplace(config) 39 | xmlReplace.replaceThis() 40 | 41 | ////////////// all values types //////////////////// 42 | // === 9. values test not support attrs 43 | val valuesReplace = ValuesReplace(config) 44 | valuesReplace.replaceValues(ValuesReplace.ALL_VALUES_TYPES) 45 | } 46 | 47 | /** 48 | * 这里是测试类 49 | */ 50 | @Test 51 | fun test() { 52 | val config = ResToolsConfig( 53 | srcFolderPath = "/Users/zhaoyu1/Documents/github/AndroidResourceTools/app/src/main/java", 54 | resFolderPath = "/Users/zhaoyu1/Documents/github/AndroidResourceTools/app/src/main/res", 55 | manifestFilePath = "/Users/zhaoyu1/Documents/github/AndroidResourceTools/app/src/main/AndroidManifest.xml", 56 | oldPrefix = "better", 57 | newPrefix = "bb_") 58 | 59 | doWork(config) 60 | } 61 | 62 | /** 63 | * 中文测试 64 | */ 65 | @Test 66 | fun testLayout() { 67 | val config = ResToolsConfig( 68 | srcFolderPath = "/Users/zhaoyu1/Documents/github/AndroidResourceTools/app/src/main/java", 69 | resFolderPath = "/Users/zhaoyu1/Documents/github/AndroidResourceTools/app/src/main/res", 70 | manifestFilePath = "/Users/zhaoyu1/Documents/github/AndroidResourceTools/app/src/main/AndroidManifest.xml", 71 | oldPrefix = "better", 72 | newPrefix = "bb_") 73 | // 1. layout 74 | val layoutReplace = LayoutReplace(config) 75 | layoutReplace.replaceThis() 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | //apply plugin: 'plugin.resTools' 5 | 6 | android { 7 | compileSdkVersion 26 8 | defaultConfig { 9 | applicationId "com.github.better.restools" 10 | minSdkVersion 19 11 | targetSdkVersion 26 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_7 24 | targetCompatibility JavaVersion.VERSION_1_7 25 | } 26 | } 27 | 28 | //config 29 | //resConfig { 30 | // new_prefix = 'better_' // 资源前缀 31 | // old_prefix = '' // 老前缀,可为''空字符串 32 | // // === below use default 33 | // // resFolderPath 资源目录 34 | // // srcFolderPath 源代码目录 35 | // // manifestFilePath 清单文件目录 36 | //} 37 | 38 | dependencies { 39 | implementation fileTree(include: ['*.jar'], dir: 'libs') 40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 41 | implementation 'com.android.support:appcompat-v7:26.1.0' 42 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 43 | testImplementation 'junit:junit:4.12' 44 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 45 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 46 | } 47 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/better/restools/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.restools 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.github.better.restools", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/better/restools/demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.restools.demo 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import kotlinx.android.synthetic.main.better_better_activity_main.* 6 | 7 | /** 8 | * 测试中文问题 9 | */ 10 | class MainActivity : AppCompatActivity() { 11 | 12 | val 中 = "测试中文变量" 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContentView(R.layout.better_better_activity_main) 17 | 18 | btn_test.setOnClickListener { 19 | text1.setText(R.string.better_groovy) 20 | text1.setTextColor(resources.getColor(R.color.better_better_colorPrimary)) 21 | text1.setBackgroundResource(R.drawable.abc_btn_borderless_material) 22 | text1.setBackgroundResource(R.drawable.better_alertdialog_left_selector) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/anim/better_actionsheet_dialog_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/better_actionsheet_dialog_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/better_better_ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/better_alertdialog_left_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/better_better_ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/better_better_aaa.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/better_better_activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 24 | 25 | 32 | 33 | 39 | 40 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/better_better_b.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/better_better_ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaoyubetter/AndroidResourceTools/4513a5064a7252f0d9acebccc9751a30aea4405a/app/src/main/res/mipmap-xxhdpi/better_better_ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/better_better_ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaoyubetter/AndroidResourceTools/4513a5064a7252f0d9acebccc9751a30aea4405a/app/src/main/res/mipmap-xxhdpi/better_better_ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-en/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ResourceTools 4 | 5 | 6 | Java 7 | Kotlin 8 | Javascript 9 | Python 10 | Groovy 11 | Android 12 | iOS 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | #F48260 8 | #3ABE00 9 | #00EEEE 10 | #0000ff 11 | #ff9c00 12 | #169ce6 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16sp 4 | 18sp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ResourceTools 3 | 4 | 5 | Java 语言 6 | Kotlin 语言 7 | Javascript 语言 8 | Python 语言 9 | Groovy 语言 10 | Android 语言 11 | iOS 语言 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/github/better/restools/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.better.restools 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.11' 5 | repositories { 6 | google() 7 | jcenter() 8 | mavenLocal() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.1.3' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | classpath 'com.github.better.restools:ResTools:1.0.1' 17 | 18 | classpath 'com.novoda:bintray-release:0.8.0'//添加 19 | 20 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 21 | } 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | google() 27 | jcenter() 28 | } 29 | 30 | tasks.withType(Javadoc) { 31 | options.addStringOption('Xdoclint:none', '-quiet') 32 | options.addStringOption('encoding', 'UTF-8') 33 | } 34 | } 35 | 36 | task clean(type: Delete) { 37 | delete rootProject.buildDir 38 | } 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':ResTools', ':ResourceTools' 2 | --------------------------------------------------------------------------------