小鸥有感上篇分享写太罗嗦, 这篇直接的, 来干货.
编写一个能实现Android布局xml自动转ViewHolder的插件, 我们需要
一个eclipse插件工程入口. 这里baidu,google. 可以参考EasyExploror插件, 下载下来反编译下.
小鸥也是第一次编写eclipse插件, 难度不大. 小鸥捣鼓这个自动生成ViewHolder的插件花了大半天. 好在最近好闲.
我们需要读取布局xml里的元素, View和它对应的id, 用的dom4j.
我们需要根据当前工程的AndroidManifest.xml, 读取package.
代码的自动生成, 简单的点, 就用freeMarker吧. 遵照Android规则, 把文件生成到gen文件夹吧.
上代码- -
如果我们有了工程的目录和当前选择的文件, 那么执行这个接口.
/** * @author zju_wjf * */ public interface IActionHandler { /** * @param projectDir * @param filePath */ public void handle(String projectDir, String filePath); }
上面接口的实现
public class XML2JFileHandler implements IActionHandler { private final static String AndroidManifest_Tag = "AndroidManifest.xml"; private final static String AndroidGen_Tag = "gen"; private final static String XML_Subfix_Tag = ".xml"; private final static String Java_Subfix_Tag = ".java"; private final IPlugLogger plugLogger; public XML2JFileHandler(IPlugLogger logger) { this.plugLogger = logger; } @Override public void handle(String projectDir, String filePath) { if (checkValid(projectDir, filePath)) { final String genPath = getGenPath(projectDir); final String mainPackage = getMainPackage(projectDir); final String layoutName = getLayoutName(filePath); final String className = getClassName(filePath); final File outputDir = new File(new File(genPath), mainPackage.replace(".", "//")); outputDir.mkdirs(); final File outFile = new File(outputDir, className + Java_Subfix_Tag); try { LayoutJFileWriter.writeJFile(new FileInputStream(filePath), new FileOutputStream(outFile), className, layoutName, mainPackage); plugLogger.log("Convert To ViewHolder Successfully!"); } catch (FileNotFoundException e) { e.printStackTrace(); plugLogger.log(e.getMessage()); } catch (IOException e) { e.printStackTrace(); plugLogger.log(e.getMessage()); } catch (TemplateException e) { e.printStackTrace(); plugLogger.log(e.getMessage()); } } else { plugLogger.log("Convert To ViewHolder Failed!"); } } private String getGenPath(String projectDir) { if (projectDir != null) { final File file = new File(new File(projectDir), AndroidGen_Tag); if (file.exists()) { return file.getAbsolutePath(); } } return null; } private String getLayoutName(final String filePath) { if (filePath != null) { final File file = new File(filePath); if (file.exists()) { final String name = file.getName(); return name.substring(0, name.length() - XML_Subfix_Tag.length()); } } return null; } private String getClassName(final String filePath) { final String layoutName = getLayoutName(filePath); return StringUtils.toUpperCase(layoutName, 0); } private String getMainPackage(String projectDir) { final String androidManifestPath = getAndroidManifest(projectDir); if (androidManifestPath != null) { /*** * <manifest * xmlns:android="http://schemas.android.com/apk/res/android" * package="com.yangfuhai.asimplecachedemo" android:versionCode="1" * android:versionName="1.0" > */ try { return LayoutXMLReader.readMainPackage(new FileInputStream(androidManifestPath)); } catch (FileNotFoundException e) { e.printStackTrace(); } } return null; } private boolean checkValid(final String projectDir, String filePath) { if (projectDir != null && filePath != null) { final File dir = new File(projectDir); final File file = new File(filePath); if (dir.isDirectory() && file.isFile()) { if (isAndroidProject(projectDir)) { if (filePath.endsWith(XML_Subfix_Tag) && filePath.startsWith(projectDir)) { return true; } } } } return false; } private String getAndroidManifest(final String projectDir) { if (projectDir != null) { final File file = new File(new File(projectDir), AndroidManifest_Tag); if (file.exists()) { return file.getAbsolutePath(); } } return null; } private boolean isAndroidProject(final String projectDir) { if (getAndroidManifest(projectDir) == null) { plugLogger.log("Not Found AndroidManifest!"); return false; } if (getMainPackage(projectDir) == null) { plugLogger.log("Not Found Main Package!"); return false; } if (getLayoutName(projectDir) == null) { plugLogger.log("Not A Vaild Layout XML!"); return false; } if (getClassName(projectDir) == null) { plugLogger.log("Not A Valid Class Name!"); return false; } if (getGenPath(projectDir) == null) { plugLogger.log("Not A Valid gen Directory!"); return false; } return true; } }
我们需要根据xml文件读取我们感兴趣的信息
/** * @author zju_wjf * */ public class LayoutXMLReader { private static final String Android_Id_Tag = "id"; private static final String Id_Start_Tag = "@+id/"; private static final String Package_Tag = "package"; /** * @param inputStream * @return */ public static String readMainPackage(final InputStream inputStream) { SAXReader saxReader = new SAXReader(); Document document = null; try { document = saxReader.read(inputStream); final Element element = document.getRootElement(); final String mainPackage = element.attributeValue(Package_Tag); return mainPackage; } catch (Exception e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } /** * @param inputStream * @return */ public static List<ViewPairBean> readLayoutXML(final InputStream inputStream) { final LinkedList<ViewPairBean> pairs = new LinkedList<ViewPairBean>(); SAXReader saxReader = new SAXReader(); Document document = null; try { document = saxReader.read(inputStream); final Element element = document.getRootElement(); readElement(element, pairs); } catch (Exception e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return pairs; } /** * 读取当前Element及其所有只节点的信息 * * @param element * @param pairs */ private final static void readElement(final Element element, final List<ViewPairBean> pairs) { if (element != null) { readElementOne(element, pairs); @SuppressWarnings("unchecked") List<Element> elements = element.elements(); for (Element childElement : elements) { readElement(childElement, pairs); } } } /** * 只读取当前Element信息 * * @param element * @param pairs */ private final static void readElementOne(final Element element, final List<ViewPairBean> pairs) { if (element != null) { final String id = element.attributeValue(Android_Id_Tag); if (id != null && id.startsWith(Id_Start_Tag)) { final ViewPairBean newPair = new ViewPairBean(); final String eleName = element.getName(); // 首字母大写 newPair.setViewType(StringUtils.toUpperCase(eleName, 0)); newPair.setViewId(id.substring(Id_Start_Tag.length())); if (newPair.getViewType() != null && newPair.getViewId() != null) { pairs.add(newPair); } } } } }
有了信息, 我们用freemarker生成java文件
public class LayoutJFileWriter { private final static String Package_Tag = "package"; private final static String Class_Tag = "class"; private final static String Layout_Tag = "layout"; private final static String ImportList_Tag = "importList"; private final static String PairList_Tag = "pairList"; private final static String TemplateDir = "template/"; private final static String TemplateFile = "ViewSetTemplate.tl"; public static void writeJFile(final InputStream layoutStream, final OutputStream jFielStream, final String className, final String layoutName, final String mainPackage) throws IOException, TemplateException { Configuration cfg = new Configuration(); cfg.setClassForTemplateLoading(LayoutJFileWriter.class, TemplateDir); cfg.setObjectWrapper(new DefaultObjectWrapper()); Template temp = cfg.getTemplate(TemplateFile); final String packageValue = mainPackage; final List<String> importList = new LinkedList<String>(); final String classValue = className; final List<ViewPairBean> pairList = LayoutXMLReader.readLayoutXML(layoutStream); final String layoutValue = layoutName; Map<String, Object> root = new HashMap<String, Object>(); root.put(Package_Tag, packageValue); root.put(Class_Tag, classValue); root.put(Layout_Tag, layoutValue); root.put(ImportList_Tag, importList); root.put(PairList_Tag, pairList); Writer out = new OutputStreamWriter(jFielStream); temp.process(root, out); out.flush(); out.close(); } }
我们的freeMarker模板文件
package ${package}; import com.androidtools.viewholder.AbstractViewHolder; import com.androidtools.viewholder.ViewHolderInject; <#list importList as one> import ${one}; </#list> public class ${class} extends AbstractViewHolder{ <#list pairList as prop> @ViewHolderInject(id = R.id.${prop.viewId}) public ${prop.viewType} ${prop.viewId}; </#list> public final int getId() { return R.layout.${layout}; } }
好了, 最后上git地址, 大家可以自定义修改,或者就直接用我生成的jar, 放在eclipse/plugins/下直接使用.
https://github.com/zjuwjf/AndroidTool.git