8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Nick White
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DynamicLayoutInflator
2 | Inflate android XML layouts at runtime
3 |
4 | Example: (see MainActivity.java for details)
5 |
6 | ```java
7 | protected void onCreate(Bundle savedInstanceState) {
8 | super.onCreate(savedInstanceState);
9 | setContentView(R.layout.activity_main);
10 | RelativeLayout main = (RelativeLayout)findViewById(R.id.main_top);
11 | try {
12 | View view = DynamicLayoutInflator.inflateName(this, "testlayout");
13 | main.addView(view);
14 | // The above two lines could also be written like this, if you know that testlayout is in your assets:
15 | // View view = DynamicLayoutInflator.inflate(this, getAssets().open("testlayout.xml"), main);
16 | DynamicLayoutInflator.setDelegate(view, this);
17 | // If we have , this is how to access it:
18 | TextView someTextView = (TextView)DynamicLayoutInflator.findViewByIdString("message");
19 | } catch (IOException e) {
20 | // This happens if getAssets().open() fails to find your layout
21 | e.printStackTrace();
22 | }
23 |
24 | // Alternatively, you can pass in a String of xml:
25 | RelativeLayout layout = (RelativeLayout)DynamicLayoutInflator.inflate(this, "");
26 | }
27 |
28 | // Since we set this class as the delegate, anything in the layout that has onClick="ohHello(2)" will log "howdy number: 2"
29 | public void ohHello(int i) {
30 | Log.d("nick", "howdy number: " + i);
31 | }
32 | ```
33 |
34 | ## See also:
35 |
36 | [iOS dynamic xml layouts](https://github.com/nickwah/NWLayoutInflator)
37 |
38 | ## License
39 |
40 | Open source under the MIT license. See LICENSE for details.
41 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.nickandjerry.dynamiclayoutinflator"
9 | minSdkVersion 15
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:22.2.0'
25 | }
26 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/nick/files/adt-bundle-mac-x86_64-20131030/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/nickandjerry/dynamiclayoutinflator/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.nickandjerry.dynamiclayoutinflator;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/assets/testlayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nickandjerry/dynamiclayoutinflator/DimensionConverter.java:
--------------------------------------------------------------------------------
1 | package com.nickandjerry.dynamiclayoutinflator;
2 |
3 | import android.util.DisplayMetrics;
4 | import android.util.Log;
5 | import android.util.TypedValue;
6 | import android.view.ViewGroup;
7 |
8 | import java.util.Collections;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 | import java.util.regex.Matcher;
12 | import java.util.regex.Pattern;
13 |
14 | /**
15 | * Created by nick on 8/10/15.
16 | *
17 | * Taken from http://stackoverflow.com/questions/8343971/how-to-parse-a-dimension-string-and-convert-it-to-a-dimension-value
18 | */
19 | public class DimensionConverter {
20 | public static Mapcached = new HashMap<>();
21 |
22 | // -- Initialize dimension string to constant lookup.
23 | public static final Map dimensionConstantLookup = initDimensionConstantLookup();
24 | private static Map initDimensionConstantLookup() {
25 | Map m = new HashMap();
26 | m.put("px", TypedValue.COMPLEX_UNIT_PX);
27 | m.put("dip", TypedValue.COMPLEX_UNIT_DIP);
28 | m.put("dp", TypedValue.COMPLEX_UNIT_DIP);
29 | m.put("sp", TypedValue.COMPLEX_UNIT_SP);
30 | m.put("pt", TypedValue.COMPLEX_UNIT_PT);
31 | m.put("in", TypedValue.COMPLEX_UNIT_IN);
32 | m.put("mm", TypedValue.COMPLEX_UNIT_MM);
33 | return Collections.unmodifiableMap(m);
34 | }
35 | // -- Initialize pattern for dimension string.
36 | private static final Pattern DIMENSION_PATTERN = Pattern.compile("^\\s*(\\d+(\\.\\d+)*)\\s*([a-zA-Z]+)\\s*$");
37 |
38 | public static int stringToDimensionPixelSize(String dimension, DisplayMetrics metrics, ViewGroup parent, boolean horizontal) {
39 | if (dimension.endsWith("%")) {
40 | float pct = Float.parseFloat(dimension.substring(0, dimension.length() - 1)) / 100.0f;
41 | return (int)(pct * (horizontal ? parent.getMeasuredWidth() : parent.getMeasuredHeight()));
42 | }
43 | return stringToDimensionPixelSize(dimension, metrics);
44 | }
45 | public static int stringToDimensionPixelSize(String dimension, DisplayMetrics metrics) {
46 | // -- Mimics TypedValue.complexToDimensionPixelSize(int data, DisplayMetrics metrics).
47 | final float f;
48 | if (cached.containsKey(dimension)) {
49 | f = cached.get(dimension);
50 | } else {
51 | InternalDimension internalDimension = stringToInternalDimension(dimension);
52 | final float value = internalDimension.value;
53 | f = TypedValue.applyDimension(internalDimension.unit, value, metrics);
54 | cached.put(dimension, f);
55 | }
56 | final int res = (int)(f+0.5f);
57 | if (res != 0) return res;
58 | if (f == 0) return 0;
59 | if (f > 0) return 1;
60 | return -1;
61 | }
62 |
63 | public static float stringToDimension(String dimension, DisplayMetrics metrics) {
64 | if (cached.containsKey(dimension)) return cached.get(dimension);
65 | // -- Mimics TypedValue.complexToDimension(int data, DisplayMetrics metrics).
66 | InternalDimension internalDimension = stringToInternalDimension(dimension);
67 | float val = TypedValue.applyDimension(internalDimension.unit, internalDimension.value, metrics);
68 | cached.put(dimension, val);
69 | return val;
70 | }
71 |
72 | private static InternalDimension stringToInternalDimension(String dimension) {
73 | // -- Match target against pattern.
74 | Matcher matcher = DIMENSION_PATTERN.matcher(dimension);
75 | if (matcher.matches()) {
76 | // -- Match found.
77 | // -- Extract value.
78 | float value = Float.valueOf(matcher.group(1)).floatValue();
79 | // -- Extract dimension units.
80 | String unit = matcher.group(3).toLowerCase();
81 | // -- Get Android dimension constant.
82 | Integer dimensionUnit = dimensionConstantLookup.get(unit);
83 | if (dimensionUnit == null) {
84 | // -- Invalid format.
85 | throw new NumberFormatException();
86 | } else {
87 | // -- Return valid dimension.
88 | return new InternalDimension(value, dimensionUnit);
89 | }
90 | } else {
91 | Log.e("DimensionConverter", "Invalid number format: " + dimension);
92 | // -- Invalid format.
93 | throw new NumberFormatException();
94 | }
95 | }
96 |
97 | private static class InternalDimension {
98 | float value;
99 | int unit;
100 |
101 | public InternalDimension(float value, int unit) {
102 | this.value = value;
103 | this.unit = unit;
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nickandjerry/dynamiclayoutinflator/DynamicLayoutInflator.java:
--------------------------------------------------------------------------------
1 | package com.nickandjerry.dynamiclayoutinflator;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.res.Resources;
6 | import android.graphics.Color;
7 | import android.graphics.Typeface;
8 | import android.graphics.drawable.Drawable;
9 | import android.graphics.drawable.GradientDrawable;
10 | import android.graphics.drawable.StateListDrawable;
11 | import android.os.Build;
12 | import android.support.annotation.Nullable;
13 | import android.text.InputType;
14 | import android.text.TextUtils;
15 | import android.util.Log;
16 | import android.util.TypedValue;
17 | import android.view.Gravity;
18 | import android.view.LayoutInflater;
19 | import android.view.View;
20 | import android.view.ViewGroup;
21 | import android.widget.Button;
22 | import android.widget.EditText;
23 | import android.widget.FrameLayout;
24 | import android.widget.ImageView;
25 | import android.widget.LinearLayout;
26 | import android.widget.RelativeLayout;
27 | import android.widget.TextView;
28 |
29 | import org.json.JSONArray;
30 | import org.json.JSONException;
31 | import org.w3c.dom.Document;
32 | import org.w3c.dom.NamedNodeMap;
33 | import org.w3c.dom.Node;
34 | import org.w3c.dom.NodeList;
35 | import org.xml.sax.SAXException;
36 |
37 | import java.io.ByteArrayInputStream;
38 | import java.io.File;
39 | import java.io.FileInputStream;
40 | import java.io.FileNotFoundException;
41 | import java.io.IOException;
42 | import java.io.InputStream;
43 | import java.lang.reflect.Constructor;
44 | import java.lang.reflect.InvocationTargetException;
45 | import java.lang.reflect.Method;
46 | import java.util.HashMap;
47 | import java.util.Map;
48 |
49 | import javax.xml.parsers.DocumentBuilder;
50 | import javax.xml.parsers.DocumentBuilderFactory;
51 | import javax.xml.parsers.ParserConfigurationException;
52 |
53 | /**
54 | * Copyright Nicholas White 2015.
55 | * Source: https://github.com/nickwah/DynamicLayoutInflator
56 | *
57 | * Licensed under the MIT License:
58 | *
59 | Permission is hereby granted, free of charge, to any person obtaining a copy
60 | of this software and associated documentation files (the "Software"), to deal
61 | in the Software without restriction, including without limitation the rights
62 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
63 | copies of the Software, and to permit persons to whom the Software is
64 | furnished to do so, subject to the following conditions:
65 |
66 | The above copyright notice and this permission notice shall be included in
67 | all copies or substantial portions of the Software.
68 |
69 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
70 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
71 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
72 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
73 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
74 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
75 | THE SOFTWARE.
76 | */
77 | public class DynamicLayoutInflator {
78 | private static final String ns = null;
79 | public static final int NO_LAYOUT_RULE = -999;
80 | public static final String[] CORNERS = {"TopLeft", "TopRight", "BottomRight", "BottomLeft"};
81 | public static int highestIdNumberUsed = 1234567;
82 |
83 | public interface ViewParamRunnable {
84 | void apply(View view, String value, ViewGroup parent, Map attrs);
85 | }
86 | public static Map viewRunnables;
87 |
88 | public interface ImageLoader {
89 | void loadImage(ImageView view, String url);
90 | void loadRoundedImage(ImageView view, String url, int radius);
91 | }
92 |
93 | private static ImageLoader imageLoader = null;
94 | public static void setImageLoader(ImageLoader il) {
95 | imageLoader = il;
96 | }
97 |
98 | public static class DynamicLayoutInfo {
99 | public DynamicLayoutInfo() {
100 | nameToIdNumber = new HashMap<>();
101 | }
102 | public HashMap nameToIdNumber;
103 | public Object delegate;
104 | public GradientDrawable bgDrawable;
105 | }
106 |
107 | public static void setDelegate(View root, Object delegate) {
108 | DynamicLayoutInfo info;
109 | if (root.getTag() == null || !(root.getTag() instanceof DynamicLayoutInfo)) {
110 | info = new DynamicLayoutInfo();
111 | root.setTag(info);
112 | } else {
113 | info = (DynamicLayoutInfo)root.getTag();
114 | }
115 | info.delegate = delegate;
116 | }
117 |
118 | public static View inflateName(Context context, String name) {
119 | return inflateName(context, name, null);
120 | }
121 | public static View inflateName(Context context, String name, ViewGroup parent) {
122 | if (name.startsWith("<")) {
123 | // Assume it's XML
124 | return DynamicLayoutInflator.inflate(context, name, parent);
125 | } else {
126 | File savedFile = context.getFileStreamPath(name + ".xml");
127 | try {
128 | InputStream fileStream = new FileInputStream(savedFile);
129 | return DynamicLayoutInflator.inflate(context, fileStream, parent);
130 | } catch (FileNotFoundException e) {
131 | }
132 | try {
133 | InputStream assetStream = context.getAssets().open(name + ".xml");
134 | return DynamicLayoutInflator.inflate(context, assetStream, parent);
135 | } catch (IOException e) {
136 | }
137 | int id = context.getResources().getIdentifier(name, "layout", context.getPackageName());
138 | if (id > 0) {
139 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
140 | return inflater.inflate(id, parent, false);
141 | }
142 | }
143 | return null;
144 | }
145 |
146 | public static View inflate(Context context, File xmlPath) {
147 | InputStream inputStream = null;
148 | try {
149 | inputStream = new FileInputStream(xmlPath);
150 | } catch (FileNotFoundException e) {
151 | e.printStackTrace();
152 | return null;
153 | }
154 | return DynamicLayoutInflator.inflate(context, inputStream);
155 | }
156 |
157 | public static View inflate(Context context, String xml) {
158 | InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
159 | return DynamicLayoutInflator.inflate(context, inputStream);
160 | }
161 |
162 | public static View inflate(Context context, String xml, ViewGroup parent) {
163 | InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
164 | return DynamicLayoutInflator.inflate(context, inputStream, parent);
165 | }
166 |
167 | public static View inflate(Context context, InputStream inputStream) {
168 | return inflate(context, inputStream, null);
169 | }
170 | public static View inflate(Context context, InputStream inputStream, ViewGroup parent) {
171 | try {
172 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
173 | dbf.setNamespaceAware(true);
174 | DocumentBuilder db = dbf.newDocumentBuilder();
175 | Document document = db.parse(inputStream);
176 | try {
177 | return inflate(context, document.getDocumentElement(), parent);
178 | } finally {
179 | inputStream.close();
180 | }
181 | } catch (IOException e) {
182 | e.printStackTrace();
183 | } catch (ParserConfigurationException e) {
184 | e.printStackTrace();
185 | } catch (SAXException e) {
186 | e.printStackTrace();
187 | }
188 | return null;
189 | }
190 |
191 | public static View inflate(Context context, Node node) {
192 | return inflate(context, node, null);
193 | }
194 | public static View inflate(Context context, Node node, ViewGroup parent) {
195 | View mainView = getViewForName(context, node.getNodeName());
196 | if (parent != null) parent.addView(mainView); // have to add to parent to enable certain layout attrs
197 | applyAttributes(mainView, getAttributesMap(node), parent);
198 | if (mainView instanceof ViewGroup && node.hasChildNodes()) {
199 | parseChildren(context, node, (ViewGroup) mainView);
200 | }
201 | return mainView;
202 | }
203 |
204 | private static void parseChildren(Context context, Node node, ViewGroup mainView) {
205 | NodeList nodeList = node.getChildNodes();
206 | for (int i = 0; i < nodeList.getLength(); i++) {
207 | Node currentNode = nodeList.item(i);
208 | if (currentNode.getNodeType() != Node.ELEMENT_NODE) continue;
209 | inflate(context, currentNode, mainView); // this recursively can call parseChildren
210 | }
211 | }
212 |
213 | private static View getViewForName(Context context, String name) {
214 | try {
215 | if (!name.contains(".")) {
216 | name = "android.widget." + name;
217 | }
218 | Class> clazz = Class.forName(name);
219 | Constructor> constructor = clazz.getConstructor(Context.class);
220 | return (View)constructor.newInstance(context);
221 | } catch (ClassNotFoundException e) {
222 | e.printStackTrace();
223 | } catch (NoSuchMethodException e) {
224 | e.printStackTrace();
225 | } catch (InvocationTargetException e) {
226 | e.printStackTrace();
227 | } catch (InstantiationException e) {
228 | e.printStackTrace();
229 | } catch (IllegalAccessException e) {
230 | e.printStackTrace();
231 | }
232 | return null;
233 | }
234 |
235 | private static HashMap getAttributesMap(Node currentNode) {
236 | NamedNodeMap attributeMap = currentNode.getAttributes();
237 | int attributeCount = attributeMap.getLength();
238 | HashMap attributes = new HashMap<>(attributeCount);
239 | for (int j = 0; j < attributeCount; j++) {
240 | Node attr = attributeMap.item(j);
241 | String nodeName = attr.getNodeName();
242 | if (nodeName.startsWith("android:")) nodeName = nodeName.substring(8);
243 | attributes.put(nodeName, attr.getNodeValue());
244 | }
245 | return attributes;
246 | }
247 |
248 | @SuppressLint("NewApi")
249 | private static void applyAttributes(View view, Map attrs, ViewGroup parent) {
250 | if (viewRunnables == null) createViewRunnables();
251 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
252 | int layoutRule;
253 | int marginLeft = 0, marginRight = 0, marginTop = 0, marginBottom = 0,
254 | paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0;
255 | boolean hasCornerRadius = false, hasCornerRadii = false;
256 | for (Map.Entry entry : attrs.entrySet()) {
257 | String attr = entry.getKey();
258 | if (viewRunnables.containsKey(attr)) {
259 | viewRunnables.get(attr).apply(view, entry.getValue(), parent, attrs);
260 | continue;
261 | }
262 | if (attr.startsWith("cornerRadius")) {
263 | hasCornerRadius = true;
264 | hasCornerRadii = !attr.equals("cornerRadius");
265 | continue;
266 | }
267 | layoutRule = NO_LAYOUT_RULE;
268 | boolean layoutTarget = false;
269 | switch (attr) {
270 | case "id":
271 | String idValue = parseId(entry.getValue());
272 | if (parent != null) {
273 | DynamicLayoutInfo info = getDynamicLayoutInfo(parent);
274 | int newId = highestIdNumberUsed++;
275 | view.setId(newId);
276 | info.nameToIdNumber.put(idValue, newId);
277 | }
278 | break;
279 | case "width":
280 | case "layout_width":
281 | switch (entry.getValue()) {
282 | case "wrap_content":
283 | layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
284 | break;
285 | case "fill_parent":
286 | case "match_parent":
287 | layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
288 | break;
289 | default:
290 | layoutParams.width = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics(), parent, true);
291 | break;
292 | }
293 | break;
294 | case "height":
295 | case "layout_height":
296 | switch (entry.getValue()) {
297 | case "wrap_content":
298 | layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
299 | break;
300 | case "fill_parent":
301 | case "match_parent":
302 | layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
303 | break;
304 | default:
305 | layoutParams.height = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics(), parent, false);
306 | break;
307 | }
308 | break;
309 | case "layout_gravity":
310 | if (parent != null && parent instanceof LinearLayout) {
311 | ((LinearLayout.LayoutParams)layoutParams).gravity = parseGravity(entry.getValue());
312 | } else if (parent != null && parent instanceof FrameLayout) {
313 | ((FrameLayout.LayoutParams)layoutParams).gravity = parseGravity(entry.getValue());
314 | }
315 | break;
316 | case "layout_weight":
317 | if (parent != null && parent instanceof LinearLayout) {
318 | ((LinearLayout.LayoutParams)layoutParams).weight = Float.parseFloat(entry.getValue());
319 | }
320 | break;
321 | case "layout_below":
322 | layoutRule = RelativeLayout.BELOW;
323 | layoutTarget = true;
324 | break;
325 | case "layout_above":
326 | layoutRule = RelativeLayout.ABOVE;
327 | layoutTarget = true;
328 | break;
329 | case "layout_toLeftOf":
330 | layoutRule = RelativeLayout.LEFT_OF;
331 | layoutTarget = true;
332 | break;
333 | case "layout_toRightOf":
334 | layoutRule = RelativeLayout.RIGHT_OF;
335 | layoutTarget = true;
336 | break;
337 | case "layout_alignBottom":
338 | layoutRule = RelativeLayout.ALIGN_BOTTOM;
339 | layoutTarget = true;
340 | break;
341 | case "layout_alignTop":
342 | layoutRule = RelativeLayout.ALIGN_TOP;
343 | layoutTarget = true;
344 | break;
345 | case "layout_alignLeft":
346 | case "layout_alignStart":
347 | layoutRule = RelativeLayout.ALIGN_LEFT;
348 | layoutTarget = true;
349 | break;
350 | case "layout_alignRight":
351 | case "layout_alignEnd":
352 | layoutRule = RelativeLayout.ALIGN_RIGHT;
353 | layoutTarget = true;
354 | break;
355 | case "layout_alignParentBottom":
356 | layoutRule = RelativeLayout.ALIGN_PARENT_BOTTOM;
357 | break;
358 | case "layout_alignParentTop":
359 | layoutRule = RelativeLayout.ALIGN_PARENT_TOP;
360 | break;
361 | case "layout_alignParentLeft":
362 | case "layout_alignParentStart":
363 | layoutRule = RelativeLayout.ALIGN_PARENT_LEFT;
364 | break;
365 | case "layout_alignParentRight":
366 | case "layout_alignParentEnd":
367 | layoutRule = RelativeLayout.ALIGN_PARENT_RIGHT;
368 | break;
369 | case "layout_centerHorizontal":
370 | layoutRule = RelativeLayout.CENTER_HORIZONTAL;
371 | break;
372 | case "layout_centerVertical":
373 | layoutRule = RelativeLayout.CENTER_VERTICAL;
374 | break;
375 | case "layout_centerInParent":
376 | layoutRule = RelativeLayout.CENTER_IN_PARENT;
377 | break;
378 | case "layout_margin":
379 | marginLeft = marginRight = marginTop = marginBottom = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics());
380 | break;
381 | case "layout_marginLeft":
382 | marginLeft = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics(), parent, true);
383 | break;
384 | case "layout_marginTop":
385 | marginTop = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics(), parent, false);
386 | break;
387 | case "layout_marginRight":
388 | marginRight = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics(), parent, true);
389 | break;
390 | case "layout_marginBottom":
391 | marginBottom = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics(), parent, false);
392 | break;
393 | case "padding":
394 | paddingBottom = paddingLeft = paddingRight = paddingTop = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics());
395 | break;
396 | case "paddingLeft":
397 | paddingLeft = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics());
398 | break;
399 | case "paddingTop":
400 | paddingTop = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics());
401 | break;
402 | case "paddingRight":
403 | paddingRight = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics());
404 | break;
405 | case "paddingBottom":
406 | paddingBottom = DimensionConverter.stringToDimensionPixelSize(entry.getValue(), view.getResources().getDisplayMetrics());
407 | break;
408 |
409 | }
410 | if (layoutRule != NO_LAYOUT_RULE && parent instanceof RelativeLayout) {
411 | if (layoutTarget) {
412 | int anchor = idNumFromIdString(parent, parseId(entry.getValue()));
413 | ((RelativeLayout.LayoutParams) layoutParams).addRule(layoutRule, anchor);
414 | } else if (entry.getValue().equals("true")) {
415 | ((RelativeLayout.LayoutParams) layoutParams).addRule(layoutRule);
416 | }
417 | }
418 | }
419 | // TODO: this is a giant mess; come up with a simpler way of deciding what to draw for the background
420 | if (attrs.containsKey("background") || attrs.containsKey("borderColor")) {
421 | String bgValue = attrs.containsKey("background") ? attrs.get("background") : null;
422 | if (bgValue != null && bgValue.startsWith("@drawable/")) {
423 | view.setBackground(getDrawableByName(view, bgValue));
424 | } else if (bgValue == null || bgValue.startsWith("#") || bgValue.startsWith("@color")) {
425 | if (view instanceof Button || attrs.containsKey("pressedColor")) {
426 | int bgColor = parseColor(view, bgValue == null ? "#00000000" : bgValue);
427 | int pressedColor;
428 | if (attrs.containsKey("pressedColor")) {
429 | pressedColor = parseColor(view, attrs.get("pressedColor"));
430 | } else {
431 | pressedColor = adjustBrightness(bgColor, 0.9f);
432 | }
433 | GradientDrawable gd = new GradientDrawable();
434 | gd.setColor(bgColor);
435 | GradientDrawable pressedGd = new GradientDrawable();
436 | pressedGd.setColor(pressedColor);
437 | if (hasCornerRadii) {
438 | float radii[] = new float[8];
439 | for (int i = 0; i < CORNERS.length; i++) {
440 | String corner = CORNERS[i];
441 | if (attrs.containsKey("cornerRadius" + corner)) {
442 | radii[i * 2] = radii[i * 2 + 1] = DimensionConverter.stringToDimension(attrs.get("cornerRadius" + corner), view.getResources().getDisplayMetrics());
443 | }
444 | gd.setCornerRadii(radii);
445 | pressedGd.setCornerRadii(radii);
446 | }
447 | } else if (hasCornerRadius) {
448 | float cornerRadius = DimensionConverter.stringToDimension(attrs.get("cornerRadius"), view.getResources().getDisplayMetrics());
449 | gd.setCornerRadius(cornerRadius);
450 | pressedGd.setCornerRadius(cornerRadius);
451 | }
452 | if (attrs.containsKey("borderColor")) {
453 | String borderWidth = "1dp";
454 | if (attrs.containsKey("borderWidth")) {
455 | borderWidth = attrs.get("borderWidth");
456 | }
457 | int borderWidthPx = DimensionConverter.stringToDimensionPixelSize(borderWidth, view.getResources().getDisplayMetrics());
458 | gd.setStroke(borderWidthPx, parseColor(view, attrs.get("borderColor")));
459 | pressedGd.setStroke(borderWidthPx, parseColor(view, attrs.get("borderColor")));
460 | }
461 | StateListDrawable selector = new StateListDrawable();
462 | selector.addState(new int[]{android.R.attr.state_pressed}, pressedGd);
463 | selector.addState(new int[]{}, gd);
464 | view.setBackground(selector);
465 | getDynamicLayoutInfo(view).bgDrawable = gd;
466 | } else if (hasCornerRadius || attrs.containsKey("borderColor")) {
467 | GradientDrawable gd = new GradientDrawable();
468 | int bgColor = parseColor(view, bgValue == null ? "#00000000" : bgValue);
469 | gd.setColor(bgColor);
470 | if (hasCornerRadii) {
471 | float radii[] = new float[8];
472 | for (int i = 0; i < CORNERS.length; i++) {
473 | String corner = CORNERS[i];
474 | if (attrs.containsKey("cornerRadius" + corner)) {
475 | radii[i * 2] = radii[i * 2 + 1] = DimensionConverter.stringToDimension(attrs.get("cornerRadius" + corner), view.getResources().getDisplayMetrics());
476 | }
477 | gd.setCornerRadii(radii);
478 | }
479 | } else if (hasCornerRadius) {
480 | float cornerRadius = DimensionConverter.stringToDimension(attrs.get("cornerRadius"), view.getResources().getDisplayMetrics());
481 | gd.setCornerRadius(cornerRadius);
482 | }
483 | if (attrs.containsKey("borderColor")) {
484 | String borderWidth = "1dp";
485 | if (attrs.containsKey("borderWidth")) {
486 | borderWidth = attrs.get("borderWidth");
487 | }
488 | gd.setStroke(DimensionConverter.stringToDimensionPixelSize(borderWidth, view.getResources().getDisplayMetrics()),
489 | parseColor(view, attrs.get("borderColor")));
490 | }
491 | view.setBackground(gd);
492 | getDynamicLayoutInfo(view).bgDrawable = gd;
493 | } else {
494 | view.setBackgroundColor(parseColor(view, bgValue));
495 | }
496 | }
497 | }
498 |
499 | if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
500 | ((ViewGroup.MarginLayoutParams) layoutParams).setMargins(marginLeft, marginTop, marginRight, marginBottom);
501 | }
502 | view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
503 | view.setLayoutParams(layoutParams);
504 | }
505 |
506 | private static DynamicLayoutInfo getDynamicLayoutInfo(View parent) {
507 | DynamicLayoutInfo info;
508 | if (parent.getTag() != null && parent.getTag() instanceof DynamicLayoutInfo) {
509 | info = (DynamicLayoutInfo)parent.getTag();
510 | } else {
511 | info = new DynamicLayoutInfo();
512 | parent.setTag(info);
513 | }
514 | return info;
515 | }
516 |
517 | private static View.OnClickListener getClickListener(final ViewGroup myParent, final String methodName) {
518 | return new View.OnClickListener() {
519 | @Override
520 | public void onClick(View view) {
521 | ViewGroup root = myParent;
522 | DynamicLayoutInfo info = null;
523 | while (root != null && (root.getParent() instanceof ViewGroup)) {
524 | if (root.getTag() != null && root.getTag() instanceof DynamicLayoutInfo) {
525 | info = (DynamicLayoutInfo)root.getTag();
526 | if (info.delegate != null) break;
527 | }
528 | root = (ViewGroup)root.getParent();
529 | }
530 | if (info != null && info.delegate != null) {
531 | final Object delegate = info.delegate;
532 | invokeMethod(delegate, methodName, false, view);
533 | } else {
534 | Log.e("DynamicLayoutInflator", "Unable to find valid delegate for click named " + methodName);
535 | }
536 | }
537 |
538 | private void invokeMethod(Object delegate, final String methodName, boolean withView, View view) {
539 | Object[] args = null;
540 | String finalMethod = methodName;
541 | if (methodName.endsWith(")")) {
542 | String[] parts = methodName.split("[(]", 2);
543 | finalMethod = parts[0];
544 | try {
545 | String argText = parts[1].replace(""", "\"");
546 | JSONArray arr = new JSONArray("[" + argText.substring(0, argText.length() - 1) + "]");
547 | args = new Object[arr.length()];
548 | for (int i = 0; i < arr.length(); i++) {
549 | args[i] = arr.get(i);
550 | }
551 | } catch (JSONException e) {
552 | e.printStackTrace();
553 | }
554 | } else if (withView) {
555 | args = new Object[1];
556 | args[0] = view;
557 | }
558 | Class> klass = delegate.getClass();
559 | try {
560 |
561 | Class>[] argClasses = null;
562 | if (args != null && args.length > 0) {
563 | argClasses = new Class[args.length];
564 | if (withView) {
565 | argClasses[0] = View.class;
566 | } else {
567 | for (int i = 0; i < args.length; i++) {
568 | Class> argClass = args[i].getClass();
569 | if (argClass == Integer.class)
570 | argClass = int.class; // Nobody uses Integer...
571 | argClasses[i] = argClass;
572 | }
573 | }
574 | }
575 | Method method = klass.getMethod(finalMethod, argClasses);
576 | method.invoke(delegate, args);
577 | } catch (IllegalAccessException e) {
578 | e.printStackTrace();
579 | } catch (InvocationTargetException e) {
580 | e.printStackTrace();
581 | } catch (NoSuchMethodException e) {
582 | e.printStackTrace();
583 | if (!withView && !methodName.endsWith(")")) {
584 | invokeMethod(delegate, methodName, true, view);
585 | }
586 | }
587 | }
588 | };
589 | }
590 |
591 | private static String parseId(String value) {
592 | if (value.startsWith("@+id/")) {
593 | return value.substring(5);
594 | } else if (value.startsWith("@id/")) {
595 | return value.substring(4);
596 | }
597 | return value;
598 | }
599 |
600 | private static int parseGravity(String value) {
601 | int gravity = Gravity.NO_GRAVITY;
602 | String[] parts = value.toLowerCase().split("[|]");
603 | for (String part : parts) {
604 | switch (part) {
605 | case "center":
606 | gravity = gravity | Gravity.CENTER;
607 | break;
608 | case "left":
609 | case "textStart":
610 | gravity = gravity | Gravity.LEFT;
611 | break;
612 | case "right":
613 | case "textEnd":
614 | gravity = gravity | Gravity.RIGHT;
615 | break;
616 | case "top":
617 | gravity = gravity | Gravity.TOP;
618 | break;
619 | case "bottom":
620 | gravity = gravity | Gravity.BOTTOM;
621 | break;
622 | case "center_horizontal":
623 | gravity = gravity | Gravity.CENTER_HORIZONTAL;
624 | break;
625 | case "center_vertical":
626 | gravity = gravity | Gravity.CENTER_VERTICAL;
627 | break;
628 | }
629 | }
630 | return gravity;
631 | }
632 |
633 | public static int idNumFromIdString(View view, String id) {
634 | if (!(view instanceof ViewGroup)) return 0;
635 | Object tag = view.getTag();
636 | if (!(tag instanceof DynamicLayoutInfo)) return 0; // not inflated by this class
637 | DynamicLayoutInfo info = (DynamicLayoutInfo)view.getTag();
638 | if (!info.nameToIdNumber.containsKey(id)) {
639 | ViewGroup grp = (ViewGroup)view;
640 | for (int i = 0; i < grp.getChildCount(); i++) {
641 | int val = idNumFromIdString(grp.getChildAt(i), id);
642 | if (val != 0) return val;
643 | }
644 | return 0;
645 | }
646 | return info.nameToIdNumber.get(id);
647 | }
648 |
649 | @Nullable
650 | public static View findViewByIdString(View view, String id) {
651 | int idNum = idNumFromIdString(view, id);
652 | if (idNum == 0) return null;
653 | return view.findViewById(idNum);
654 | }
655 |
656 | public static int parseColor(View view, String text) {
657 | if (text.startsWith("@color/")) {
658 | Resources resources = view.getResources();
659 | return resources.getColor(resources.getIdentifier(text.substring("@color/".length()), "color", view.getContext().getPackageName()));
660 | }
661 | if (text.length() == 4 && text.startsWith("#")) {
662 | text = "#" + text.charAt(1) + text.charAt(1) + text.charAt(2) + text.charAt(2) + text.charAt(3) + text.charAt(3);
663 | }
664 | return Color.parseColor(text);
665 | }
666 |
667 | public static int adjustBrightness(int color, float amount) {
668 | int red = color & 0xFF0000 >> 16;
669 | int green = color & 0x00FF00 >> 8;
670 | int blue = color & 0x0000FF;
671 | int result = (int)(blue * amount);
672 | result += (int)(green * amount) << 8;
673 | result += (int)(red * amount) << 16;
674 | return result;
675 | }
676 |
677 | public static Drawable getDrawableByName(View view, String name) {
678 | Resources resources = view.getResources();
679 | return resources.getDrawable(resources.getIdentifier(name, "drawable",
680 | view.getContext().getPackageName()));
681 | }
682 |
683 | public static void createViewRunnables() {
684 | viewRunnables = new HashMap<>(30);
685 | viewRunnables.put("scaleType", new ViewParamRunnable() {
686 | @Override
687 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
688 | if (view instanceof ImageView) {
689 | ImageView.ScaleType scaleType = ((ImageView)view).getScaleType();
690 | switch (value.toLowerCase()) {
691 | case "center":
692 | scaleType = ImageView.ScaleType.CENTER;
693 | break;
694 | case "center_crop":
695 | scaleType = ImageView.ScaleType.CENTER_CROP;
696 | break;
697 | case "center_inside":
698 | scaleType = ImageView.ScaleType.CENTER_INSIDE;
699 | break;
700 | case "fit_center":
701 | scaleType = ImageView.ScaleType.FIT_CENTER;
702 | break;
703 | case "fit_end":
704 | scaleType = ImageView.ScaleType.FIT_END;
705 | break;
706 | case "fit_start":
707 | scaleType = ImageView.ScaleType.FIT_START;
708 | break;
709 | case "fit_xy":
710 | scaleType = ImageView.ScaleType.FIT_XY;
711 | break;
712 | case "matrix":
713 | scaleType = ImageView.ScaleType.MATRIX;
714 | break;
715 | }
716 | ((ImageView) view).setScaleType(scaleType);
717 | }
718 | }
719 | });
720 | viewRunnables.put("orientation", new ViewParamRunnable() {
721 | @Override
722 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
723 | if (view instanceof LinearLayout) {
724 | ((LinearLayout)view).setOrientation(value.equals("vertical") ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
725 | }
726 | }
727 | });
728 | viewRunnables.put("text", new ViewParamRunnable() {
729 | @Override
730 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
731 | if (view instanceof TextView) {
732 | ((TextView)view).setText(value);
733 | }
734 | }
735 | });
736 | viewRunnables.put("textSize", new ViewParamRunnable() {
737 | @Override
738 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
739 | if (view instanceof TextView) {
740 | ((TextView)view).setTextSize(TypedValue.COMPLEX_UNIT_PX, DimensionConverter.stringToDimension(value, view.getResources().getDisplayMetrics()));
741 | }
742 | }
743 | });
744 | viewRunnables.put("textColor", new ViewParamRunnable() {
745 | @Override
746 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
747 | if (view instanceof TextView) {
748 | ((TextView)view).setTextColor(parseColor(view, value));
749 | }
750 | }
751 | });
752 | viewRunnables.put("textStyle", new ViewParamRunnable() {
753 | @Override
754 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
755 | if (view instanceof TextView) {
756 | int typeFace = Typeface.NORMAL;
757 | if (value.contains("bold")) typeFace |= Typeface.BOLD;
758 | else if (value.contains("italic")) typeFace |= Typeface.ITALIC;
759 | ((TextView) view).setTypeface(null, typeFace);
760 | }
761 | }
762 | });
763 | viewRunnables.put("textAlignment", new ViewParamRunnable() {
764 | @Override
765 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
766 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
767 | int alignment = View.TEXT_ALIGNMENT_TEXT_START;
768 | switch (value) {
769 | case "center":
770 | alignment = View.TEXT_ALIGNMENT_CENTER;
771 | break;
772 | case "left":
773 | case "textStart":
774 | break;
775 | case "right":
776 | case "textEnd":
777 | alignment = View.TEXT_ALIGNMENT_TEXT_END;
778 | break;
779 | }
780 | view.setTextAlignment(alignment);
781 | } else {
782 | int gravity = Gravity.LEFT;
783 | switch (value) {
784 | case "center":
785 | gravity = Gravity.CENTER;
786 | break;
787 | case "left":
788 | case "textStart":
789 | break;
790 | case "right":
791 | case "textEnd":
792 | gravity = Gravity.RIGHT;
793 | break;
794 | }
795 | ((TextView)view).setGravity(gravity);
796 | }
797 | }
798 | });
799 | viewRunnables.put("ellipsize", new ViewParamRunnable() {
800 | @Override
801 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
802 | if (view instanceof TextView) {
803 | TextUtils.TruncateAt where = TextUtils.TruncateAt.END;
804 | switch (value) {
805 | case "start":
806 | where = TextUtils.TruncateAt.START;
807 | break;
808 | case "middle":
809 | where = TextUtils.TruncateAt.MIDDLE;
810 | break;
811 | case "marquee":
812 | where = TextUtils.TruncateAt.MARQUEE;
813 | break;
814 | case "end":
815 | break;
816 | }
817 | ((TextView) view).setEllipsize(where);
818 | }
819 | }
820 | });
821 | viewRunnables.put("singleLine", new ViewParamRunnable() {
822 | @Override
823 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
824 | if (view instanceof TextView) {
825 | ((TextView)view).setSingleLine();
826 | }
827 | }
828 | });
829 | viewRunnables.put("hint", new ViewParamRunnable() {
830 | @Override
831 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
832 | if (view instanceof EditText) {
833 | ((EditText)view).setHint(value);
834 | }
835 | }
836 | });
837 | viewRunnables.put("inputType", new ViewParamRunnable() {
838 | @Override
839 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
840 | if (view instanceof TextView) {
841 | int inputType = 0;
842 | switch (value) {
843 | case "textEmailAddress":
844 | inputType |= InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
845 | break;
846 | case "number":
847 | inputType |= InputType.TYPE_CLASS_NUMBER;
848 | break;
849 | case "phone":
850 | inputType |= InputType.TYPE_CLASS_PHONE;
851 | break;
852 | }
853 | if (inputType > 0) ((TextView)view).setInputType(inputType);
854 | }
855 | }
856 | });
857 | viewRunnables.put("gravity", new ViewParamRunnable() {
858 | @Override
859 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
860 | int gravity = parseGravity(value);
861 | if (view instanceof TextView) {
862 | ((TextView) view).setGravity(gravity);
863 | } else if (view instanceof LinearLayout) {
864 | ((LinearLayout)view).setGravity(gravity);
865 | } else if (view instanceof RelativeLayout) {
866 | ((RelativeLayout)view).setGravity(gravity);
867 | }
868 | }
869 | });
870 | viewRunnables.put("src", new ViewParamRunnable() {
871 | @Override
872 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
873 | if (view instanceof ImageView) {
874 | String imageName = value;
875 | if (imageName.startsWith("//")) imageName = "http:" + imageName;
876 | if (imageName.startsWith("http")) {
877 | if (imageLoader != null) {
878 | if (attrs.containsKey("cornerRadius")) {
879 | int radius = DimensionConverter.stringToDimensionPixelSize(attrs.get("cornerRadius"), view.getResources().getDisplayMetrics());
880 | imageLoader.loadRoundedImage((ImageView)view, imageName, radius);
881 | } else {
882 | imageLoader.loadImage((ImageView) view, imageName);
883 | }
884 | }
885 | } else if (imageName.startsWith("@drawable/")) {
886 | imageName = imageName.substring("@drawable/".length());
887 | ((ImageView)view).setImageDrawable(getDrawableByName(view, imageName));
888 | }
889 | }
890 | }
891 | });
892 | viewRunnables.put("visibility", new ViewParamRunnable() {
893 | @Override
894 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
895 | int visibility = View.VISIBLE;
896 | String visValue = value.toLowerCase();
897 | if (visValue.equals("gone")) visibility = View.GONE;
898 | else if (visValue.equals("invisible")) visibility = View.INVISIBLE;
899 | view.setVisibility(visibility);
900 | }
901 | });
902 | viewRunnables.put("clickable", new ViewParamRunnable() {
903 | @Override
904 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
905 | view.setClickable(value.equals("true"));
906 | }
907 | });
908 | viewRunnables.put("tag", new ViewParamRunnable() {
909 | @Override
910 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
911 | // Sigh, this is dangerous because we use tags for other purposes
912 | if (view.getTag() == null) view.setTag(value);
913 | }
914 | });
915 | viewRunnables.put("onClick", new ViewParamRunnable() {
916 | @Override
917 | public void apply(View view, String value, ViewGroup parent, Map attrs) {
918 | view.setOnClickListener(getClickListener(parent, value));
919 | }
920 | });
921 | }
922 |
923 | }
924 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nickandjerry/dynamiclayoutinflator/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.nickandjerry.dynamiclayoutinflator;
2 |
3 | import android.support.v7.app.ActionBarActivity;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import android.view.Menu;
7 | import android.view.MenuItem;
8 | import android.view.View;
9 | import android.widget.RelativeLayout;
10 |
11 | import java.io.IOException;
12 | import java.io.InputStreamReader;
13 |
14 |
15 | public class MainActivity extends ActionBarActivity {
16 |
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setContentView(R.layout.activity_main);
21 | RelativeLayout main = (RelativeLayout)findViewById(R.id.main_top);
22 | try {
23 | View view = DynamicLayoutInflator.inflate(this, getAssets().open("testlayout.xml"), main);
24 | DynamicLayoutInflator.setDelegate(view, this);
25 | } catch (IOException e) {
26 | e.printStackTrace();
27 | }
28 | }
29 |
30 | public void ohHello() {
31 | Log.d("nick", "howdy");
32 | }
33 |
34 | public void logStringNum(String text, int num) {
35 | Log.d("nick", "logging: " + text + " - " + num);
36 | }
37 |
38 | @Override
39 | public boolean onCreateOptionsMenu(Menu menu) {
40 | // Inflate the menu; this adds items to the action bar if it is present.
41 | getMenuInflater().inflate(R.menu.menu_main, menu);
42 | return true;
43 | }
44 |
45 | @Override
46 | public boolean onOptionsItemSelected(MenuItem item) {
47 | // Handle action bar item clicks here. The action bar will
48 | // automatically handle clicks on the Home/Up button, so long
49 | // as you specify a parent activity in AndroidManifest.xml.
50 | int id = item.getItemId();
51 |
52 | //noinspection SimplifiableIfStatement
53 | if (id == R.id.action_settings) {
54 | return true;
55 | }
56 |
57 | return super.onOptionsItemSelected(item);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickwah/DynamicLayoutInflator/409dc6f56c941a74ad66afde8b8463af635bc930/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickwah/DynamicLayoutInflator/409dc6f56c941a74ad66afde8b8463af635bc930/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickwah/DynamicLayoutInflator/409dc6f56c941a74ad66afde8b8463af635bc930/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickwah/DynamicLayoutInflator/409dc6f56c941a74ad66afde8b8463af635bc930/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DynamicLayoutInflator
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickwah/DynamicLayoutInflator/409dc6f56c941a74ad66afde8b8463af635bc930/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 12 12:45:55 PDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------