package utils; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import entity.IdNamingBean; public class AndroidUtils { public static void main(String[] args) { autoCreateAdapter(); autoCreateActivity(); autoCreateRecyclerAdapter(); } /** DIMEN单位 */ public static List<String> dimenUnits = new ArrayList<String>(); static { dimenUnits.add("px"); dimenUnits.add("sp"); dimenUnits.add("dip"); dimenUnits.add("dp"); } public static List<IdNamingBean> idNamingBeans = new ArrayList<IdNamingBean>(); /** * 递归获取layout中全部控件 * * @param layoutXml 布局文件的绝对路径,如xxx/res/layout/main.xml * @param include 是否将include引用的布局中内容也获取到 */ public static void parseElementFromXml(String layoutXml, boolean include) { Document document = XmlUtil.read(layoutXml); List<Element> docElements = XmlUtil.getAllElements(document); // 将view名称和对应的id名封装为一个实体类,并存至集合中 for (Element element : docElements) { Attribute attrID = element.attribute("id"); // 如果包含include并且需要获取其中内容则进行递归获取 if(element.getName().equals("include") && include) { Attribute attribute = element.attribute("layout"); // 原布局路径和include中的布局拼成新的路径 String includeLayoutXml = layoutXml.substring(0, layoutXml.lastIndexOf("\\") + 1) + attribute.getValue().substring(attribute.getValue().indexOf("/") + 1) + ".xml"; // 继续递归获取include的布局中控件 parseElementFromXml(includeLayoutXml, include); } // 保存有id的控件信息 if(attrID != null) { String value = attrID.getValue(); String idName = value.substring(value.indexOf("/") + 1); IdNamingBean bean = new IdNamingBean(element.getName(), idName, element); if(!idNamingBeans.contains(bean)) { idNamingBeans.add(bean); } } } } /** * 自动遍历xml中所有带id的控件,在activity文件中设置对应变量,变量名为id名<br> * <br> * 使用前需要将xml文件内容复制到本项目里的Android文件夹下的layout.xml文件中<br> * 运行该方法后,根据布局文件生成的相关代码会在Android文件夹下Activity.java中查看,可以复制到自己项目里使用 */ public static void autoCreateActivity() { // 获取layout中控件信息,信息会保存至idNamingBeans集合中 idNamingBeans.clear(); parseElementFromXml("Android" + File.separator + "layout.xml", false); // 解析idNamingBeans集合中的信息,生成页面文本信息 String activityContent = createActivityContent(); // 获取activity文件 File javaFile = new File("Android" + File.separator + "Activity.java"); // 将生成的内容写入至java类文件中 FileUtils.writeString2File(activityContent, javaFile, "utf-8"); } /** * 自动遍历xml中所有带id的控件,在activity文件中设置对应变量,变量名为id名 * * @param layoutXml 布局文件的绝对路径,如xxx/res/layout/main.xml * @param activityFile Activity类文件名,如xxx/src/.../MainActivity.java * @param include 是否将include引用的布局中内容也获取到 */ public static void autoCreateActivity(String layoutXml, String activityFile, boolean include) { // 获取layout中控件信息,信息会保存至idNamingBeans集合中 idNamingBeans.clear(); parseElementFromXml(layoutXml, include); // 解析idNamingBeans集合中的信息,生成页面文本信息 String activityContent = createActivityContent(); // 获取activity文件 File javaFile = new File(activityFile); // 读取java文件的字符串 String fileContent = FileUtils.readToString(javaFile); // 将生成的内容写入至java类文件内的起始端 fileContent = fileContent.replaceFirst("\\{", "\\{" + activityContent); FileUtils.writeString2File(fileContent, javaFile); } /** * 生成activity文件内容 */ public static String createActivityContent() { StringBuilder sb = new StringBuilder(); sb.append("\n\n"); // 根据view名-id名的实体类,依次生成控件对应的成员变量,变量名取id名称赋值 // private TextView tv_name; for(IdNamingBean bean : idNamingBeans) { sb.append(StringUtils.formatSingleLine(1, "private " + bean.getViewName() + " " + bean.getIdName() + ";")); } sb.append("\n"); // 生成initView自定义方法,并在其中依次findViewById为view成员变量赋值 // private void initView() { // tv_name = (TextView)findViewById(R.id.tv_name); // } sb.append(StringUtils.formatSingleLine(1, "private void initView() {")); for(IdNamingBean bean : idNamingBeans) { sb.append(StringUtils.formatSingleLine(2, bean.getIdName() + " = " + "(" + bean.getViewName() + ") findViewById(R.id." + bean.getIdName() + ");")); } sb.append("\n"); // 是否包含EditText控件,如果包含,自动生成非空判断代码 boolean hasEditText = false; ///** // * TODO 使用输入内容,可根据需要自行修改补充本方法 // */ //private void submit() { // // 开始验证输入内容 // String content = et_content.getText().toString().trim(); // if(!TextUtils.isEmpty(content)) { // Toast.makeText(this, "content不能为空", Toast.LENGTH_SHORT).show(); // return; // } // // // TODO 验证成功,下面开始使用数据 // // //} StringBuilder sbEditText = new StringBuilder(); sbEditText.append("\n"); sbEditText.append(StringUtils.formatSingleLine(1, "/**")); sbEditText.append(StringUtils.formatSingleLine(1, " * TODO 输入验证,可根据需要自行修改补充")); sbEditText.append(StringUtils.formatSingleLine(1, " */")); sbEditText.append(StringUtils.formatSingleLine(1, "private void submit() {")); sbEditText.append(StringUtils.formatSingleLine(2, "// 开始验证输入内容")); for(IdNamingBean bean : idNamingBeans) { Attribute attrTag = bean.getElement().attribute("tag"); // 只判断EditText控件 if(bean.getViewName().equals("EditText")) { // 带有可选标识(tag为optional)的EditText不做非空验证 if(attrTag != null && attrTag.getValue().equals("optional")) { continue; } // 截取最后一个_后面的内容作为名称,不包含_时使用全部id作为名称 String idName = bean.getIdName(); int index = idName.lastIndexOf("_"); String name = index == -1 ? idName : idName.substring(index + 1); sbEditText.append(StringUtils.formatSingleLine(2, "String " + name + " = " + idName + ".getText().toString().trim();")); sbEditText.append(StringUtils.formatSingleLine(2, "if(TextUtils.isEmpty(" + name + ")) {")); sbEditText.append(StringUtils.formatSingleLine(3, "Toast.makeText(this, \"" + name + "不能为空\", Toast.LENGTH_SHORT).show();")); sbEditText.append(StringUtils.formatSingleLine(3, "return;")); sbEditText.append(StringUtils.formatSingleLine(2, "}")); sbEditText.append(StringUtils.formatSingleLine(2, "")); hasEditText = true; } } sbEditText.append(StringUtils.formatSingleLine(2, "// TODO 验证成功,下面开始使用数据")); sbEditText.append(StringUtils.formatSingleLine(2, "")); sbEditText.append(StringUtils.formatSingleLine(2, "")); sbEditText.append(StringUtils.formatSingleLine(1, "}")); // 是否包含可点击的控件,如果包含,自动生成onClick相关代码 boolean hasClickView = false; // 点击事件复写的onclick方法 // @Override // public void onClick(View v) { // switch (v.getId()) { // case R.id.btn_ok: // // doSomething // break; // } // } StringBuilder sbOnClick = new StringBuilder(); sbOnClick.append("\n"); sbOnClick.append(StringUtils.formatSingleLine(1, "@Override")); sbOnClick.append(StringUtils.formatSingleLine(1, "public void onClick(View v) {")); sbOnClick.append(StringUtils.formatSingleLine(2, "switch (v.getId()) {")); for(IdNamingBean bean : idNamingBeans) { Attribute attrClickable = bean.getElement().attribute("clickable"); // 只设置Button的点击事件,和参数包含clickable=true的控件 if(bean.getViewName().equals("Button") || (attrClickable != null && attrClickable.getValue().equals("true"))) { // 设置监听 // btn_ok.setOnClickListener(this); sb.append(StringUtils.formatSingleLine(2, bean.getIdName() + ".setOnClickListener(this);")); // 在onclick中分别处理不同id的点击 sbOnClick.append(StringUtils.formatSingleLine(2, "case R.id." + bean.getIdName() + ":")); sbOnClick.append("\n"); sbOnClick.append(StringUtils.formatSingleLine(3, "break;")); hasClickView = true; } } sbOnClick.append(StringUtils.formatSingleLine(2, "}")); sbOnClick.append(StringUtils.formatSingleLine(1, "}")); // 是否包含RadioGroup/Button等控件,如果包含,自动生成onCheckChanged相关代码 boolean hasCheckedView = false; // 点击事件复写的onclick方法 // @Override // public void onCheckedChanged(RadioGroup group, int checkedId) { // switch (checkedId) { // case R.id.rb_home: // // doSomething // break; // } // } StringBuilder sbOnChecked = new StringBuilder(); sbOnChecked.append("\n"); sbOnChecked.append(StringUtils.formatSingleLine(1, "@Override")); sbOnChecked.append(StringUtils.formatSingleLine(1, "public void onCheckedChanged(RadioGroup group, int checkedId) {")); sbOnChecked.append(StringUtils.formatSingleLine(2, "switch (checkedId) {")); for(IdNamingBean bean : idNamingBeans) { // 只设置Button的点击事件,和参数包含clickable=true的控件 if(bean.getViewName().equals("RadioGroup")) { // 设置监听 // rg.setOnCheckedChangeListener(this); sb.append(StringUtils.formatSingleLine(2, bean.getIdName() + ".setOnCheckedChangeListener(this);")); hasCheckedView = true; } else if(bean.getViewName().equals("RadioButton")) { // 在onCheckedChanged中分别处理不同id的选中 sbOnChecked.append(StringUtils.formatSingleLine(2, "case R.id." + bean.getIdName() + ":")); sbOnChecked.append("\n"); sbOnChecked.append(StringUtils.formatSingleLine(3, "break;")); hasCheckedView = true; } } sbOnChecked.append(StringUtils.formatSingleLine(2, "}")); sbOnChecked.append(StringUtils.formatSingleLine(1, "}")); sb.append(StringUtils.formatSingleLine(1, "}\n")); sb.append("\n"); if(hasClickView) { sb.append(sbOnClick); } if(hasCheckedView) { sb.append(sbOnChecked); } if(hasEditText) { sb.append(sbEditText); } String activityContent = sb.toString(); return activityContent; } /** * 生成activity对应的测试文件内容(针对已经写好的页面代码生成) */ public static String createActivityContent4EspressoTest(String projectPath, String activityPath) { StringBuilder sb = new StringBuilder(); sb.append("\n\n"); File activityFile = new File(projectPath + activityPath); // 页面名 String activityName = FileUtils.getName(activityFile); // 页面内容 String activityContent = FileUtils.readToString(activityFile, "UTF-8"); // 布局内容元素 List<IdNamingBean> layoutElements; // setContentView(R.layout.activity_login); String setContentViewRegex = "setContentView\\(R.layout.([\\w_]+)\\);"; Pattern setContentViewPattern = Pattern.compile(setContentViewRegex); Matcher setContentViewMatcher = setContentViewPattern.matcher(activityContent); if(setContentViewMatcher.find()) { // projectPath = D:\PlayFun\HaveFun // layoutPath = projectPath + \app\src\main\res\layout String layoutPath = projectPath + "\\app\\src\\main\\res\\layout\\" + setContentViewMatcher.group(1) + ".xml"; parseElementFromXml(layoutPath, true); layoutElements = idNamingBeans; } else { throw new RuntimeException("页面必须要setContentView绑定布局"); } // 类名: 页面名+Test String className = activityName + "Test"; // @RunWith(AndroidJUnit4.class) // public class RegistActivityTest { // // @Rule // public ActivityTestRule<RegistActivity> mActivityRule = new ActivityTestRule<>(RegistActivity.class, true, false); sb.append(StringUtils.formatSingleLine(0, "@RunWith(AndroidJUnit4.class)")); sb.append(StringUtils.formatSingleLine(0, "public class "+className+" {")); sb.append("\n"); sb.append(StringUtils.formatSingleLine(1, "@Rule")); sb.append(StringUtils.formatSingleLine(1, "public ActivityTestRule<"+activityName+"> mActivityRule = new ActivityTestRule<>("+activityName+".class, true, false);")); sb.append("\n"); // @Test // public void test() { sb.append(StringUtils.formatSingleLine(1, "@Test")); sb.append(StringUtils.formatSingleLine(1, "public void test() {")); // 页面初始化 sb.append(StringUtils.formatSingleLine(2, "Intent intent = new Intent();")); // 判断页面初始化时是否有getExtra,如果有需要在测试代码中putExtra // userId = getIntent().getLongExtra("userId", 0); String getExtraRegex = ".get([\\w]+)Extra\\(\"([\\w_]+)\""; Pattern getExtraPattern = Pattern.compile(getExtraRegex); Matcher getExtraMatcher = getExtraPattern.matcher(activityContent); if(getExtraMatcher.find()) { // Intent intent = new Intent(); // intent.putExtra("userId", 1016l); // mActivityRule.launchActivity(intent); sb.append(StringUtils.formatSingleLine(2, "// 待测试页面需要Extra数据如下")); String type = getExtraMatcher.group(1); String key = getExtraMatcher.group(2); sb.append(StringUtils.formatSingleLine(2, "intent.putExtra(\""+key+"\", 添加"+type+"类型的值);")); } sb.append(StringUtils.formatSingleLine(2, "mActivityRule.launchActivity(intent);")); sb.append("\n"); // 用onView定位控件,并执行动作 // onView(withId(R.id.et_username)).perform(typeText("boredream"), closeSoftKeyboard()); for(IdNamingBean bean : layoutElements) { // 控件名 String viewName = bean.getViewName(); // 不同控件对应的操作 String perform = ""; // 一般自定义控件都会在名字里表明自己的类型,因此使用contains判断 if(viewName.contains("EditText")) { // EditText对应输入 perform = ".perform(typeText(输入测试内容), closeSoftKeyboard())"; } else if(viewName.contains("Button") || viewName.contains("CheckBox") ) { // Button/RadioButton/CheckBox对应点击 perform = ".perform(click())"; } else { // 无法判断的类型,不添加动作 } sb.append(StringUtils.formatSingleLine(2, "onView(withId(R.id."+bean.getIdName()+"))"+perform+";")); } // 最后验证部分,留给开发者自己根据业务处理,添加部分注释引导 // TODO 复制上面的onView部分定位控件,然后根据需要编写期望的check结果 // 示例: 比如需要验证dialog/toast是否显示可以如下(如果验证页面上控件则不需要.inRoot部分) // onView(withText("登录")) // .inRoot(withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView())))) // .check(matches(isDisplayed())); sb.append("\n"); sb.append(StringUtils.formatSingleLine(2, "// TODO 复制上面的onView部分定位控件,然后根据需要编写期望的check结果")); sb.append(StringUtils.formatSingleLine(2, "// 示例: 比如需要验证dialog/toast是否显示可以如下(如果验证页面上控件则不需要.inRoot部分)")); sb.append(StringUtils.formatSingleLine(2, "// onView(withText(\"请输入密码\"))")); sb.append(StringUtils.formatSingleLine(2, "// .inRoot(withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView()))))")); sb.append(StringUtils.formatSingleLine(2, "// .check(matches(isDisplayed()));")); sb.append("\n"); sb.append(StringUtils.formatSingleLine(1, "}")); sb.append(StringUtils.formatSingleLine(0, "}")); return sb.toString(); } /** * 自动遍历xml中所有带id的控件,在adapter文件中生成最基本的代码<br> * <br> * 使用前需要将xml文件内容复制到本项目里的Android文件夹下的item.xml文件中<br> * 运行该方法后,根据布局文件生成的相关代码会在Android文件夹下Adapter.java中查看,可以复制到自己项目里使用 */ public static void autoCreateAdapter() { String layoutXml = "Android" + File.separator + "item.xml"; // 获取layout中控件信息,信息会保存至idNamingBeans集合中 idNamingBeans.clear(); parseElementFromXml(layoutXml, false); // 解析idNamingBeans集合中的信息,生成适配器文本信息 String adapterContent = createAdapterContent(layoutXml); // 获取adapter文件 File javaFile = new File("Android" + File.separator + "Adapter.java"); // 将生成的内容写入至java类文件中 FileUtils.writeString2File(adapterContent, javaFile); } /** * 自动遍历xml中所有带id的控件,在adapter文件中生成最基本的代码 * * @param layoutXml item布局文件的绝对路径,如xxx/res/layout/item.xml * @param adapterFile Adapter类文件名,如xxx/src/.../MyAdapter.java * @param include 是否将include引用的布局中内容也获取到 */ public static void autoCreateAdapter(String layoutXml, String adapterFile, boolean include) { idNamingBeans.clear(); parseElementFromXml(layoutXml, include); String adapterContent = createAdapterContent(layoutXml); // 读取java文件的字符串 File javaFile = new File(adapterFile); String fileContent = FileUtils.readToString(javaFile); // 将生成的内容写入至java类文件内的起始端 fileContent = fileContent.replaceFirst("\\{", "\\{\n" + adapterContent); // 写入回文件 FileUtils.writeString2File(fileContent, javaFile); } /** * 生成adapter文件内容 */ private static String createAdapterContent(String layoutXml) { StringBuilder sbAdapterInfo = new StringBuilder(); sbAdapterInfo.append("\n"); // 成员变量,只设置最基本的集合类和context sbAdapterInfo.append(StringUtils.formatSingleLine(1, "private Context context;")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "// TODO change the MyItem class to your data bean class")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "private List<MyItem> datas;")); sbAdapterInfo.append("\n"); // 根据成员变量创建的构造函数 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public MyAdapter(Context context, List<MyItem> datas) {")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "this.context = context;")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "this.datas = datas;")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); sbAdapterInfo.append("\n"); // 重写getCount方法 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "@Override")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public int getItemCount() {")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "return datas.size();")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); sbAdapterInfo.append("\n"); // ViewHolder class的申明处理 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public static class ViewHolder{")); for(IdNamingBean bean : idNamingBeans) { // public TextView item_tv; sbAdapterInfo.append("\t\t") .append("public ") .append(bean.getViewName()) .append(" ") .append(bean.getIdName()) .append(";\n"); } sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); // 重写getView方法,并进行优化处理 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "@Override")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public View getView(int position, View convertView, ViewGroup parent) {")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "ViewHolder holder;")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "if(convertView == null) {")); sbAdapterInfo.append(StringUtils.formatSingleLine(3, "holder = new ViewHolder();")); sbAdapterInfo.append(StringUtils.formatSingleLine(3, "convertView = View.inflate(context, R.layout."+FileUtils.getName(layoutXml)+", null);")); // getView中viewholder的变量赋值处理 // 根据view名-id名的实体类,依次生成控件对应的holder变量,变量名取id名称赋值 for(IdNamingBean bean : idNamingBeans) { // holder.item_tv = (TextView) convertView.findViewById(R.id.item_tv); sbAdapterInfo.append("\t\t\t") .append("holder.") .append(bean.getIdName()) .append(" = (") .append(bean.getViewName()) .append(") ") .append("convertView.findViewById(R.id.") .append(bean.getIdName()) .append(");\n"); } sbAdapterInfo.append(StringUtils.formatSingleLine(3, "convertView.setTag(holder);")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "} else {")); sbAdapterInfo.append(StringUtils.formatSingleLine(3, "holder = (ViewHolder) convertView.getTag();")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "}")); sbAdapterInfo.append("\n"); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "// TODO set data")); sbAdapterInfo.append("\n"); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "return convertView;")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); sbAdapterInfo.append("\n"); sbAdapterInfo.append("\n"); return sbAdapterInfo.toString(); } /** * 自动遍历xml中所有带id的控件,在adapter文件中生成最基本的代码<br> * <br> * 使用前需要将xml文件内容复制到本项目里的Android文件夹下的item.xml文件中<br> * 运行该方法后,根据布局文件生成的相关代码会在Android文件夹下Adapter.java中查看,可以复制到自己项目里使用 */ public static void autoCreateRecyclerAdapter() { String layoutXml = "Android" + File.separator + "item_recyclerview.xml"; // 获取layout中控件信息,信息会保存至idNamingBeans集合中 idNamingBeans.clear(); parseElementFromXml(layoutXml, false); // 解析idNamingBeans集合中的信息,生成适配器文本信息 String adapterContent = createRecyclerAdapterContent(layoutXml); // 获取adapter文件 File javaFile = new File("Android" + File.separator + "RecyclerAdapter.java"); // 将生成的内容写入至java类文件中 FileUtils.writeString2File(adapterContent, javaFile); } /** * 生成RecyclerView的adapter文件内容 */ private static String createRecyclerAdapterContent(String layoutXml) { StringBuilder sbAdapterInfo = new StringBuilder(); sbAdapterInfo.append("\n"); // 成员变量,只设置最基本的集合类和context sbAdapterInfo.append(StringUtils.formatSingleLine(1, "private Context context;")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "private List<Object> datas;")); sbAdapterInfo.append("\n"); // 根据成员变量创建的构造函数 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public MyAdapter(Context context, List<Object> datas) {")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "this.context = context;")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "this.datas = datas;")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); sbAdapterInfo.append("\n"); // 重写getItemCount方法 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "@Override")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public int getItemCount() {")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "return datas.size();")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); sbAdapterInfo.append("\n"); // ViewHolder class的申明处理 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public static class ViewHolder extends RecyclerView.ViewHolder {")); for(IdNamingBean bean : idNamingBeans) { // public TextView item_tv; sbAdapterInfo.append("\t\t") .append("public ") .append(bean.getViewName()) .append(" ") .append(bean.getIdName()) .append(";\n"); } sbAdapterInfo.append(StringUtils.formatSingleLine(2, "public ViewHolder(final View itemView) {")); sbAdapterInfo.append(StringUtils.formatSingleLine(3, "super(itemView);")); for (IdNamingBean bean : idNamingBeans) { // public TextView item_tv; sbAdapterInfo.append("\t\t\t") .append(bean.getIdName()) .append(" = (") .append(bean.getViewName()) .append(") ") .append("itemView.findViewById(R.id.") .append(bean.getIdName()) .append(");\n"); } sbAdapterInfo.append(StringUtils.formatSingleLine(2, "}")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); sbAdapterInfo.append("\n"); // 重写onCreateViewHolder方法 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "@Override")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public CourseAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "View v = LayoutInflater.from(context).inflate(R.layout.item_xx, parent, false);")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "return new ViewHolder(v);")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); sbAdapterInfo.append("\n"); // 重写onBindViewHolder方法 sbAdapterInfo.append(StringUtils.formatSingleLine(1, "@Override")); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "public void onBindViewHolder(ViewHolder holder, int position) {")); sbAdapterInfo.append(StringUtils.formatSingleLine(2, "final Object data = datas.get(position);")); sbAdapterInfo.append("\n"); sbAdapterInfo.append("\n"); sbAdapterInfo.append(StringUtils.formatSingleLine(1, "}")); sbAdapterInfo.append("\n"); return sbAdapterInfo.toString(); } /** * 批量修改图片sel文件名,复制的方式拷贝到同级目录下的sel文件夹中 * <br>修改成功后请再使用batchCreateSelFiles方法生成对应的sel.xml文件 * * @param normalPath normal普通状态图片的文件夹 * @param specialPath 特殊状态(pressed按下/checked选中)图片的文件夹 * @param pre 需要添加的前缀 * @param end 特殊状态(pressed按下/checked选中)后缀名 */ public static void batchRenameSelFiles(String normalPath, String specialPath, String pre, String end) { String savePath = normalPath.substring(0, normalPath.lastIndexOf("\\")); File saveDir = new File(savePath, "sel"); if(!saveDir.exists()) { saveDir.mkdirs(); } List<File> files = FileUtils.getAllFiles(normalPath); for(File file : files) { String fileName = pre + "_" + file.getName(); fileName = fileName.replace(" ", "_").toLowerCase(Locale.CHINESE); File nFile = new File(saveDir, fileName); // file.renameTo(nFile); // rename 直接移动文件,我们希望是复制并重命名 FileUtils.copyFileByChannel(file, nFile); } List<File> filePresses = FileUtils.getAllFiles(specialPath); for(File file : filePresses) { String[] nameMap = FileUtils.getNameMap(file); String fileName = pre + "_" + nameMap[0] + "_" + end + nameMap[1]; fileName = fileName.replace(" ", "_").toLowerCase(Locale.CHINESE); File nFile = new File(saveDir, fileName); // file.renameTo(nFile); // rename 直接移动文件,我们希望是复制并重命名 FileUtils.copyFileByChannel(file, nFile); } } /** * 批量生成sel的xml文件 * <br>比如图片名字是ic_img.png和按下的ic_img_pressed.png,那么最终生成的就是封装好的ic_img_sel.xml * * @param path 包含全部图片的文件路径 * @param end 特殊状态(pressed按下/checked选中)后缀名 */ public static void batchCreateSelFiles(String path, String end) { List<File> files = FileUtils.getAllFiles(path); for(File file : files) { String fileName = FileUtils.getName(file); // 用normal状态的图片名生成对应的_sel.xml文件 if(!fileName.endsWith(end)) { Document doc = createSelector(fileName, fileName + "_" + end, end); // fileName + "_" + end File nFile = new File(path, fileName + "_sel.xml"); XmlUtil.write2xml(nFile, doc); } } } /** * 自动创建实心矩形sel,包括普通和按下状态的形状和总的sel,生成的xml文件保存在temp文件夹中 * * @param normalColorName 普通状态时的填充颜色 * @param specialColorName 特殊状态时的填充颜色 * @param cornersRadius 矩形圆角半径,需要带单位,比如4dp * @param end 特殊状态(pressed按下/checked选中)后缀名 */ public static void createShapeSel(String normalColorName, String specialColorName, String cornersRadius, String end) { String shape = "rectangle"; Document normalShapeDoc = createShape(shape, cornersRadius, null, null, "@color/" + normalColorName); Document specialShapeDoc = createShape(shape, cornersRadius, null, null, "@color/" + specialColorName); String normalShapeName = "correct_" + normalColorName; String specialShapeName = "correct_" + specialColorName; String path = "temp"; File normalShapeFile = new File(path, normalShapeName + ".xml"); File specialShapeFile = new File(path, specialShapeName + ".xml"); XmlUtil.write2xml(normalShapeFile, normalShapeDoc); XmlUtil.write2xml(specialShapeFile, specialShapeDoc); Document selDoc = AndroidUtils.createSelector(normalShapeName, specialShapeName, "pressed"); File selFile = new File(path, shape + "_" + normalColorName + "2" + specialColorName + "_sel.xml"); XmlUtil.write2xml(selFile, selDoc); } /** * 自动生成shape(只考虑shape,corner,stroke,solid参数) * * @param shape 形状,一共有四种:rectangle矩形,oval圆,line线,ring环 * @param cornersRadius 圆角边半径,需要带单位,比如4dp * @param strokeWidth 边线宽度,需要带单位,比如1px(stroke为可选,不要边线时传入空即可) * @param strokeColor 边线颜色(可以是#ARGB或者是@color/颜色名) * @param solidColor 填充颜色(可以是#ARGB或者是@color/颜色名) * @return */ public static Document createShape(String shape, String cornersRadius, String strokeWidth, String strokeColor, String solidColor) { // <?xml version="1.0" encoding="utf-8"?> // <shape xmlns:android="http://schemas.android.com/apk/res/android" // android:shape="rectangle" > // // <corners android:radius="4dp" /> // // <stroke // android:width="1px" // android:color="@color/white" /> // // <solid android:color="@color/transparent" /> // // </shape> Document doc = XmlUtil.read("res\\drawable\\shape_correct.xml"); Element rootElement = doc.getRootElement(); rootElement.attribute("shape").setValue(shape); Element cornerElement = rootElement.element("corners"); cornerElement.attribute("radius").setValue(cornersRadius); Element strokeElement = rootElement.element("stroke"); if(strokeWidth == null || strokeWidth.length() == 0) { rootElement.remove(strokeElement); } else { strokeElement.attribute("width").setValue(strokeWidth); strokeElement.attribute("color").setValue(strokeColor); } Element solidElement = rootElement.element("solid"); solidElement.attribute("color").setValue(solidColor); return doc; } /** * 生成sel的document * * @param normalName normal普通状态的图片名 * @param specialName 特殊状态(pressed按下/checked选中)的图片名 * @param end 特殊状态(pressed按下/checked选中)后缀名 * @return */ public static Document createSelector(String normalName, String specialName, String end) { Document doc = XmlUtil.read("res\\drawable\\sel.xml"); Element rootElement = doc.getRootElement(); List<Element> elements = XmlUtil.getAllElements(doc); for(Element element : elements) { Attribute attr = element.attribute("drawable"); if(attr == null) { continue; } String value = attr.getStringValue(); if(value.contains(end)) { // 替换特殊状态(pressed/checked)的item加后缀 value = value.replace(end, specialName); attr.setValue(value); } else if(element.attributeCount() > 1){ // 移除不需要的element rootElement.remove(element); } else { // normal状态的item不加后缀 value = value.replace("normal", normalName); attr.setValue(value); } } return doc; } /** * 将参数值抽取到values文件夹下 * <p>如textColor="#ff00aa",将#ff00aa这样的具体值替换为@color/colorname * <br>并在color.xml文件内创建一个对应颜色item * * @param proPath 项目绝对路径 * @param valuesXml values文件名称,如strings.xml dimens.xml等 * @param type 抽取内容的前缀,如@color/,则type参数就输入"color" * @param itemName values文件内item的名称 * @param itemAttrName values文件内item的参数,一般都是name * @param itemAttrValue values文件内item的参数值,也是抽取值后替换的名称 * @param itemValue values文件内item的值,即替换后的值 * @param values 被替换的内容,支持模糊替换,只要匹配集合里中其中一个就会被抽取替换,最终抽取成一个值itemValue * @param matchAttr 匹配参数名,即只会替换该参数名对应的值 */ @SuppressWarnings("unchecked") public static void extract2values(String proPath, String valuesXml, String type, String itemName, String itemAttrName, String itemAttrValue, String itemValue, List<String> values, String matchAttr) { List<File> files = FileUtils.getAllFiles(new File(proPath)); String valuesPath = proPath + "/res/values/" + valuesXml; File valuesFile = new File(valuesPath); if(!valuesFile.exists()) { System.out.println("文件不存在,请确定文件["+valuesXml+"]位于/res/values/文件夹下,且文件名称正确"); return; } int extractFileCount = 0; for(File file : files) { if(!file.getName().endsWith(".xml")) { continue; } if(file.getName().equals(valuesXml)) { continue; } Document tarDoc = XmlUtil.read(file); // 是否有替换操作 boolean isReplace = XmlUtil.replaceAttrValue(tarDoc, values, "@"+type+"/"+itemAttrValue, matchAttr); if(!isReplace) { continue; } XmlUtil.write2xml(file, tarDoc); Document valuesDoc = XmlUtil.read(valuesFile); Element rootElement = valuesDoc.getRootElement(); List<Element> elements = rootElement.elements(); // 是否在values/xx.xml对应文件下下已有某个抽取过的值 boolean hasInValues = false; for(Element element : elements) { String attrValue = element.attributeValue(itemAttrName); if(attrValue.equals(itemAttrValue)) { hasInValues = true; break; } } if(!hasInValues) { Element element = rootElement.addElement(itemName); element.addAttribute(itemAttrName, itemAttrValue); element.setText(itemValue); XmlUtil.write2xml(valuesFile, valuesDoc); } extractFileCount ++; } System.out.println("将" + values + "抽取为[" + valuesXml + "]文件下的[" + itemAttrValue + "=" + itemValue + "]"); System.out.println("共抽取了" + extractFileCount + "个文件下的内容"); System.out.println("-------------------------"); } /** * 将参数值抽取到values文件夹下 * <p>如textColor="#ff00aa",将#ff00aa这样的具体值替换为@color/colorname * <br>并在color.xml文件内创建一个对应颜色item * * @param proPath 项目绝对路径 * @param valuesXml values文件名称,如strings.xml dimens.xml等 * @param type 抽取内容的前缀,如@color/,则type参数就输入"color" * @param itemName values文件内item的名称 * @param itemAttrName values文件内item的参数,一般都是name * @param itemAttrValue values文件内item的参数值,也是抽取值后替换的名称 * @param itemValue values文件内item的值,即替换后的值 * @param values 被替换的内容,支持模糊替换,只要匹配集合里中其中一个就会被抽取替换,最终抽取成一个值itemValue * @param matchAttr 匹配参数名,即只会替换该参数名对应的值 */ @SuppressWarnings("unchecked") public static void extract2valuesByJsoup(String proPath, String valuesXml, String type, String itemName, String itemAttrName, String itemAttrValue, String itemValue, List<String> values, String matchAttr) { List<File> files = FileUtils.getAllFiles(new File(proPath)); String valuesPath = proPath + "/res/values/" + valuesXml; File valuesFile = new File(valuesPath); if(!valuesFile.exists()) { System.out.println("文件不存在,请确定文件["+valuesXml+"]位于/res/values/文件夹下,且文件名称正确"); return; } int extractFileCount = 0; for(File file : files) { if(!file.getName().endsWith(".xml")) { continue; } if(file.getName().equals(valuesXml)) { continue; } Document tarDoc = XmlUtil.read(file); // 是否有替换操作 boolean isReplace = XmlUtil.replaceAttrValue(tarDoc, values, "@"+type+"/"+itemAttrValue, matchAttr); if(!isReplace) { continue; } XmlUtil.write2xml(file, tarDoc); Document valuesDoc = XmlUtil.read(valuesFile); Element rootElement = valuesDoc.getRootElement(); List<Element> elements = rootElement.elements(); // 是否在values/xx.xml对应文件下下已有某个抽取过的值 boolean hasInValues = false; for(Element element : elements) { String attrValue = element.attributeValue(itemAttrName); if(attrValue.equals(itemAttrValue)) { hasInValues = true; break; } } if(!hasInValues) { Element element = rootElement.addElement(itemName); element.addAttribute(itemAttrName, itemAttrValue); element.setText(itemValue); XmlUtil.write2xml(valuesFile, valuesDoc); } extractFileCount ++; } System.out.println("将" + values + "抽取为[" + valuesXml + "]文件下的[" + itemAttrValue + "=" + itemValue + "]"); System.out.println("共抽取了" + extractFileCount + "个文件下的内容"); System.out.println("-------------------------"); } /** * 抽取为dimen值 * * <p>一般抽取的都是size参数,如20sp,60px,500dp等,支持范围替换 * <br>例如:dimenValue=50sp,belowScope=2;aboveScope=4, * <br>则只要是48dp~54dp范围的值都会替换成dimenValue值 * * @param proPath 项目绝对路径 * @param dimenXml 保存dimen的xml文件名称,一般都是dimens.xml * @param dimenName dimens.xml文件内item的参数值,也是抽取值后替换的名称 * @param dimenValue dimens.xml文件内item的值,即抽取前值 * @param matchAttr 匹配参数名,即只会替换该参数名对应的值 */ public static void extract2Dimen(String proPath, String dimenXml, String dimenName, String dimenValue, String matchAttr) { // 浮点型单位值 String regex = "^(-?\\d+)(\\.\\d+)?(dp|dip|sp|px)$"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(dimenValue); System.out.println(matcher.matches()); int index = dimenUnits.indexOf(dimenValue.replaceAll("\\d+", "")); if(index == -1) { System.out.println("被抽取dimen值的单位不合法,必须为px/sp/dip/dp其中一种"); return; } List<String> values = new ArrayList<String>(); values.add(dimenValue); extract2values(proPath, dimenXml, "dimen", "dimen", "name", dimenName, dimenValue, values, matchAttr); } public static void extract2Color(String proPath, String colorXml, String colorName, String RGB) { List<String> values = new ArrayList<String>(); values.add(RGB); extract2values(proPath, colorXml, "color", "color", "name", colorName, RGB, values, ""); } public static void extract2String(String proPath, String stringXml, String stringName, String stringValue) { List<String> values = new ArrayList<String>(); values.add(stringValue); extract2values(proPath, stringXml, "string", "string", "name", stringName, stringValue, values, ""); } public static void relaceLayoutViewNames(String proPath, List<String> tarList, String replacement) { List<File> allFiles = FileUtils.getAllFiles(proPath); for(File file : allFiles) { // 如果是xml文件,且在layout..目录下,则视为布局文件 if(file.getName().endsWith(".xml") && file.getParentFile().getName().startsWith("layout")) { Document document = XmlUtil.read(file); boolean hasReplace = XmlUtil.replaceElementName(document, tarList, replacement); if(hasReplace) { XmlUtil.write2xml(file, document); } } } } /** * 删除无用的src文件 * * @param rootPath 根目录的绝对路径 */ public static void delNoUseSrcFile(String rootPath) { List<File> files = FileUtils.getAllFiles(rootPath); out: for (File file : files) { String name = file.getName(); // 只需要删除src包下的文件 if(!file.getPath().contains("\\src\\")) { continue; } for (File compareFile : files) { String compareName = compareFile.getName(); if(name.equals(compareName)) { // 自己跟自己不匹配 continue; } // 只需要对比src包和layout包下的文件 if(!compareFile.getPath().contains("\\src\\") && !compareFile.getPath().contains("\\layout")) { continue; } if(!compareFile.exists()) { continue; } // 如果对比文件的本文内容中包含文件名,则视为有使用 String fileContent = FileUtils.readToString(compareFile); if (fileContent.contains(FileUtils.getName(file))) { continue out; } } // 删除没使用过的文件 String absname = file.getAbsoluteFile().getName(); boolean delete = file.delete(); System.out.println(absname + " ... delete=" + delete); } } /** * 删除无用的布局xml文件 * * @param rootPath 根目录的绝对路径 */ public static void delNoUseLayoutFile(String rootPath) { List<File> files = FileUtils.getAllFiles(rootPath); out: for (File file : files) { String name = file.getName(); // 只需要删除layout包下的xml文件 if(!name.endsWith(".xml") || !file.getPath().contains("\\layout")) { continue; } for (File compareFile : files) { String compareName = compareFile.getName(); if(name.equals(compareName)) { // 自己跟自己不匹配 continue; } // 只需要对比src包下和layout包下文件 if(!compareFile.getPath().contains("\\layout") && !compareFile.getPath().contains("\\src\\")) { continue; } // 如果对比文件的本文内容中包含文件名,则视为有使用 String fileContent = FileUtils.readToString(compareFile, "UTF-8"); if (fileContent.contains(FileUtils.getName(file))) { continue out; } } // 删除没使用过的文件 String absname = file.getAbsoluteFile().getName()+""; boolean delete = file.delete(); System.out.println(absname + " ... delete=" + delete); } } }