文档章节

[原]Android 插件 根据布局xml自动生成ViewHolder 技术分享(二)

l
 lingoes
发布于 2015/05/22 16:24
字数 1144
阅读 40
收藏 0

小鸥有感上篇分享写太罗嗦, 这篇直接的, 来干货.


编写一个能实现Android布局xml自动转ViewHolder的插件, 我们需要

  1. 一个eclipse插件工程入口. 这里baidu,google. 可以参考EasyExploror插件, 下载下来反编译下. 

    小鸥也是第一次编写eclipse插件, 难度不大. 小鸥捣鼓这个自动生成ViewHolder的插件花了大半天. 好在最近好闲.


  2. 我们需要读取布局xml里的元素, View和它对应的id, 用的dom4j.

  3. 我们需要根据当前工程的AndroidManifest.xml, 读取package.

  4. 代码的自动生成, 简单的点, 就用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


© 著作权归作者所有

l
粉丝 2
博文 2
码字总数 1920
作品 0
杭州
私信 提问
[原]Android 插件 根据布局xml自动生成ViewHolder 技术分享(一)

码农小鸥, 在看某TT开源项目的Android实现, 发现到处分布的ViewHolder, 作为有代码洁癖的小码农, 小鸥觉得这个地方应该有很大的优化点. 基本的ViewHolder的样式 public class MyViewHorlder...

lingoes
2015/05/22
488
0
打造你的开发神器——介绍Android Studio上的几个插件

这个月因为各种事情在忙,包括赶项目,回老家,还有准备旅游的事,所以应该写不了四篇博客了。今天介绍一下关于Android Studio 的几个好用的插件,都是我在用的,它们或能帮你节省时间,或者...

浩码农
2015/04/11
0
0
Android studio插件整理

Android studio 以下简称AS,*号表示插件的常用比例,越高常用性越高 AS打开设置的快捷键Ctrl + alt + S 在线安装:File-->settings-->Plugins-->Browse repositories-->然后再输入框输入But...

惟吾德馨_慧
2018/05/23
0
0
Kotlin Android Extensions使用指南

Kotlin Android Extensions是Kotlin团队开发的一个插件,目的是让我们在开发过程中更少的编写代码。目前包括了视图绑定的功能。 几种绑定视图方式对比 xml文件如下 第一种,传统方式绑定视图...

猴亮屏
2018/10/29
94
0
Kotlin Android Extensions

原文地址:https://proandroiddev.com/kotlin-android-extensions-the-definitive-guide-786d190b30e7 Kotlin Android Extensions是一个Kotlin插件,将会生成一些额外的代码然你跟可以访问布...

小菜鸟程序媛
2018/09/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

3_数组

3_数组

行者终成事
今天
7
0
经典系统设计面试题解析:如何设计TinyURL(二)

原文链接:https://www.educative.io/courses/grokking-the-system-design-interview/m2ygV4E81AR 编者注:本文以一道经典的系统设计面试题:《如何设计TinyURL》的参考答案和解析为例,帮助...

APEMESH
今天
7
0
使用logstash同步MySQL数据到ES

概述   在生成业务常有将MySQL数据同步到ES的需求,如果需要很高的定制化,往往需要开发同步程序用于处理数据。但没有特殊业务需求,官方提供的logstash就很有优势了。   在使用logstas...

zxiaofan666
今天
10
0
X-MSG-IM-分布式信令跟踪能力

经过一周多的鏖战, X-MSG-IM的分布式信令跟踪能力已基本具备, 特点是: 实时. 只有要RX/TX就会实时产生信令跟踪事件, 先入kafka, 再入influxdb待查. 同时提供实时sub/pub接口. 完备. 可以完整...

dev5
今天
7
0
OpenJDK之CyclicBarrier

OpenJDK8,本人看的是openJDK。以前就看过,只是经常忘记,所以记录下 图1 CyclicBarrier是Doug Lea在JDK1.5中引入的,作用就不详细描述了,主要有如下俩个方法使用: await()方法,如果当前线...

克虏伯
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部