[原]Android 插件 根據佈局xml自動生成ViewHolder 技術分享(二)

小鷗有感上篇分享寫太羅嗦, 這篇直接的, 來乾貨.
java


編寫一個能實現Android佈局xml自動轉ViewHolder的插件, 咱們須要android

  1. 一個eclipse插件工程入口. 這裏baidu,google. 能夠參考EasyExploror插件, 下載下來反編譯下. git

    小鷗也是第一次編寫eclipse插件, 難度不大. 小鷗搗鼓這個自動生成ViewHolder的插件花了大半天. 好在最近好閒.github


  2. 咱們須要讀取佈局xml裏的元素, View和它對應的id, 用的dom4j.app

  3. 咱們須要根據當前工程的AndroidManifest.xml, 讀取package.dom

  4. 代碼的自動生成, 簡單的點, 就用freeMarker吧. 遵守Android規則, 把文件生成到gen文件夾吧.eclipse


上代碼- -ide


若是咱們有了工程的目錄和當前選擇的文件, 那麼執行這個接口.佈局

/**
 * @author zju_wjf
 *
 */
public interface IActionHandler {
	/**
	 * @param projectDir
	 * @param filePath
	 */
	public void handle(String projectDir, String filePath);
}


上面接口的實現
this

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

相關文章
相關標籤/搜索