Feature: Dynamic layout inflater
This commit is contained in:
parent
437879248c
commit
c92754b73a
9
NeoModule/src/main/java/io/neomodule/NeoModule.java
Normal file
9
NeoModule/src/main/java/io/neomodule/NeoModule.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package io.neomodule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class NeoModule {
|
||||||
|
public abstract void launch();
|
||||||
|
}
|
280
NeoModule/src/main/java/io/neomodule/layout/Configuration.java
Normal file
280
NeoModule/src/main/java/io/neomodule/layout/Configuration.java
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
package io.neomodule.layout;
|
||||||
|
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.neomodule.layout.abs.ImageLoader;
|
||||||
|
import io.neomodule.layout.abs.ViewAttributeRunnable;
|
||||||
|
import io.neomodule.layout.utils.AttributeParser;
|
||||||
|
import io.neomodule.layout.utils.DimensionConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Configuration {
|
||||||
|
public final int noLayoutRule = -999;
|
||||||
|
public final String[] viewCorners = {"TopLeft", "TopRight", "BottomRight", "BottomLeft"};
|
||||||
|
|
||||||
|
public Map<String, ViewAttributeRunnable> viewRunnables;
|
||||||
|
public ImageLoader imageLoader = null;
|
||||||
|
|
||||||
|
Configuration() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void createViewRunnablesIfNeeded() {
|
||||||
|
if (viewRunnables != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewRunnables = new HashMap<>(30);
|
||||||
|
viewRunnables.put("scaleType", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof ImageView) {
|
||||||
|
ImageView.ScaleType scaleType = ((ImageView) view).getScaleType();
|
||||||
|
switch (value.toLowerCase()) {
|
||||||
|
case "center":
|
||||||
|
scaleType = ImageView.ScaleType.CENTER;
|
||||||
|
break;
|
||||||
|
case "center_crop":
|
||||||
|
scaleType = ImageView.ScaleType.CENTER_CROP;
|
||||||
|
break;
|
||||||
|
case "center_inside":
|
||||||
|
scaleType = ImageView.ScaleType.CENTER_INSIDE;
|
||||||
|
break;
|
||||||
|
case "fit_center":
|
||||||
|
scaleType = ImageView.ScaleType.FIT_CENTER;
|
||||||
|
break;
|
||||||
|
case "fit_end":
|
||||||
|
scaleType = ImageView.ScaleType.FIT_END;
|
||||||
|
break;
|
||||||
|
case "fit_start":
|
||||||
|
scaleType = ImageView.ScaleType.FIT_START;
|
||||||
|
break;
|
||||||
|
case "fit_xy":
|
||||||
|
scaleType = ImageView.ScaleType.FIT_XY;
|
||||||
|
break;
|
||||||
|
case "matrix":
|
||||||
|
scaleType = ImageView.ScaleType.MATRIX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
((ImageView) view).setScaleType(scaleType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("orientation", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof LinearLayout) {
|
||||||
|
((LinearLayout) view).setOrientation(value.equals("vertical") ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("text", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof TextView) {
|
||||||
|
((TextView) view).setText(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("textSize", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof TextView) {
|
||||||
|
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, DimensionConverter.toDimension(value, view.getResources().getDisplayMetrics()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("textColor", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof TextView) {
|
||||||
|
((TextView) view).setTextColor(AttributeParser.parseColor(view, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("textStyle", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof TextView) {
|
||||||
|
int typeFace = Typeface.NORMAL;
|
||||||
|
if (value.contains("bold")) typeFace |= Typeface.BOLD;
|
||||||
|
else if (value.contains("italic")) typeFace |= Typeface.ITALIC;
|
||||||
|
((TextView) view).setTypeface(null, typeFace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("textAlignment", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
|
||||||
|
int alignment = View.TEXT_ALIGNMENT_TEXT_START;
|
||||||
|
switch (value) {
|
||||||
|
case "center":
|
||||||
|
alignment = View.TEXT_ALIGNMENT_CENTER;
|
||||||
|
break;
|
||||||
|
case "left":
|
||||||
|
case "textStart":
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
case "textEnd":
|
||||||
|
alignment = View.TEXT_ALIGNMENT_TEXT_END;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
view.setTextAlignment(alignment);
|
||||||
|
} else {
|
||||||
|
int gravity = Gravity.LEFT;
|
||||||
|
switch (value) {
|
||||||
|
case "center":
|
||||||
|
gravity = Gravity.CENTER;
|
||||||
|
break;
|
||||||
|
case "left":
|
||||||
|
case "textStart":
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
case "textEnd":
|
||||||
|
gravity = Gravity.RIGHT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
((TextView) view).setGravity(gravity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("ellipsize", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof TextView) {
|
||||||
|
TextUtils.TruncateAt where = TextUtils.TruncateAt.END;
|
||||||
|
switch (value) {
|
||||||
|
case "start":
|
||||||
|
where = TextUtils.TruncateAt.START;
|
||||||
|
break;
|
||||||
|
case "middle":
|
||||||
|
where = TextUtils.TruncateAt.MIDDLE;
|
||||||
|
break;
|
||||||
|
case "marquee":
|
||||||
|
where = TextUtils.TruncateAt.MARQUEE;
|
||||||
|
break;
|
||||||
|
case "end":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
((TextView) view).setEllipsize(where);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("singleLine", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof TextView) {
|
||||||
|
((TextView) view).setSingleLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("hint", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof EditText) {
|
||||||
|
((EditText) view).setHint(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("inputType", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof TextView) {
|
||||||
|
int inputType = 0;
|
||||||
|
switch (value) {
|
||||||
|
case "textEmailAddress":
|
||||||
|
inputType |= InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
|
||||||
|
break;
|
||||||
|
case "number":
|
||||||
|
inputType |= InputType.TYPE_CLASS_NUMBER;
|
||||||
|
break;
|
||||||
|
case "phone":
|
||||||
|
inputType |= InputType.TYPE_CLASS_PHONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (inputType > 0) ((TextView) view).setInputType(inputType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("gravity", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
int gravity = AttributeParser.parseGravity(value);
|
||||||
|
if (view instanceof TextView) {
|
||||||
|
((TextView) view).setGravity(gravity);
|
||||||
|
} else if (view instanceof LinearLayout) {
|
||||||
|
((LinearLayout) view).setGravity(gravity);
|
||||||
|
} else if (view instanceof RelativeLayout) {
|
||||||
|
((RelativeLayout) view).setGravity(gravity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("src", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
if (view instanceof ImageView) {
|
||||||
|
String imageName = value;
|
||||||
|
if (imageName.startsWith("//")) imageName = "http:" + imageName;
|
||||||
|
if (imageName.startsWith("http")) {
|
||||||
|
if (imageLoader != null) {
|
||||||
|
if (attrs.containsKey("cornerRadius")) {
|
||||||
|
int radius = DimensionConverter.toDimensionPixelSize(attrs.get("cornerRadius"), view.getResources().getDisplayMetrics());
|
||||||
|
imageLoader.loadRoundedImage((ImageView) view, imageName, radius);
|
||||||
|
} else {
|
||||||
|
imageLoader.loadImage((ImageView) view, imageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (imageName.startsWith("@drawable/")) {
|
||||||
|
imageName = imageName.substring("@drawable/".length());
|
||||||
|
((ImageView) view).setImageDrawable(AttributeParser.getDrawableByName(view, imageName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("visibility", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
int visibility = View.VISIBLE;
|
||||||
|
String visValue = value.toLowerCase();
|
||||||
|
if (visValue.equals("gone")) visibility = View.GONE;
|
||||||
|
else if (visValue.equals("invisible")) visibility = View.INVISIBLE;
|
||||||
|
view.setVisibility(visibility);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("clickable", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
view.setClickable(value.equals("true"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("tag", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
throw new IllegalStateException("You cannot set tag in this situation, because we have other purpose.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewRunnables.put("onClick", new ViewAttributeRunnable() {
|
||||||
|
@Override
|
||||||
|
public void apply(View view, String value, ViewGroup parent, Map<String, String> attrs) {
|
||||||
|
view.setOnClickListener(AttributeParser.parseOnClick(parent, value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
15
NeoModule/src/main/java/io/neomodule/layout/LayoutInfo.java
Normal file
15
NeoModule/src/main/java/io/neomodule/layout/LayoutInfo.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package io.neomodule.layout;
|
||||||
|
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class LayoutInfo {
|
||||||
|
public HashMap<String, Integer> nameToIdNumber;
|
||||||
|
public Object delegate;
|
||||||
|
public GradientDrawable backgroundDrawable;
|
||||||
|
|
||||||
|
public LayoutInfo() {
|
||||||
|
nameToIdNumber = new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,235 @@
|
|||||||
|
package io.neomodule.layout;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
|
import io.neomodule.layout.abs.ImageLoader;
|
||||||
|
import io.neomodule.layout.utils.AttributeApply;
|
||||||
|
import io.neomodule.layout.utils.UniqueId;
|
||||||
|
|
||||||
|
public class NeoLayoutInflater {
|
||||||
|
private static Configuration CONFIG = new Configuration();
|
||||||
|
|
||||||
|
public static void setImageLoader(ImageLoader il) {
|
||||||
|
CONFIG.imageLoader = il;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDelegate(View root, Object delegate) {
|
||||||
|
LayoutInfo info;
|
||||||
|
if (root.getTag() == null || !(root.getTag() instanceof LayoutInfo)) {
|
||||||
|
info = new LayoutInfo();
|
||||||
|
root.setTag(info);
|
||||||
|
} else {
|
||||||
|
info = (LayoutInfo) root.getTag();
|
||||||
|
}
|
||||||
|
info.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static View inflateName(Context context, String name) {
|
||||||
|
return inflateName(context, name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static View inflateName(Context context, String name, ViewGroup parent) {
|
||||||
|
if (name.startsWith("<")) {
|
||||||
|
// Assume it's XML
|
||||||
|
return NeoLayoutInflater.inflate(context, name, parent);
|
||||||
|
} else {
|
||||||
|
File savedFile = context.getFileStreamPath(name + ".xml");
|
||||||
|
try {
|
||||||
|
InputStream fileStream = new FileInputStream(savedFile);
|
||||||
|
return NeoLayoutInflater.inflate(context, fileStream, parent);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream assetStream = context.getAssets().open(name + ".xml");
|
||||||
|
return NeoLayoutInflater.inflate(context, assetStream, parent);
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
int id = context.getResources().getIdentifier(name, "layout", context.getPackageName());
|
||||||
|
if (id > 0) {
|
||||||
|
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
return inflater.inflate(id, parent, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static View inflate(Context context, File xmlPath) {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = new FileInputStream(xmlPath);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return NeoLayoutInflater.inflate(context, inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static View inflate(Context context, String xml) {
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
||||||
|
return NeoLayoutInflater.inflate(context, inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static View inflate(Context context, String xml, ViewGroup parent) {
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
||||||
|
return NeoLayoutInflater.inflate(context, inputStream, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static View inflate(Context context, InputStream inputStream) {
|
||||||
|
return inflate(context, inputStream, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static View inflate(Context context, InputStream inputStream, ViewGroup parent) {
|
||||||
|
try {
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
dbf.setNamespaceAware(true);
|
||||||
|
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||||
|
Document document = db.parse(inputStream);
|
||||||
|
try {
|
||||||
|
return inflate(context, document.getDocumentElement(), parent);
|
||||||
|
} finally {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
} catch (IOException | ParserConfigurationException | SAXException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Nullable
|
||||||
|
public static <T extends View> T findViewById(View view, String id) {
|
||||||
|
int idNum = UniqueId.idFromString(view, id);
|
||||||
|
if (idNum == 0) return null;
|
||||||
|
return (T) view.findViewById(idNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static View inflate(Context context, Node node) {
|
||||||
|
return inflate(context, node, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static View inflate(Context context, Node node, ViewGroup parent) {
|
||||||
|
View mainView = constructView(context, node.getNodeName());
|
||||||
|
if (parent != null)
|
||||||
|
parent.addView(mainView); // have to add to parent to enable certain layout attrs
|
||||||
|
applyAttributes(mainView, getAttributesMap(node), parent);
|
||||||
|
if (mainView instanceof ViewGroup && node.hasChildNodes()) {
|
||||||
|
parseChildren(context, node, (ViewGroup) mainView);
|
||||||
|
}
|
||||||
|
return mainView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历节点里的每一个字节点,并解析成 View
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param node 节点
|
||||||
|
* @param mainView 父视图
|
||||||
|
*/
|
||||||
|
private static void parseChildren(Context context, Node node, ViewGroup mainView) {
|
||||||
|
NodeList nodeList = node.getChildNodes();
|
||||||
|
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||||
|
Node currentNode = nodeList.item(i);
|
||||||
|
if (currentNode.getNodeType() != Node.ELEMENT_NODE) continue;
|
||||||
|
inflate(context, currentNode, mainView); // this recursively can call parseChildren
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 xml 里定义的节点名的 View
|
||||||
|
* 如果节点名里没有带包名,就默认创建 android.widget 包下的实例,如 EditText, TextView
|
||||||
|
* 如果节点名里带了包名,就创建对应的实例,如 com.xxx.view.SomeView
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param name xml里的节点名
|
||||||
|
* @return 对应的 View
|
||||||
|
*/
|
||||||
|
private static View constructView(Context context, String name) {
|
||||||
|
try {
|
||||||
|
if (!name.contains(".")) {
|
||||||
|
name = "android.widget." + name;
|
||||||
|
}
|
||||||
|
Class<?> clazz = Class.forName(name);
|
||||||
|
Constructor<?> constructor = clazz.getConstructor(Context.class);
|
||||||
|
|
||||||
|
return (View) constructor.newInstance(context);
|
||||||
|
} catch (ClassNotFoundException
|
||||||
|
| NoSuchMethodException
|
||||||
|
| InstantiationException
|
||||||
|
| InvocationTargetException
|
||||||
|
| IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 得到一个节点下所有的 xml 属性
|
||||||
|
*
|
||||||
|
* @param currentNode 节点
|
||||||
|
* @return 属性的名字和值
|
||||||
|
*/
|
||||||
|
private static HashMap<String, String> getAttributesMap(Node currentNode) {
|
||||||
|
NamedNodeMap attributeMap = currentNode.getAttributes();
|
||||||
|
int attributeCount = attributeMap.getLength();
|
||||||
|
HashMap<String, String> attributes = new HashMap<>(attributeCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < attributeCount; i++) {
|
||||||
|
Node attr = attributeMap.item(i);
|
||||||
|
String nodeName = attr.getNodeName();
|
||||||
|
|
||||||
|
// 跳过头部的 namespace
|
||||||
|
if (nodeName.startsWith("android:")) {
|
||||||
|
nodeName = nodeName.substring(8);
|
||||||
|
}
|
||||||
|
attributes.put(nodeName, attr.getNodeValue());
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对一个 View 设置属性
|
||||||
|
*
|
||||||
|
* @param view 需要设置属性的view
|
||||||
|
* @param attrs 所有属性
|
||||||
|
* @param parent view的父视图
|
||||||
|
*/
|
||||||
|
private static void applyAttributes(View view, Map<String, String> attrs, ViewGroup parent) {
|
||||||
|
CONFIG.createViewRunnablesIfNeeded();
|
||||||
|
new AttributeApply(view, attrs, parent).apply(CONFIG);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package io.neomodule.layout.abs;
|
||||||
|
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
public interface ImageLoader {
|
||||||
|
void loadImage(ImageView view, String url);
|
||||||
|
|
||||||
|
void loadRoundedImage(ImageView view, String url, int radius);
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package io.neomodule.layout.abs;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface ViewAttributeRunnable {
|
||||||
|
void apply(View view, String value, ViewGroup parent, Map<String, String> attrs);
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package io.neomodule.layout.listener;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import io.neomodule.layout.LayoutInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class OnClickForwarder implements View.OnClickListener {
|
||||||
|
private ViewGroup parent;
|
||||||
|
private String methodName;
|
||||||
|
|
||||||
|
public OnClickForwarder(ViewGroup parent, String methodName) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.methodName = methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ViewGroup root = parent;
|
||||||
|
LayoutInfo info = null;
|
||||||
|
while (root != null && (root.getParent() instanceof ViewGroup)) {
|
||||||
|
if (root.getTag() != null && root.getTag() instanceof LayoutInfo) {
|
||||||
|
info = (LayoutInfo) root.getTag();
|
||||||
|
if (info.delegate != null) break;
|
||||||
|
}
|
||||||
|
root = (ViewGroup) root.getParent();
|
||||||
|
}
|
||||||
|
if (info != null && info.delegate != null) {
|
||||||
|
final Object delegate = info.delegate;
|
||||||
|
invokeMethod(delegate, methodName, false, view);
|
||||||
|
} else {
|
||||||
|
Log.e("DynamicLayoutInflater", "Unable to find valid delegate for click named " + methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeMethod(Object delegate, final String methodName, boolean withView, View view) {
|
||||||
|
Object[] args = null;
|
||||||
|
String finalMethod = methodName;
|
||||||
|
if (methodName.endsWith(")")) {
|
||||||
|
String[] parts = methodName.split("[(]", 2);
|
||||||
|
finalMethod = parts[0];
|
||||||
|
try {
|
||||||
|
String argText = parts[1].replace(""", "\"");
|
||||||
|
JSONArray arr = new JSONArray("[" + argText.substring(0, argText.length() - 1) + "]");
|
||||||
|
args = new Object[arr.length()];
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
args[i] = arr.get(i);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else if (withView) {
|
||||||
|
args = new Object[1];
|
||||||
|
args[0] = view;
|
||||||
|
}
|
||||||
|
Class<?> klass = delegate.getClass();
|
||||||
|
try {
|
||||||
|
|
||||||
|
Class<?>[] argClasses = null;
|
||||||
|
if (args != null && args.length > 0) {
|
||||||
|
argClasses = new Class[args.length];
|
||||||
|
if (withView) {
|
||||||
|
argClasses[0] = View.class;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
Class<?> argClass = args[i].getClass();
|
||||||
|
if (argClass == Integer.class)
|
||||||
|
argClass = int.class; // Nobody uses Integer...
|
||||||
|
argClasses[i] = argClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Method method = klass.getMethod(finalMethod, argClasses);
|
||||||
|
method.invoke(delegate, args);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
if (!withView && !methodName.endsWith(")")) {
|
||||||
|
invokeMethod(delegate, methodName, true, view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,504 @@
|
|||||||
|
package io.neomodule.layout.utils;
|
||||||
|
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.graphics.drawable.StateListDrawable;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.neomodule.layout.Configuration;
|
||||||
|
import io.neomodule.layout.LayoutInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class AttributeApply {
|
||||||
|
/**
|
||||||
|
* 存储处理中的状态
|
||||||
|
*/
|
||||||
|
private static class Status {
|
||||||
|
int layoutRule;
|
||||||
|
int marginLeft = 0;
|
||||||
|
int marginRight = 0;
|
||||||
|
int marginTop = 0;
|
||||||
|
int marginBottom = 0;
|
||||||
|
int paddingLeft = 0;
|
||||||
|
int paddingRight = 0;
|
||||||
|
int paddingTop = 0;
|
||||||
|
int paddingBottom = 0;
|
||||||
|
boolean hasCornerRadius = false;
|
||||||
|
boolean hasCornerRadii = false;
|
||||||
|
boolean layoutTarget = false;
|
||||||
|
|
||||||
|
Status(Configuration config) {
|
||||||
|
this.layoutRule = config.noLayoutRule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private View view;
|
||||||
|
private Map<String, String> attrs;
|
||||||
|
private ViewGroup parent;
|
||||||
|
|
||||||
|
public AttributeApply(View view, Map<String, String> attrs, ViewGroup parent) {
|
||||||
|
this.view = view;
|
||||||
|
this.attrs = attrs;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把xml里定义的属性设置到 View 中
|
||||||
|
*
|
||||||
|
* @param config LayoutInflater的配置
|
||||||
|
*/
|
||||||
|
public void apply(Configuration config) {
|
||||||
|
Status status = new Status(config);
|
||||||
|
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : attrs.entrySet()) {
|
||||||
|
String attr = entry.getKey();
|
||||||
|
// 如果存在预设的处理方式,我们就不管了
|
||||||
|
if (config.viewRunnables.containsKey(attr)) {
|
||||||
|
config.viewRunnables.get(attr).apply(view, entry.getValue(), parent, attrs);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr.startsWith("cornerRadius")) {
|
||||||
|
status.hasCornerRadius = true;
|
||||||
|
status.hasCornerRadii = !attr.equals("cornerRadius");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyAttribute(attr, entry, layoutParams, status);
|
||||||
|
|
||||||
|
if (status.layoutRule != config.noLayoutRule && parent instanceof RelativeLayout) {
|
||||||
|
if (status.layoutTarget) {
|
||||||
|
int anchor = UniqueId.idFromString(parent, AttributeParser.parseId(entry.getValue()));
|
||||||
|
((RelativeLayout.LayoutParams) layoutParams).addRule(status.layoutRule, anchor);
|
||||||
|
} else if (entry.getValue().equals("true")) {
|
||||||
|
((RelativeLayout.LayoutParams) layoutParams).addRule(status.layoutRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 View 的背景
|
||||||
|
if (attrs.containsKey("background") || attrs.containsKey("borderColor")) {
|
||||||
|
String backgroundValue = attrs.containsKey("background") ? attrs.get("background") : null;
|
||||||
|
|
||||||
|
// 如果直接从 drawable 中拿那就简单多了
|
||||||
|
if (backgroundValue != null && backgroundValue.startsWith("@drawable/")) {
|
||||||
|
applyBackgroundDrawable(backgroundValue);
|
||||||
|
|
||||||
|
} else if (backgroundValue == null
|
||||||
|
|| backgroundValue.startsWith("#")
|
||||||
|
|| backgroundValue.startsWith("@color")) {
|
||||||
|
applyBackgroundColor(config, status, backgroundValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyParsedMargin(status, layoutParams);
|
||||||
|
applyParsedPadding(status);
|
||||||
|
view.setLayoutParams(layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析每一个属性
|
||||||
|
*
|
||||||
|
* @param attr 属性名
|
||||||
|
* @param entry 属性的名和值
|
||||||
|
* @param layoutParams 父视图的 LayoutParams
|
||||||
|
* @param status 处理状态
|
||||||
|
*/
|
||||||
|
private void applyAttribute(String attr, Map.Entry<String, String> entry,
|
||||||
|
ViewGroup.LayoutParams layoutParams, Status status) {
|
||||||
|
if (attr.startsWith("layout_margin")) {
|
||||||
|
applyLayoutMargin(attr, entry, status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr.startsWith("padding")) {
|
||||||
|
applyPadding(attr, entry, status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (attr) {
|
||||||
|
case "id":
|
||||||
|
applyId(entry);
|
||||||
|
break;
|
||||||
|
case "width":
|
||||||
|
case "layout_width":
|
||||||
|
applyLayoutWidth(layoutParams, entry);
|
||||||
|
break;
|
||||||
|
case "height":
|
||||||
|
case "layout_height":
|
||||||
|
applyLayoutHeight(layoutParams, entry);
|
||||||
|
break;
|
||||||
|
case "layout_gravity":
|
||||||
|
applyGravity(layoutParams, entry);
|
||||||
|
break;
|
||||||
|
case "layout_weight":
|
||||||
|
applyLayoutWeight(layoutParams, entry);
|
||||||
|
break;
|
||||||
|
case "layout_below":
|
||||||
|
status.layoutRule = RelativeLayout.BELOW;
|
||||||
|
status.layoutTarget = true;
|
||||||
|
break;
|
||||||
|
case "layout_above":
|
||||||
|
status.layoutRule = RelativeLayout.ABOVE;
|
||||||
|
status.layoutTarget = true;
|
||||||
|
break;
|
||||||
|
case "layout_toLeftOf":
|
||||||
|
status.layoutRule = RelativeLayout.LEFT_OF;
|
||||||
|
status.layoutTarget = true;
|
||||||
|
break;
|
||||||
|
case "layout_toRightOf":
|
||||||
|
status.layoutRule = RelativeLayout.RIGHT_OF;
|
||||||
|
status.layoutTarget = true;
|
||||||
|
break;
|
||||||
|
case "layout_alignBottom":
|
||||||
|
status.layoutRule = RelativeLayout.ALIGN_BOTTOM;
|
||||||
|
status.layoutTarget = true;
|
||||||
|
break;
|
||||||
|
case "layout_alignTop":
|
||||||
|
status.layoutRule = RelativeLayout.ALIGN_TOP;
|
||||||
|
status.layoutTarget = true;
|
||||||
|
break;
|
||||||
|
case "layout_alignLeft":
|
||||||
|
case "layout_alignStart":
|
||||||
|
status.layoutRule = RelativeLayout.ALIGN_LEFT;
|
||||||
|
status.layoutTarget = true;
|
||||||
|
break;
|
||||||
|
case "layout_alignRight":
|
||||||
|
case "layout_alignEnd":
|
||||||
|
status.layoutRule = RelativeLayout.ALIGN_RIGHT;
|
||||||
|
status.layoutTarget = true;
|
||||||
|
break;
|
||||||
|
case "layout_alignParentBottom":
|
||||||
|
status.layoutRule = RelativeLayout.ALIGN_PARENT_BOTTOM;
|
||||||
|
break;
|
||||||
|
case "layout_alignParentTop":
|
||||||
|
status.layoutRule = RelativeLayout.ALIGN_PARENT_TOP;
|
||||||
|
break;
|
||||||
|
case "layout_alignParentLeft":
|
||||||
|
case "layout_alignParentStart":
|
||||||
|
status.layoutRule = RelativeLayout.ALIGN_PARENT_LEFT;
|
||||||
|
break;
|
||||||
|
case "layout_alignParentRight":
|
||||||
|
case "layout_alignParentEnd":
|
||||||
|
status.layoutRule = RelativeLayout.ALIGN_PARENT_RIGHT;
|
||||||
|
break;
|
||||||
|
case "layout_centerHorizontal":
|
||||||
|
status.layoutRule = RelativeLayout.CENTER_HORIZONTAL;
|
||||||
|
break;
|
||||||
|
case "layout_centerVertical":
|
||||||
|
status.layoutRule = RelativeLayout.CENTER_VERTICAL;
|
||||||
|
break;
|
||||||
|
case "layout_centerInParent":
|
||||||
|
status.layoutRule = RelativeLayout.CENTER_IN_PARENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 status 中读取处理完毕的 padding 系列值,并且应用到 View 上
|
||||||
|
*
|
||||||
|
* @param status 处理状态
|
||||||
|
*/
|
||||||
|
private void applyParsedPadding(Status status) {
|
||||||
|
view.setPadding(status.paddingLeft, status.paddingTop, status.paddingRight, status.paddingBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 status 中读取处理完毕的 layout_margin 系列值,并且应用到 View 上
|
||||||
|
*
|
||||||
|
* @param status 处理状态
|
||||||
|
* @param layoutParams 父视图的 LayoutParams
|
||||||
|
*/
|
||||||
|
private void applyParsedMargin(Status status, ViewGroup.LayoutParams layoutParams) {
|
||||||
|
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
||||||
|
((ViewGroup.MarginLayoutParams) layoutParams).setMargins(status.marginLeft,
|
||||||
|
status.marginTop,
|
||||||
|
status.marginRight,
|
||||||
|
status.marginBottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从当前 app 的 drawable 中得到背景并设置到 View 上
|
||||||
|
*
|
||||||
|
* @param drawableResourceName 资源在 drawable 下的名字
|
||||||
|
*/
|
||||||
|
private void applyBackgroundDrawable(String drawableResourceName) {
|
||||||
|
view.setBackground(AttributeParser.getDrawableByName(view, drawableResourceName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析一个 # 开头的颜色值或从当前app 的 R.color 下取得颜色应用于 View 的背景颜色
|
||||||
|
*
|
||||||
|
* @param config LayoutInflater 配置
|
||||||
|
* @param status 处理状态
|
||||||
|
* @param colorValue # 或者 @color/ 开头的颜色值
|
||||||
|
*/
|
||||||
|
private void applyBackgroundColor(Configuration config, Status status, String colorValue) {
|
||||||
|
int validatedColor = AttributeParser.parseColor(view, colorValue == null ? "#00000000" : colorValue);
|
||||||
|
|
||||||
|
if (view instanceof Button || attrs.containsKey("pressedColor")) {
|
||||||
|
// 按钮有按下效果,所以我们视为同一种情况
|
||||||
|
applyPressedColor(config, status, validatedColor);
|
||||||
|
|
||||||
|
} else if (status.hasCornerRadius || attrs.containsKey("borderColor")) {
|
||||||
|
GradientDrawable gd = new GradientDrawable();
|
||||||
|
gd.setColor(validatedColor);
|
||||||
|
|
||||||
|
// 把 pressed 和正常视为同一种情况
|
||||||
|
applyCorners(config, status, gd, gd);
|
||||||
|
applyBorderColor(gd, gd);
|
||||||
|
|
||||||
|
view.setBackground(gd);
|
||||||
|
getLayoutInfo().backgroundDrawable = gd;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
view.setBackgroundColor(validatedColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 pressedColor 或者 Button 的背景色
|
||||||
|
* @param config LayoutInflater 配置
|
||||||
|
* @param status 出炉状态
|
||||||
|
* @param colorValue 颜色值
|
||||||
|
*/
|
||||||
|
private void applyPressedColor(Configuration config, Status status, int colorValue) {
|
||||||
|
int pressedColor;
|
||||||
|
|
||||||
|
if (attrs.containsKey("pressedColor")) {
|
||||||
|
pressedColor = AttributeParser.parseColor(view, attrs.get("pressedColor"));
|
||||||
|
} else {
|
||||||
|
pressedColor = AttributeParser.adjustBrightness(colorValue, 0.9f);
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientDrawable gd = new GradientDrawable();
|
||||||
|
gd.setColor(colorValue);
|
||||||
|
GradientDrawable pressedGd = new GradientDrawable();
|
||||||
|
pressedGd.setColor(pressedColor);
|
||||||
|
|
||||||
|
applyCorners(config, status, gd, pressedGd);
|
||||||
|
applyBorderColor(gd, pressedGd);
|
||||||
|
|
||||||
|
StateListDrawable selector = new StateListDrawable();
|
||||||
|
selector.addState(new int[]{android.R.attr.state_pressed}, pressedGd);
|
||||||
|
selector.addState(new int[]{}, gd);
|
||||||
|
view.setBackground(selector);
|
||||||
|
|
||||||
|
getLayoutInfo().backgroundDrawable = gd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 borderColor
|
||||||
|
* @param gd 背景
|
||||||
|
* @param pressedGd 按下时的背景,如果没有,设置为 gd 即可
|
||||||
|
*/
|
||||||
|
private void applyBorderColor(GradientDrawable gd, GradientDrawable pressedGd) {
|
||||||
|
if (attrs.containsKey("borderColor")) {
|
||||||
|
String borderWidth = "1dp";
|
||||||
|
if (attrs.containsKey("borderWidth")) {
|
||||||
|
borderWidth = attrs.get("borderWidth");
|
||||||
|
}
|
||||||
|
int borderWidthPx = DimensionConverter.toDimensionPixelSize(borderWidth, view.getResources().getDisplayMetrics());
|
||||||
|
gd.setStroke(borderWidthPx, AttributeParser.parseColor(view, attrs.get("borderColor")));
|
||||||
|
pressedGd.setStroke(borderWidthPx, AttributeParser.parseColor(view, attrs.get("borderColor")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 cornerRadius 或者 cornerRadiusXXX
|
||||||
|
* @param config LayoutInflater 配置
|
||||||
|
* @param status 出炉状态
|
||||||
|
* @param gd 背景
|
||||||
|
* @param pressedGd 按下背景,如果没有,设置为 gd 即可
|
||||||
|
*/
|
||||||
|
private void applyCorners(Configuration config, Status status, GradientDrawable gd, GradientDrawable pressedGd) {
|
||||||
|
if (status.hasCornerRadii) {
|
||||||
|
float radii[] = new float[8];
|
||||||
|
for (int i = 0; i < config.viewCorners.length; i++) {
|
||||||
|
String corner = config.viewCorners[i];
|
||||||
|
if (attrs.containsKey("cornerRadius" + corner)) {
|
||||||
|
radii[i * 2] = radii[i * 2 + 1] = DimensionConverter.toDimension(attrs.get("cornerRadius" + corner), view.getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
gd.setCornerRadii(radii);
|
||||||
|
pressedGd.setCornerRadii(radii);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (status.hasCornerRadius) {
|
||||||
|
float cornerRadius = DimensionConverter.toDimension(attrs.get("cornerRadius"), view.getResources().getDisplayMetrics());
|
||||||
|
gd.setCornerRadius(cornerRadius);
|
||||||
|
pressedGd.setCornerRadius(cornerRadius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 xml 属性中的 padding 系列属性,如 padding="10dp", paddingLeft="16dp"
|
||||||
|
*
|
||||||
|
* @param attr 属性名,需要判断是 padding 还是 paddingXXX
|
||||||
|
* @param entry 属性值
|
||||||
|
* @param status 处理状态
|
||||||
|
*/
|
||||||
|
private void applyPadding(String attr, Map.Entry<String, String> entry, Status status) {
|
||||||
|
switch (attr) {
|
||||||
|
case "padding":
|
||||||
|
status.paddingBottom = status.paddingLeft = status.paddingRight = status.paddingTop
|
||||||
|
= DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics());
|
||||||
|
break;
|
||||||
|
case "paddingLeft":
|
||||||
|
status.paddingLeft = DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics());
|
||||||
|
break;
|
||||||
|
case "paddingTop":
|
||||||
|
status.paddingTop = DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics());
|
||||||
|
break;
|
||||||
|
case "paddingRight":
|
||||||
|
status.paddingRight = DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics());
|
||||||
|
break;
|
||||||
|
case "paddingBottom":
|
||||||
|
status.paddingBottom = DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics());
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 xml 属性中的 layout_margin 系列属性,如 layout_margin="10dp", layout_marginRight="16dp"
|
||||||
|
*
|
||||||
|
* @param attr 属性名,需要判断是 layout_margin 还是 layout_marginXXX
|
||||||
|
* @param entry 属性值
|
||||||
|
* @param status 处理状态
|
||||||
|
*/
|
||||||
|
private void applyLayoutMargin(String attr, Map.Entry<String, String> entry, Status status) {
|
||||||
|
switch (attr) {
|
||||||
|
case "layout_margin":
|
||||||
|
status.marginLeft = status.marginRight = status.marginTop = status.marginBottom
|
||||||
|
= DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics());
|
||||||
|
break;
|
||||||
|
case "layout_marginLeft":
|
||||||
|
status.marginLeft = DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics(), parent, true);
|
||||||
|
break;
|
||||||
|
case "layout_marginTop":
|
||||||
|
status.marginTop = DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics(), parent, false);
|
||||||
|
break;
|
||||||
|
case "layout_marginRight":
|
||||||
|
status.marginRight = DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics(), parent, true);
|
||||||
|
break;
|
||||||
|
case "layout_marginBottom":
|
||||||
|
status.marginBottom = DimensionConverter.toDimensionPixelSize(entry.getValue(),
|
||||||
|
view.getResources().getDisplayMetrics(), parent, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 layout_weight
|
||||||
|
*
|
||||||
|
* @param layoutParams 父视图的 LayoutParams
|
||||||
|
* @param entry 属性值
|
||||||
|
*/
|
||||||
|
private void applyLayoutWeight(ViewGroup.LayoutParams layoutParams, Map.Entry<String, String> entry) {
|
||||||
|
if (parent != null && parent instanceof LinearLayout) {
|
||||||
|
((LinearLayout.LayoutParams) layoutParams).weight = Float.parseFloat(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 gravity
|
||||||
|
*
|
||||||
|
* @param layoutParams 父视图的 LayoutParams
|
||||||
|
* @param entry 属性值
|
||||||
|
*/
|
||||||
|
private void applyGravity(ViewGroup.LayoutParams layoutParams, Map.Entry<String, String> entry) {
|
||||||
|
if (parent != null && parent instanceof LinearLayout) {
|
||||||
|
((LinearLayout.LayoutParams) layoutParams).gravity = AttributeParser.parseGravity(entry.getValue());
|
||||||
|
} else if (parent != null && parent instanceof FrameLayout) {
|
||||||
|
((FrameLayout.LayoutParams) layoutParams).gravity = AttributeParser.parseGravity(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 layout_height
|
||||||
|
*
|
||||||
|
* @param layoutParams 父视图的 LayoutParams
|
||||||
|
* @param entry 属性值
|
||||||
|
*/
|
||||||
|
private void applyLayoutHeight(ViewGroup.LayoutParams layoutParams, Map.Entry<String, String> entry) {
|
||||||
|
switch (entry.getValue()) {
|
||||||
|
case "wrap_content":
|
||||||
|
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||||
|
break;
|
||||||
|
case "fill_parent":
|
||||||
|
case "match_parent":
|
||||||
|
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
layoutParams.height = DimensionConverter.toDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics(), parent, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 layout_width
|
||||||
|
*
|
||||||
|
* @param layoutParams 父视图的 LayoutParams
|
||||||
|
* @param entry 属性值
|
||||||
|
*/
|
||||||
|
private void applyLayoutWidth(ViewGroup.LayoutParams layoutParams, Map.Entry<String, String> entry) {
|
||||||
|
switch (entry.getValue()) {
|
||||||
|
case "wrap_content":
|
||||||
|
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||||
|
break;
|
||||||
|
case "fill_parent":
|
||||||
|
case "match_parent":
|
||||||
|
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
layoutParams.width = DimensionConverter.toDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics(), parent, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 id
|
||||||
|
*
|
||||||
|
* @param entry 属性值
|
||||||
|
*/
|
||||||
|
private void applyId(Map.Entry<String, String> entry) {
|
||||||
|
String idString = AttributeParser.parseId(entry.getValue());
|
||||||
|
if (parent != null) {
|
||||||
|
LayoutInfo info = getLayoutInfo();
|
||||||
|
int newId = UniqueId.newId();
|
||||||
|
view.setId(newId);
|
||||||
|
info.nameToIdNumber.put(idString, newId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LayoutInfo getLayoutInfo() {
|
||||||
|
LayoutInfo info;
|
||||||
|
if (parent.getTag() != null && parent.getTag() instanceof LayoutInfo) {
|
||||||
|
info = (LayoutInfo) parent.getTag();
|
||||||
|
} else {
|
||||||
|
info = new LayoutInfo();
|
||||||
|
parent.setTag(info);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package io.neomodule.layout.utils;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import io.neomodule.layout.listener.OnClickForwarder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class AttributeParser {
|
||||||
|
public static int adjustBrightness(int color, float amount) {
|
||||||
|
int red = color & 0xFF0000 >> 16;
|
||||||
|
int green = color & 0x00FF00 >> 8;
|
||||||
|
int blue = color & 0x0000FF;
|
||||||
|
int result = (int) (blue * amount);
|
||||||
|
result += (int) (green * amount) << 8;
|
||||||
|
result += (int) (red * amount) << 16;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String parseId(String value) {
|
||||||
|
if (value.startsWith("@+id/")) {
|
||||||
|
return value.substring(5);
|
||||||
|
} else if (value.startsWith("@id/")) {
|
||||||
|
return value.substring(4);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int parseGravity(String value) {
|
||||||
|
int gravity = Gravity.NO_GRAVITY;
|
||||||
|
String[] parts = value.toLowerCase().split("[|]");
|
||||||
|
for (String part : parts) {
|
||||||
|
switch (part) {
|
||||||
|
case "center":
|
||||||
|
gravity = gravity | Gravity.CENTER;
|
||||||
|
break;
|
||||||
|
case "left":
|
||||||
|
case "textStart":
|
||||||
|
gravity = gravity | Gravity.LEFT;
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
case "textEnd":
|
||||||
|
gravity = gravity | Gravity.RIGHT;
|
||||||
|
break;
|
||||||
|
case "top":
|
||||||
|
gravity = gravity | Gravity.TOP;
|
||||||
|
break;
|
||||||
|
case "bottom":
|
||||||
|
gravity = gravity | Gravity.BOTTOM;
|
||||||
|
break;
|
||||||
|
case "center_horizontal":
|
||||||
|
gravity = gravity | Gravity.CENTER_HORIZONTAL;
|
||||||
|
break;
|
||||||
|
case "center_vertical":
|
||||||
|
gravity = gravity | Gravity.CENTER_VERTICAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gravity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Drawable getDrawableByName(View view, String name) {
|
||||||
|
Resources resources = view.getResources();
|
||||||
|
return resources.getDrawable(resources.getIdentifier(name, "drawable",
|
||||||
|
view.getContext().getPackageName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int parseColor(View view, String text) {
|
||||||
|
if (text.startsWith("@color/")) {
|
||||||
|
Resources resources = view.getResources();
|
||||||
|
return resources.getColor(resources.getIdentifier(text.substring("@color/".length()), "color", view.getContext().getPackageName()));
|
||||||
|
}
|
||||||
|
if (text.length() == 4 && text.startsWith("#")) {
|
||||||
|
text = "#" + text.charAt(1) + text.charAt(1) + text.charAt(2) + text.charAt(2) + text.charAt(3) + text.charAt(3);
|
||||||
|
}
|
||||||
|
return Color.parseColor(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static View.OnClickListener parseOnClick(ViewGroup parent, String methodName) {
|
||||||
|
return new OnClickForwarder(parent, methodName);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
package io.neomodule.layout.utils;
|
||||||
|
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class DimensionConverter {
|
||||||
|
private static Map<String, Float> cached = new HashMap<>();
|
||||||
|
|
||||||
|
// -- Initialize dimension string to constant lookup.
|
||||||
|
private static final Map<String, Integer> dimensionConstantLookup = initDimensionConstantLookup();
|
||||||
|
|
||||||
|
private static Map<String, Integer> initDimensionConstantLookup() {
|
||||||
|
Map<String, Integer> m = new HashMap<>();
|
||||||
|
m.put("px", TypedValue.COMPLEX_UNIT_PX);
|
||||||
|
m.put("dip", TypedValue.COMPLEX_UNIT_DIP);
|
||||||
|
m.put("dp", TypedValue.COMPLEX_UNIT_DIP);
|
||||||
|
m.put("sp", TypedValue.COMPLEX_UNIT_SP);
|
||||||
|
m.put("pt", TypedValue.COMPLEX_UNIT_PT);
|
||||||
|
m.put("in", TypedValue.COMPLEX_UNIT_IN);
|
||||||
|
m.put("mm", TypedValue.COMPLEX_UNIT_MM);
|
||||||
|
return Collections.unmodifiableMap(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Initialize pattern for dimension string.
|
||||||
|
private static final Pattern DIMENSION_PATTERN = Pattern.compile("^\\s*(\\d+(\\.\\d+)*)\\s*([a-zA-Z]+)\\s*$");
|
||||||
|
|
||||||
|
public static int toDimensionPixelSize(String dimension, DisplayMetrics metrics, ViewGroup parent, boolean horizontal) {
|
||||||
|
if (dimension.endsWith("%")) {
|
||||||
|
float pct = Float.parseFloat(dimension.substring(0, dimension.length() - 1)) / 100.0f;
|
||||||
|
return (int) (pct * (horizontal ? parent.getMeasuredWidth() : parent.getMeasuredHeight()));
|
||||||
|
}
|
||||||
|
return toDimensionPixelSize(dimension, metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toDimensionPixelSize(String dimension, DisplayMetrics metrics) {
|
||||||
|
// -- Mimics TypedValue.complexToDimensionPixelSize(int data, DisplayMetrics metrics).
|
||||||
|
final float f;
|
||||||
|
if (cached.containsKey(dimension)) {
|
||||||
|
f = cached.get(dimension);
|
||||||
|
} else {
|
||||||
|
InternalDimension internalDimension = toInternalDimension(dimension);
|
||||||
|
final float value = internalDimension.value;
|
||||||
|
f = TypedValue.applyDimension(internalDimension.unit, value, metrics);
|
||||||
|
cached.put(dimension, f);
|
||||||
|
}
|
||||||
|
final int res = (int) (f + 0.5f);
|
||||||
|
if (res != 0) return res;
|
||||||
|
if (f == 0) return 0;
|
||||||
|
if (f > 0) return 1;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float toDimension(String dimension, DisplayMetrics metrics) {
|
||||||
|
if (cached.containsKey(dimension)) return cached.get(dimension);
|
||||||
|
// -- Mimics TypedValue.complexToDimension(int data, DisplayMetrics metrics).
|
||||||
|
InternalDimension internalDimension = toInternalDimension(dimension);
|
||||||
|
float val = TypedValue.applyDimension(internalDimension.unit, internalDimension.value, metrics);
|
||||||
|
cached.put(dimension, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InternalDimension toInternalDimension(String dimension) {
|
||||||
|
// -- Match target against pattern.
|
||||||
|
Matcher matcher = DIMENSION_PATTERN.matcher(dimension);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
// -- Match found.
|
||||||
|
// -- Extract value.
|
||||||
|
float value = Float.valueOf(matcher.group(1));
|
||||||
|
// -- Extract dimension units.
|
||||||
|
String unit = matcher.group(3).toLowerCase();
|
||||||
|
// -- Get Android dimension constant.
|
||||||
|
Integer dimensionUnit = dimensionConstantLookup.get(unit);
|
||||||
|
if (dimensionUnit == null) {
|
||||||
|
// -- Invalid format.
|
||||||
|
throw new NumberFormatException();
|
||||||
|
} else {
|
||||||
|
// -- Return valid dimension.
|
||||||
|
return new InternalDimension(value, dimensionUnit);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e("DimensionConverter", "Invalid number format: " + dimension);
|
||||||
|
// -- Invalid format.
|
||||||
|
throw new NumberFormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InternalDimension {
|
||||||
|
float value;
|
||||||
|
int unit;
|
||||||
|
|
||||||
|
InternalDimension(float value, int unit) {
|
||||||
|
this.value = value;
|
||||||
|
this.unit = unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package io.neomodule.layout.utils;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import io.neomodule.layout.LayoutInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class UniqueId {
|
||||||
|
private static int ID = 52019;
|
||||||
|
|
||||||
|
public static int newId() {
|
||||||
|
return ID++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int idFromString(View view, String id) {
|
||||||
|
if (!(view instanceof ViewGroup)) return 0;
|
||||||
|
Object tag = view.getTag();
|
||||||
|
if (!(tag instanceof LayoutInfo)) return 0; // not inflated by this class
|
||||||
|
LayoutInfo info = (LayoutInfo) view.getTag();
|
||||||
|
if (!info.nameToIdNumber.containsKey(id)) {
|
||||||
|
ViewGroup grp = (ViewGroup) view;
|
||||||
|
for (int i = 0; i < grp.getChildCount(); i++) {
|
||||||
|
int val = idFromString(grp.getChildAt(i), id);
|
||||||
|
if (val != 0) return val;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return info.nameToIdNumber.get(id);
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,8 @@ android {
|
|||||||
applicationId "io.neoterm"
|
applicationId "io.neoterm"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 24
|
versionCode 25
|
||||||
versionName "1.2.2"
|
versionName "1.2.3"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "zh-rCN", "zh-rTW"
|
resConfigs "zh-rCN", "zh-rTW"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.1.3-2'
|
ext.kotlin_version = '1.1.4'
|
||||||
repositories {
|
repositories {
|
||||||
maven { url 'https://dl.google.com/dl/android/maven2/' }
|
maven { url 'https://dl.google.com/dl/android/maven2/' }
|
||||||
jcenter()
|
jcenter()
|
||||||
|
1
neomodule/.gitignore
vendored
Normal file
1
neomodule/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
32
neomodule/build.gradle
Normal file
32
neomodule/build.gradle
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 26
|
||||||
|
buildToolsVersion "26.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 26
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
implementation 'com.android.support:appcompat-v7:26.0.1'
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.0'
|
||||||
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.0'
|
||||||
|
}
|
25
neomodule/proguard-rules.pro
vendored
Normal file
25
neomodule/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in /Users/kiva/devel/android-sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,26 @@
|
|||||||
|
package io.neomodule;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() throws Exception {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("io.neomodule.test", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
2
neomodule/src/main/AndroidManifest.xml
Normal file
2
neomodule/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="io.neomodule" />
|
3
neomodule/src/main/res/values/strings.xml
Normal file
3
neomodule/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">NeoModule</string>
|
||||||
|
</resources>
|
17
neomodule/src/test/java/io/neomodule/ExampleUnitTest.java
Normal file
17
neomodule/src/test/java/io/neomodule/ExampleUnitTest.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package io.neomodule;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() throws Exception {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
include ':app', ':chrome-tabs', ':NeoLang'
|
include ':app', ':chrome-tabs', ':NeoLang', ':NeoModule'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user