* refactor: move library dynamic layout inflater to autojs module

* fix: ScriptExecuteActivity memory leak
* fix: InterruptedException on script exit event
This commit is contained in:
hyb1996
2018-01-04 16:22:38 +08:00
parent 7145faa533
commit 2d287f720c
38 changed files with 2677 additions and 59 deletions

View File

@@ -40,7 +40,6 @@ dependencies {
})
testCompile 'junit:junit:4.12'
compile fileTree(dir: 'libs', include: ['*.jar'])
compile('com.github.hyb1996:DynamicLayoutInflator:0.26')
compile 'org.greenrobot:eventbus:3.0.0'
compile 'net.lingala.zip4j:zip4j:1.3.2'
compile('com.afollestad.material-dialogs:core:0.9.2.3', {

View File

@@ -73,14 +73,11 @@ public class ScreenCapturer {
setImageListener(mHandler);
return;
}
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mImageAcquireLooper = Looper.myLooper();
setImageListener(new Handler());
Looper.loop();
}
new Thread(() -> {
Looper.prepare();
mImageAcquireLooper = Looper.myLooper();
setImageListener(new Handler());
Looper.loop();
}).start();
}

View File

@@ -2,6 +2,7 @@ package com.stardust.autojs.core.looper;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.autojs.runtime.api.Threads;
@@ -13,7 +14,7 @@ import com.stardust.lang.ThreadCompat;
* Created by Stardust on 2017/7/29.
*/
public class Loopers {
public class Loopers implements MessageQueue.IdleHandler {
public interface LooperQuitHandler {
boolean shouldQuit();
@@ -30,6 +31,7 @@ public class Loopers {
private Handler mMainHandler;
private Looper mMainLooper;
private Threads mThreads;
private MessageQueue mMainMessageQueue;
public Loopers(ScriptRuntime runtime) {
mTimers = runtime.timers;
@@ -38,6 +40,7 @@ public class Loopers {
prepare();
mMainLooper = Looper.myLooper();
mMainHandler = new Handler();
mMainMessageQueue = Looper.myQueue();
}
@@ -92,33 +95,37 @@ public class Loopers {
waitWhenIdle.set(b);
}
public void quitAll() {
public void recycle() {
quitServantLooper();
mMainMessageQueue.removeIdleHandler(this);
}
public void setMainLooperQuitHandler(LooperQuitHandler mainLooperQuitHandler) {
mMainLooperQuitHandler = mainLooperQuitHandler;
}
@Override
public boolean queueIdle() {
Looper l = Looper.myLooper();
if (l == null)
return true;
if (l == mMainLooper) {
if (shouldQuitLooper() && !mThreads.hasRunningThreads() &&
mMainLooperQuitHandler != null && mMainLooperQuitHandler.shouldQuit()) {
l.quit();
}
} else {
if (shouldQuitLooper()) {
l.quit();
}
}
return true;
}
public void prepare() {
if (Looper.myLooper() == null)
Looper.prepare();
Looper.myQueue().addIdleHandler(() -> {
Looper l = Looper.myLooper();
if (l == null)
return true;
if (l == mMainLooper) {
if (shouldQuitLooper() && !mThreads.hasRunningThreads() &&
mMainLooperQuitHandler != null && mMainLooperQuitHandler.shouldQuit()) {
l.quit();
}
} else {
if (shouldQuitLooper()) {
l.quit();
}
}
return true;
});
Looper.myQueue().addIdleHandler(this);
waitWhenIdle.set(Looper.myLooper() == Looper.getMainLooper());
}

View File

@@ -1,23 +1,14 @@
package com.stardust.autojs.core.ui;
import android.content.Context;
import android.text.InputType;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.stardust.autojs.core.ui.inflater.DynamicLayoutInflater;
import com.stardust.autojs.core.ui.widget.JsFrameLayout;
import com.stardust.autojs.core.ui.widget.JsImageView;
import com.stardust.autojs.core.ui.xml.JsImageViewAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.JsImageViewAttrSetter;
import com.stardust.autojs.core.ui.xml.XmlConverter;
import com.stardust.util.MapEntries;
import org.autojs.dynamiclayoutinflater.DynamicLayoutInflater;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
/**
* Created by Stardust on 2017/5/14.

View File

@@ -1,13 +1,10 @@
package com.stardust.autojs.core.ui;
import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import org.autojs.dynamiclayoutinflater.util.Ids;
import com.stardust.autojs.core.ui.inflater.util.Ids;
/**

View File

@@ -0,0 +1,236 @@
package com.stardust.autojs.core.ui.inflater;
import android.content.Context;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InflateException;
import android.view.View;
import android.view.ViewGroup;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
import com.stardust.autojs.core.ui.inflater.attrsetter.BaseViewAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.DatePickerAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.FrameLayoutAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.ImageViewAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.LinearLayoutAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.ProgressBarAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.RadioGroupAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.SpinnerAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.TextViewAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.TimePickerAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.ToolbarAttrSetter;
import com.stardust.autojs.core.ui.inflater.attrsetter.ViewGroupAttrSetter;
import com.stardust.autojs.core.ui.inflater.util.Res;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
/**
* Copyright Nicholas White 2015.
* Source: https://github.com/nickwah/DynamicLayoutInflator
* <p>
* Licensed under the MIT License:
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
public class DynamicLayoutInflater {
private static final String LOG_TAG = "DynamicLayoutInflater";
private Map<String, ViewAttrSetter<?>> mViewAttrSetters = new HashMap<>();
private Map<String, com.stardust.autojs.core.ui.inflater.ViewCreator<?>> mViewCreators = new HashMap<>();
private Context mContext;
public DynamicLayoutInflater(Context context) {
mContext = context;
registerViewAttrSetter(TextView.class.getName(), new TextViewAttrSetter<>());
registerViewAttrSetter(EditText.class.getName(), new TextViewAttrSetter<>());
registerViewAttrSetter(ImageView.class.getName(), new ImageViewAttrSetter<>());
registerViewAttrSetter(LinearLayout.class.getName(), new LinearLayoutAttrSetter<>());
registerViewAttrSetter(FrameLayout.class.getName(), new FrameLayoutAttrSetter<>());
registerViewAttrSetter(View.class.getName(), new BaseViewAttrSetter<>());
registerViewAttrSetter(Toolbar.class.getName(), new ToolbarAttrSetter<>());
registerViewAttrSetter(DatePicker.class.getName(), new DatePickerAttrSetter());
registerViewAttrSetter(RadioGroup.class.getName(), new RadioGroupAttrSetter<>());
registerViewAttrSetter(ProgressBar.class.getName(), new ProgressBarAttrSetter<>());
registerViewAttrSetter(Spinner.class.getName(), new SpinnerAttrSetter());
registerViewAttrSetter(TimePicker.class.getName(), new TimePickerAttrSetter());
}
public void registerViewAttrSetter(String fullName, ViewAttrSetter<?> setter) {
mViewAttrSetters.put(fullName, setter);
com.stardust.autojs.core.ui.inflater.ViewCreator<?> creator = setter.getCreator();
if (creator != null) {
mViewCreators.put(fullName, creator);
}
}
public View inflate(String xml) {
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
return inflate(inputStream);
}
public View inflate(String xml, ViewGroup parent) {
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
return inflate(inputStream, parent);
}
public View inflate(InputStream inputStream) {
return inflate(inputStream, null);
}
public View inflate(InputStream inputStream, ViewGroup parent) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(inputStream);
return inflate(document.getDocumentElement(), parent);
} catch (Exception e) {
throw new InflateException(e);
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@SuppressWarnings("unchecked")
private View inflate(Node node, ViewGroup parent) {
HashMap<String, String> attrs = getAttributesMap(node);
View view = createViewForName(node.getNodeName(), attrs);
if (parent != null) {
parent.addView(view); // have to add to parent to enable certain layout attrs
}
ViewAttrSetter<View> setter = (ViewAttrSetter<View>) getViewAttrSetter(view);
applyAttributes(view, setter, attrs, parent);
if (view instanceof ViewGroup && node.hasChildNodes()) {
inflateChildren(node, (ViewGroup) view);
if (setter instanceof ViewGroupAttrSetter) {
((ViewGroupAttrSetter) setter).applyPendingAttributesAboutChildren((ViewGroup) view);
}
}
return view;
}
@Nullable
private ViewAttrSetter<?> getViewAttrSetter(View view) {
ViewAttrSetter<?> setter = mViewAttrSetters.get(view.getClass().getName());
Class c = view.getClass();
while (setter == null && c != View.class) {
c = c.getSuperclass();
setter = mViewAttrSetters.get(c.getName());
}
return setter;
}
private void inflateChildren(Node node, ViewGroup parent) {
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(currentNode, parent); // this recursively can call inflateChildren
}
}
private View createViewForName(String name, HashMap<String, String> attrs) {
try {
if (name.equals("View")) {
return new View(mContext);
}
if (!name.contains(".")) {
name = "android.widget." + name;
}
ViewCreator<?> creator = mViewCreators.get(name);
if (creator != null) {
return creator.create(mContext, attrs);
}
Class<?> clazz = Class.forName(name);
String style = attrs.get("style");
if (style == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return (View) clazz.getConstructor(Context.class).newInstance(mContext);
} else {
int styleRes = Res.parseStyle(mContext, style);
return (View) clazz.getConstructor(Context.class, AttributeSet.class, int.class, int.class)
.newInstance(mContext, null, 0, styleRes);
}
} catch (Exception e) {
throw new InflateException(e);
}
}
private HashMap<String, String> getAttributesMap(Node currentNode) {
NamedNodeMap attributeMap = currentNode.getAttributes();
int attributeCount = attributeMap.getLength();
HashMap<String, String> attributes = new HashMap<>(attributeCount);
for (int j = 0; j < attributeCount; j++) {
Node attr = attributeMap.item(j);
String nodeName = attr.getNodeName();
attributes.put(nodeName, attr.getNodeValue());
}
return attributes;
}
@SuppressWarnings("unchecked")
private void applyAttributes(View view, ViewAttrSetter<View> setter, Map<String, String> attrs, ViewGroup parent) {
if (setter != null) {
for (Map.Entry<String, String> entry : attrs.entrySet()) {
String[] attr = entry.getKey().split(":");
if (attr.length == 1) {
setter.setAttr(view, attr[0], entry.getValue(), parent, attrs);
} else if (attr.length == 2) {
setter.setAttr(view, attr[0], attr[1], entry.getValue(), parent, attrs);
} else {
throw new InflateException("illegal attr name: " + entry.getKey());
}
}
setter.applyPendingAttributes(view, parent);
} else {
Log.e(LOG_TAG, "cannot set attributes for view: " + view.getClass());
}
}
}

View File

@@ -0,0 +1,33 @@
package com.stardust.autojs.core.ui.inflater;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.view.View;
import android.widget.ImageView;
/**
* Created by Stardust on 2017/11/3.
*/
public interface ImageLoader {
interface BitmapCallback {
void onLoaded(Bitmap bitmap);
}
interface DrawableCallback {
void onLoaded(Drawable drawable);
}
void loadInto(ImageView view, Uri uri);
void loadIntoBackground(View view, Uri uri);
Drawable load(View view, Uri uri);
void load(View view, Uri uri, DrawableCallback callback);
void load(View view, Uri uri, BitmapCallback callback);
}

View File

@@ -0,0 +1,25 @@
package com.stardust.autojs.core.ui.inflater;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import java.util.Map;
/**
* Created by Stardust on 2017/11/3.
*/
public interface ViewAttrSetter<V extends View> {
boolean setAttr(V view, String attrName, String value, ViewGroup parent, Map<String, String> attrs);
boolean setAttr(V view, String ns, String attrName, String value, ViewGroup parent, Map<String, String> attrs);
void applyPendingAttributes(V view, ViewGroup parent);
@Nullable
ViewCreator<V> getCreator();
}

View File

@@ -0,0 +1,16 @@
package com.stardust.autojs.core.ui.inflater;
import android.content.Context;
import android.view.View;
import java.util.Map;
/**
* Created by Stardust on 2017/11/29.
*/
public interface ViewCreator<V extends View> {
V create(Context context, Map<String, String> attrs);
}

View File

@@ -0,0 +1,602 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.os.Build;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.stardust.autojs.core.ui.inflater.ViewAttrSetter;
import com.stardust.autojs.core.ui.inflater.ViewCreator;
import com.stardust.autojs.core.ui.inflater.util.Colors;
import com.stardust.autojs.core.ui.inflater.util.Dimensions;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
import com.stardust.autojs.core.ui.inflater.util.Gravities;
import com.stardust.autojs.core.ui.inflater.util.Ids;
import com.stardust.autojs.core.ui.inflater.util.Strings;
import com.stardust.autojs.core.ui.inflater.util.ValueMapper;
import java.lang.reflect.Method;
import java.util.Map;
/**
* Created by Stardust on 2017/11/3.
*/
public class BaseViewAttrSetter<V extends View> implements ViewAttrSetter<V> {
protected static final ValueMapper<PorterDuff.Mode> TINT_MODES = new ValueMapper<PorterDuff.Mode>("tintMode")
.map("add", PorterDuff.Mode.ADD)
.map("multiply", PorterDuff.Mode.MULTIPLY)
.map("screen", PorterDuff.Mode.SCREEN)
.map("src_atop", PorterDuff.Mode.SRC_ATOP)
.map("src_in", PorterDuff.Mode.SRC_IN)
.map("src_over", PorterDuff.Mode.SRC_OVER);
private static final ValueMapper<Integer> DRAWABLE_CACHE_QUALITIES = new ValueMapper<Integer>("drawingCacheQuality")
.map("auto", View.DRAWING_CACHE_QUALITY_AUTO)
.map("high", View.DRAWING_CACHE_QUALITY_HIGH)
.map("low", View.DRAWING_CACHE_QUALITY_LOW);
private static final ValueMapper<Integer> IMPORTANT_FOR_ACCESSIBILITY = new ValueMapper<Integer>("importantForAccessibility")
.map("auto", 0) //View.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
.map("no", 2) //View.IMPORTANT_FOR_ACCESSIBILITY_NO)
.map("noHideDescendants", 4) //View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
.map("yes", 1); //View.IMPORTANT_FOR_ACCESSIBILITY_YES);
private static final ValueMapper<Integer> LAYOUT_DIRECTIONS = new ValueMapper<Integer>("layoutDirection")
.map("inherit", 2)
.map("locale", 3)
.map("ltr", 0)
.map("rtl", 1);
private static final ValueMapper<Integer> SCROLLBARS_STYLES = new ValueMapper<Integer>("scrollbarStyle")
.map("insideInset", View.SCROLLBARS_INSIDE_INSET)
.map("insideOverlay", View.SCROLLBARS_INSIDE_OVERLAY)
.map("outsideInset", View.SCROLLBARS_OUTSIDE_INSET)
.map("outsideOverlay", View.SCROLLBARS_OUTSIDE_OVERLAY);
private static final ValueMapper<Integer> SCROLL_INDICATORS = new ValueMapper<Integer>("scrollIndicators")
.map("bottom", 2) //View.SCROLL_INDICATOR_BOTTOM)
.map("end", 20) //View.SCROLL_INDICATOR_END)
.map("left", 4) //View.SCROLL_INDICATOR_LEFT)
.map("none", 0)
.map("right", 8) //View.SCROLL_INDICATOR_RIGHT)
.map("start", 10) //View.SCROLL_INDICATOR_START)
.map("top", 1); //View.SCROLL_INDICATOR_TOP)
private static final ValueMapper<Integer> VISIBILITY = new ValueMapper<Integer>("visibility")
.map("visible", View.VISIBLE)
.map("invisible", View.INVISIBLE)
.map("gone", View.GONE);
private static final ValueMapper<Integer> TEXT_DIRECTIONS = new ValueMapper<Integer>("textDirection")
.map("anyRtl", 2)
.map("firstStrong", 1)
.map("firstStrongLtr", 6)
.map("firstStrongRtl", 7)
.map("inherit", 0)
.map("locale", 5)
.map("ltr", 3)
.map("rtl", 4);
private static final ValueMapper<Integer> TEXT_ALIGNMENTS = new ValueMapper<Integer>("textAlignment")
.map("center", 4)
.map("gravity", 1)
.map("inherit", 0)
.map("textEnd", 3)
.map("textStart", 2)
.map("viewEnd", 6)
.map("viewStart", 5);
@Override
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
Integer layoutRule = null;
boolean layoutTarget = false;
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
switch (attr) {
case "id":
view.setId(Ids.parse(value));
break;
case "gravity":
return setGravity(view, value);
case "width":
case "layout_width":
switch (value) {
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 = Dimensions.parseToPixel(value, view.getResources().getDisplayMetrics(), parent, true);
break;
}
break;
case "height":
case "layout_height":
switch (value) {
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 = Dimensions.parseToPixel(value, view.getResources().getDisplayMetrics(), parent, false);
break;
}
break;
case "layout_gravity":
if (parent != null && parent instanceof LinearLayout) {
((LinearLayout.LayoutParams) layoutParams).gravity = Gravities.parse(value);
} else if (parent != null && parent instanceof FrameLayout) {
((FrameLayout.LayoutParams) layoutParams).gravity = Gravities.parse(value);
}
break;
case "layout_weight":
if (parent != null && parent instanceof LinearLayout) {
((LinearLayout.LayoutParams) layoutParams).weight = Float.parseFloat(value);
}
break;
case "layout_below":
layoutRule = RelativeLayout.BELOW;
layoutTarget = true;
break;
case "layout_above":
layoutRule = RelativeLayout.ABOVE;
layoutTarget = true;
break;
case "layout_toLeftOf":
layoutRule = RelativeLayout.LEFT_OF;
layoutTarget = true;
break;
case "layout_toRightOf":
layoutRule = RelativeLayout.RIGHT_OF;
layoutTarget = true;
break;
case "layout_alignBottom":
layoutRule = RelativeLayout.ALIGN_BOTTOM;
layoutTarget = true;
break;
case "layout_alignTop":
layoutRule = RelativeLayout.ALIGN_TOP;
layoutTarget = true;
break;
case "layout_alignLeft":
case "layout_alignStart":
layoutRule = RelativeLayout.ALIGN_LEFT;
layoutTarget = true;
break;
case "layout_alignRight":
case "layout_alignEnd":
layoutRule = RelativeLayout.ALIGN_RIGHT;
layoutTarget = true;
break;
case "layout_alignParentBottom":
layoutRule = RelativeLayout.ALIGN_PARENT_BOTTOM;
break;
case "layout_alignParentTop":
layoutRule = RelativeLayout.ALIGN_PARENT_TOP;
break;
case "layout_alignParentLeft":
case "layout_alignParentStart":
layoutRule = RelativeLayout.ALIGN_PARENT_LEFT;
break;
case "layout_alignParentRight":
case "layout_alignParentEnd":
layoutRule = RelativeLayout.ALIGN_PARENT_RIGHT;
break;
case "layout_centerHorizontal":
layoutRule = RelativeLayout.CENTER_HORIZONTAL;
break;
case "layout_centerVertical":
layoutRule = RelativeLayout.CENTER_VERTICAL;
break;
case "layout_centerInParent":
layoutRule = RelativeLayout.CENTER_IN_PARENT;
break;
case "layout_margin":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
int margin = Dimensions.parseToIntPixel(value, view);
params.bottomMargin = params.leftMargin = params.topMargin = params.rightMargin = margin;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
params.setMarginStart(margin);
params.setMarginEnd(margin);
}
}
break;
case "layout_marginLeft":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.leftMargin = Dimensions.parseToIntPixel(value, view);
}
break;
case "layout_marginTop":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.topMargin = Dimensions.parseToIntPixel(value, view);
}
break;
case "layout_marginRight":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.rightMargin = Dimensions.parseToIntPixel(value, view);
}
break;
case "layout_marginBottom":
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.bottomMargin = Dimensions.parseToIntPixel(value, view);
}
break;
case "padding":
int p = Dimensions.parseToIntPixel(value, view);
view.setPadding(p, p, p, p);
break;
case "paddingLeft":
view.setPadding(Dimensions.parseToIntPixel(value, view), view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom());
break;
case "paddingTop":
view.setPadding(view.getPaddingLeft(), Dimensions.parseToIntPixel(value, view), view.getPaddingRight(), view.getPaddingBottom());
break;
case "paddingRight":
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), Dimensions.parseToIntPixel(value, view), view.getPaddingBottom());
break;
case "paddingBottom":
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), Dimensions.parseToIntPixel(value, view));
break;
case "background":
Drawables.setupWithViewBackground(view, value);
break;
case "accessibilityLiveRegion":
case "accessibilityTraversalAfter":
case "accessibilityTraversalBefore":
Exceptions.unsupports(view, attr, value);
case "alpha":
view.setAlpha(Float.valueOf(value));
break;
case "autofillHints":
case "autofilledHighlight":
Exceptions.unsupports(view, attr, value);
break;
case "backgroundTint":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setBackgroundTintList(ColorStateList.valueOf(Colors.parse(view, value)));
}
break;
case "backgroundTintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setBackgroundTintMode(TINT_MODES.get(value));
}
break;
case "checked":
if (view instanceof CompoundButton) {
((CompoundButton) view).setChecked(Boolean.parseBoolean(value));
}
break;
case "clickable":
view.setClickable(Boolean.valueOf(value));
break;
case "contentDescription":
view.setContentDescription(Strings.parse(view, value));
break;
case "contextClickable":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setContextClickable(Boolean.valueOf(value));
}
break;
case "defaultFocusHighlightEnabled":
Exceptions.unsupports(view, attr, value);
break;
case "drawingCacheQuality":
view.setDrawingCacheQuality(DRAWABLE_CACHE_QUALITIES.get(value));
break;
case "duplicateParentState":
view.setDuplicateParentStateEnabled(Boolean.valueOf(value));
break;
case "elevation":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setElevation(Dimensions.parseToIntPixel(value, view));
}
break;
case "fadeScrollbars":
view.setScrollbarFadingEnabled(Boolean.valueOf(value));
break;
case "fadingEdgeLength":
view.setFadingEdgeLength(Dimensions.parseToIntPixel(value, view));
break;
case "filterTouchesWhenObscured":
view.setFilterTouchesWhenObscured(Boolean.valueOf(value));
break;
case "fitsSystemWindows":
view.setFitsSystemWindows(Boolean.valueOf(value));
break;
case "focusable":
view.setFocusable(Boolean.valueOf(value));
break;
case "focusableInTouchMode":
view.setFocusableInTouchMode(Boolean.valueOf(value));
break;
case "focusedByDefault":
Exceptions.unsupports(view, attr, value);
break;
case "forceHasOverlappingRendering":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
view.forceHasOverlappingRendering(Boolean.valueOf(value));
}
break;
case "foreground":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setForeground(Drawables.parse(view, value));
}
break;
case "foregroundGravity":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setForegroundGravity(Gravities.parse(value));
}
break;
case "foregroundTint":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setForegroundTintList(ColorStateList.valueOf(Colors.parse(view, value)));
}
break;
case "foregroundTintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setForegroundTintMode(TINT_MODES.get(value));
}
break;
case "hapticFeedbackEnabled":
view.setHapticFeedbackEnabled(Boolean.valueOf(value));
break;
case "importantForAccessibility":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY.get(value));
}
break;
case "importantForAutofill":
Exceptions.unsupports(view, attr, value);
break;
case "isScrollContainer":
view.setScrollContainer(Boolean.valueOf(value));
break;
case "keepScreenOn":
view.setKeepScreenOn(Boolean.valueOf(value));
break;
case "keyboardNavigationCluster":
Exceptions.unsupports(view, attr, value);
break;
case "layerType":
Exceptions.unsupports(view, attr, value);
break;
case "layoutDirection":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setLayoutDirection(LAYOUT_DIRECTIONS.get(value));
}
break;
case "longClickable":
view.setLongClickable(Boolean.valueOf(value));
break;
case "minHeight":
view.setMinimumHeight(Dimensions.parseToIntPixel(value, view));
break;
case "minWidth":
view.setMinimumWidth(Dimensions.parseToIntPixel(value, view));
break;
case "nextClusterForward":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusDown":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusForward":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusLeft":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusRight":
Exceptions.unsupports(view, attr, value);
break;
case "nextFocusUp":
Exceptions.unsupports(view, attr, value);
break;
case "onClick":
Exceptions.unsupports(view, attr, value);
break;
case "paddingEnd":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(),
Dimensions.parseToIntPixel(value, view), view.getPaddingBottom());
} else {
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
Dimensions.parseToIntPixel(value, view), view.getPaddingBottom());
}
break;
case "paddingHorizontal":
case "paddingVertical":
Exceptions.unsupports(view, attr, value);
break;
case "paddingStart":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setPaddingRelative(Dimensions.parseToIntPixel(value, view), view.getPaddingTop(),
view.getPaddingEnd(), view.getPaddingBottom());
} else {
view.setPadding(Dimensions.parseToIntPixel(value, view), view.getPaddingTop(),
view.getPaddingRight(), view.getPaddingBottom());
}
break;
case "requiresFadingEdge":
for (String str : value.split("|")) {
if (str.equals("horizontal")) {
view.setHorizontalFadingEdgeEnabled(true);
} else if (str.equals("vertical")) {
view.setVerticalFadingEdgeEnabled(true);
}
}
break;
case "rotation":
view.setRotation(Float.parseFloat(value));
break;
case "rotationX":
view.setRotationX(Float.parseFloat(value));
break;
case "rotationY":
view.setRotationY(Float.parseFloat(value));
break;
case "saveEnabled":
view.setSaveEnabled(Boolean.valueOf(value));
break;
case "scaleX":
view.setScaleX(Dimensions.parseToPixel(value, view));
break;
case "scaleY":
view.setScaleY(Dimensions.parseToPixel(value, view));
break;
case "scrollIndicators":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setScrollIndicators(SCROLL_INDICATORS.get(value));
}
break;
case "scrollX":
view.setScrollX(Dimensions.parseToIntPixel(value, view));
break;
case "scrollY":
view.setScrollY(Dimensions.parseToIntPixel(value, view));
break;
case "scrollbarAlwaysDrawHorizontalTrack":
case "scrollbarAlwaysDrawVerticalTrack":
Exceptions.unsupports(view, attr, value);
break;
case "scrollbarDefaultDelayBeforeFade":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setScrollBarDefaultDelayBeforeFade(Integer.valueOf(value));
}
break;
case "scrollbarFadeDuration":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setScrollBarFadeDuration(Integer.valueOf(value));
}
break;
case "scrollbarSize":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setScrollBarSize(Dimensions.parseToIntPixel(value, view));
}
break;
case "scrollbarStyle":
view.setScrollBarStyle(SCROLLBARS_STYLES.get(value));
break;
case "scrollbarThumbHorizontal":
case "scrollbarThumbVertical":
case "scrollbarTrackHorizontal":
case "scrollbarTrackVertical":
Exceptions.unsupports(view, attr, value);
case "scrollbars":
for (String str : value.split("|")) {
if (str.equals("horizontal")) {
view.setHorizontalScrollBarEnabled(true);
} else if (str.equals("vertical")) {
view.setVerticalScrollBarEnabled(true);
}
}
break;
case "soundEffectsEnabled":
view.setSoundEffectsEnabled(Boolean.valueOf(value));
break;
case "stateListAnimator":
Exceptions.unsupports(view, attr, value);
case "tag":
view.setTag(Strings.parse(view, value));
break;
case "textAlignment":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setTextAlignment(TEXT_ALIGNMENTS.get(value));
}
break;
case "textDirection":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
view.setTextDirection(TEXT_DIRECTIONS.get(value));
}
break;
case "theme":
//Exceptions.unsupports(view, attr, value);
break;
case "tooltipText":
Exceptions.unsupports(view, attr, value);
break;
case "transformPivotX":
view.setPivotX(Dimensions.parseToPixel(value, view));
break;
case "transformPivotY":
view.setPivotY(Dimensions.parseToPixel(value, view));
break;
case "transitionName":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setTransitionName(Strings.parse(view, value));
}
break;
case "translationX":
view.setTranslationX(Dimensions.parseToPixel(value, view));
break;
case "translationY":
view.setTranslationY(Dimensions.parseToPixel(value, view));
break;
case "translationZ":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setTranslationZ(Dimensions.parseToPixel(value, view));
}
break;
case "visibility":
view.setVisibility(VISIBILITY.get(value));
break;
default:
return false;
}
if (layoutRule != null && parent instanceof RelativeLayout) {
if (layoutTarget) {
int anchor = Ids.parse(value);
((RelativeLayout.LayoutParams) layoutParams).addRule(layoutRule, anchor);
} else if (value.equals("true")) {
((RelativeLayout.LayoutParams) layoutParams).addRule(layoutRule);
}
}
return true;
}
@Override
public boolean setAttr(V view, String ns, String attrName, String value, ViewGroup parent, Map<String, String> attrs) {
if (ns.equals("android")) {
return setAttr(view, attrName, value, parent, attrs);
}
return false;
}
private boolean setGravity(V view, String g) {
try {
Method setGravity = view.getClass().getMethod("setGravity", int.class);
setGravity.invoke(view, Gravities.parse(g));
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public void applyPendingAttributes(V view, ViewGroup parent) {
}
@Nullable
@Override
public ViewCreator<V> getCreator() {
return null;
}
}

View File

@@ -0,0 +1,93 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.os.Build;
import android.support.annotation.Nullable;
import android.view.InflateException;
import android.view.View;
import android.view.ViewGroup;
import android.widget.DatePicker;
import com.stardust.autojs.R;
import com.stardust.autojs.core.ui.inflater.ViewCreator;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Map;
/**
* Created by Stardust on 2017/11/29.
*/
public class DatePickerAttrSetter extends BaseViewAttrSetter<DatePicker> {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("mm/dd/yyyy", Locale.getDefault());
@Override
public boolean setAttr(DatePicker view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
switch (attr) {
case "calendarTextColor":
Exceptions.unsupports(view, attr, value);
break;
case "calendarViewShown":
view.setCalendarViewShown(Boolean.parseBoolean(attr));
break;
case "dayOfWeekBackground":
case "dayOfWeekTextAppearance":
case "endYear":
Exceptions.unsupports(view, attr, value);
break;
case "firstDayOfWeek":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setFirstDayOfWeek(Integer.parseInt(value));
}
break;
case "headerBackground":
case "headerDayOfMonthTextAppearance":
case "headerMonthTextAppearance":
case "headerYearTextAppearance":
Exceptions.unsupports(view, attr, value);
break;
case "maxDate":
try {
view.setMaxDate(DATE_FORMAT.parse(value).getTime());
} catch (ParseException e) {
throw new InflateException(e);
}
break;
case "minDate":
try {
view.setMinDate(DATE_FORMAT.parse(value).getTime());
} catch (ParseException e) {
throw new InflateException(e);
}
break;
case "spinnersShown":
view.setSpinnersShown(Boolean.parseBoolean(value));
break;
case "startYear":
case "yearListItemTextAppearance":
case "yearListSelectorColor":
Exceptions.unsupports(view, attr, value);
break;
default:
return super.setAttr(view, attr, value, parent, attrs);
}
return true;
}
@Nullable
@Override
public ViewCreator<DatePicker> getCreator() {
return (context, attrs) -> {
String datePickerMode = attrs.remove("android:datePickerMode");
if (datePickerMode == null || !datePickerMode.equals("spinner")) {
return new DatePicker(context);
}
DatePicker datePicker = (DatePicker) View.inflate(context, R.layout.date_picker_spinner, null);
datePicker.setCalendarViewShown(false);
return datePicker;
};
}
}

View File

@@ -0,0 +1,33 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.view.View;
/**
* Created by Stardust on 2017/11/4.
*/
public class Exceptions {
public interface ExceptionHandler {
boolean handleUnsupportedException(UnsupportedOperationException e, View v, String attrName, String value);
}
private static ExceptionHandler sExceptionHandler;
public static void unsupports(View v, String name, String value) {
UnsupportedOperationException e = new UnsupportedOperationException(String.format("Attr %s:%s=\"%s\" is not supported",
v.getClass().getSimpleName(), name, value));
if (sExceptionHandler == null || !sExceptionHandler.handleUnsupportedException(e, v, name, value)) {
throw e;
}
}
public static ExceptionHandler getExceptionHandler() {
return sExceptionHandler;
}
public static void setExceptionHandler(ExceptionHandler exceptionHandler) {
sExceptionHandler = exceptionHandler;
}
}

View File

@@ -0,0 +1,40 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.stardust.autojs.core.ui.inflater.util.Gravities;
import java.util.Map;
/**
* Created by Stardust on 2017/11/29.
*/
public class FrameLayoutAttrSetter<V extends FrameLayout> extends ViewGroupAttrSetter<V> {
private Integer mGravity;
@Override
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
if (attr.equals("gravity")) {
mGravity = Gravities.parse(value);
return true;
}
return super.setAttr(view, attr, value, parent, attrs);
}
@Override
public void applyPendingAttributesAboutChildren(V view) {
if (mGravity == null)
return;
for (int i = 0; i < view.getChildCount(); i++) {
View child = view.getChildAt(i);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) child.getLayoutParams();
params.gravity = mGravity;
child.setLayoutParams(params);
}
mGravity = null;
}
}

View File

@@ -0,0 +1,106 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.os.Build;
import android.view.InflateException;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.stardust.autojs.core.ui.inflater.util.Colors;
import com.stardust.autojs.core.ui.inflater.util.Dimensions;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
import java.util.Map;
/**
* Created by Stardust on 2017/11/3.
*/
public class ImageViewAttrSetter<V extends ImageView> extends BaseViewAttrSetter<V> {
@Override
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
if (super.setAttr(view, attr, value, parent, attrs)) {
return true;
}
switch (attr) {
case "adjustViewBounds":
view.setAdjustViewBounds(Boolean.valueOf(value));
break;
case "baseline":
view.setBaseline(Dimensions.parseToIntPixel(value, view));
break;
case "baselineAlignBottom":
view.setBaselineAlignBottom(Boolean.valueOf(value));
break;
case "cropToPadding":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setCropToPadding(Boolean.valueOf(value));
}
break;
case "maxHeight":
view.setMaxHeight(Dimensions.parseToIntPixel(value, view));
break;
case "maxWidth":
view.setMaxWidth(Dimensions.parseToIntPixel(value, view));
break;
case "path":
Drawables.setupWithImage(view, wrapAsPath(value));
break;
case "scaleType":
view.setScaleType(parseScaleType(value));
break;
case "src":
Drawables.setupWithImage(view, value);
break;
case "tint":
view.setColorFilter(Colors.parse(view, value));
break;
case "tintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setImageTintMode(TINT_MODES.get(value));
}
break;
case "url":
Drawables.setupWithImage(view, wrapAsUrl(value));
break;
default:
return false;
}
return true;
}
private String wrapAsPath(String value) {
if (!value.startsWith("file://")) {
return "file://" + value;
}
return value;
}
private String wrapAsUrl(String value) {
if (!value.startsWith("http://") && !value.startsWith("https://")) {
return "http://" + value;
}
return value;
}
private ImageView.ScaleType parseScaleType(String value) {
switch (value.toLowerCase()) {
case "center":
return ImageView.ScaleType.CENTER;
case "center_crop":
return ImageView.ScaleType.CENTER_CROP;
case "center_inside":
return ImageView.ScaleType.CENTER_INSIDE;
case "fit_center":
return ImageView.ScaleType.FIT_CENTER;
case "fit_end":
return ImageView.ScaleType.FIT_END;
case "fit_start":
return ImageView.ScaleType.FIT_START;
case "fit_xy":
return ImageView.ScaleType.FIT_XY;
case "matrix":
return ImageView.ScaleType.MATRIX;
}
throw new InflateException("unknown scale type: " + value);
}
}

View File

@@ -1,16 +1,14 @@
package com.stardust.autojs.core.ui.xml;
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import com.makeramen.roundedimageview.Corner;
import com.stardust.autojs.core.ui.inflater.util.Colors;
import com.stardust.autojs.core.ui.inflater.util.Dimensions;
import com.stardust.autojs.core.ui.inflater.util.ValueMapper;
import com.stardust.autojs.core.ui.widget.JsImageView;
import org.autojs.dynamiclayoutinflater.attrsetter.BaseViewAttrSetter;
import org.autojs.dynamiclayoutinflater.attrsetter.ImageViewAttrSetter;
import org.autojs.dynamiclayoutinflater.util.Colors;
import org.autojs.dynamiclayoutinflater.util.Dimensions;
import org.autojs.dynamiclayoutinflater.util.ValueMapper;
import java.util.Map;
@@ -37,6 +35,18 @@ public class JsImageViewAttrSetter<V extends JsImageView> extends ImageViewAttrS
case "radius":
view.setCornerRadius(Dimensions.parseToPixel(value, view));
break;
case "radiusTopLeft":
view.setCornerRadius(Dimensions.parseToPixel(value, view), view.getCornerRadius(Corner.TOP_RIGHT), view.getCornerRadius(Corner.BOTTOM_LEFT), view.getCornerRadius(Corner.BOTTOM_RIGHT));
break;
case "radiusTopRight":
view.setCornerRadius(view.getCornerRadius(Corner.TOP_LEFT), Dimensions.parseToPixel(value, view), view.getCornerRadius(Corner.BOTTOM_LEFT), view.getCornerRadius(Corner.BOTTOM_RIGHT));
break;
case "radiusBottomLeft":
view.setCornerRadius(view.getCornerRadius(Corner.TOP_LEFT), view.getCornerRadius(Corner.TOP_RIGHT), Dimensions.parseToPixel(value, view), view.getCornerRadius(Corner.BOTTOM_RIGHT));
break;
case "radiusBottomRight":
view.setCornerRadius(view.getCornerRadius(Corner.TOP_LEFT), view.getCornerRadius(Corner.TOP_RIGHT), view.getCornerRadius(Corner.BOTTOM_LEFT), Dimensions.parseToPixel(value, view));
break;
case "borderWidth":
view.setBorderWidth(Dimensions.parseToPixel(value, view));
break;

View File

@@ -0,0 +1,51 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
import com.stardust.autojs.core.ui.inflater.util.Gravities;
import com.stardust.autojs.core.ui.inflater.util.ValueMapper;
import java.util.Map;
/**
* Created by Stardust on 2017/11/4.
*/
public class LinearLayoutAttrSetter<V extends LinearLayout> extends ViewGroupAttrSetter<V> {
private static final ValueMapper<Integer> ORIENTATIONS = new ValueMapper<Integer>("orientation")
.map("vertical", LinearLayout.VERTICAL)
.map("horizontal", LinearLayout.HORIZONTAL);
@Override
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
switch (attr) {
case "baselineAligned":
view.setBaselineAligned(Boolean.valueOf(value));
break;
case "baselineAlignedChildIndex":
view.setBaselineAlignedChildIndex(Integer.valueOf(value));
break;
case "divider":
view.setDividerDrawable(Drawables.parse(view, value));
break;
case "gravity":
view.setGravity(Gravities.parse(value));
break;
case "measureWithLargestChild":
view.setMeasureWithLargestChildEnabled(Boolean.valueOf(value));
break;
case "orientation":
view.setOrientation(ORIENTATIONS.get(value));
break;
case "weightSum":
view.setWeightSum(Float.valueOf(value));
break;
default:
return super.setAttr(view, attr, value, parent, attrs);
}
return true;
}
}

View File

@@ -0,0 +1,138 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.util.AttributeSet;
import java.util.ArrayList;
import java.util.Map;
/**
* Created by Stardust on 2017/11/5.
*/
public class MapAttrbuteSet implements AttributeSet {
private Map<String, String> mMap;
private ArrayList<String> mKeys;
private ArrayList<String> mValues;
public MapAttrbuteSet(Map<String, String> map) {
mMap = map;
mKeys = new ArrayList<>(map.size());
mValues = new ArrayList<>(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
mKeys.add(entry.getKey());
mValues.add(entry.getValue());
}
}
@Override
public int getAttributeCount() {
return mKeys.size();
}
@Override
public String getAttributeName(int index) {
return mKeys.get(index);
}
@Override
public String getAttributeValue(int index) {
return mValues.get(index);
}
@Override
public String getAttributeValue(String namespace, String name) {
return mMap.get(name);
}
@Override
public String getPositionDescription() {
return null;
}
@Override
public int getAttributeNameResource(int index) {
return 0;
}
@Override
public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) {
return 0;
}
@Override
public boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue) {
String v = mMap.get(attribute);
return v == null ? defaultValue : Boolean.valueOf(v);
}
@Override
public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
return 0;
}
@Override
public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
return 0;
}
@Override
public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) {
return 0;
}
@Override
public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) {
return 0;
}
@Override
public int getAttributeListValue(int index, String[] options, int defaultValue) {
return 0;
}
@Override
public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
return false;
}
@Override
public int getAttributeResourceValue(int index, int defaultValue) {
return 0;
}
@Override
public int getAttributeIntValue(int index, int defaultValue) {
return 0;
}
@Override
public int getAttributeUnsignedIntValue(int index, int defaultValue) {
return 0;
}
@Override
public float getAttributeFloatValue(int index, float defaultValue) {
return 0;
}
@Override
public String getIdAttribute() {
return null;
}
@Override
public String getClassAttribute() {
return null;
}
@Override
public int getIdAttributeResourceValue(int defaultValue) {
return 0;
}
@Override
public int getStyleAttribute() {
return 0;
}
}

View File

@@ -0,0 +1,119 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.content.res.ColorStateList;
import android.os.Build;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.stardust.autojs.core.ui.inflater.util.Colors;
import com.stardust.autojs.core.ui.inflater.util.Dimensions;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
import java.util.Map;
/**
* Created by Stardust on 2017/11/29.
*/
public class ProgressBarAttrSetter<V extends ProgressBar> extends BaseViewAttrSetter<V> {
@Override
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
switch (attr) {
case "animationResolution":
Exceptions.unsupports(view, attr, value);
break;
case "indeterminate":
view.setIndeterminate(Boolean.parseBoolean(value));
break;
case "indeterminateBehavior":
Exceptions.unsupports(view, attr, value);
break;
case "indeterminateDrawable":
view.setIndeterminateDrawable(Drawables.parse(view, value));
break;
case "indeterminateDuration":
Exceptions.unsupports(view, attr, value);
break;
case "indeterminateOnly":
Exceptions.unsupports(view, attr, value);
break;
case "indeterminateTint":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setIndeterminateTintList(ColorStateList.valueOf(Colors.parse(view, value)));
}
break;
case "indeterminateTintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setIndeterminateTintMode(TINT_MODES.get(value));
}
break;
case "interpolato":
Exceptions.unsupports(view, attr, value);
break;
case "max":
view.setMax(Integer.parseInt(value));
break;
case "maxHeight":
Exceptions.unsupports(view, attr, value);
break;
case "maxWidth":
Exceptions.unsupports(view, attr, value);
break;
case "min":
Exceptions.unsupports(view, attr, value);
break;
case "minHeigh":
view.setMinimumHeight(Dimensions.parseToIntPixel(value, view));
break;
case "minWidth":
view.setMinimumWidth(Dimensions.parseToIntPixel(value, view));
break;
case "mirrorForRtl":
Exceptions.unsupports(view, attr, value);
break;
case "progress":
view.setProgress(Integer.parseInt(value));
break;
case "progressBackgroundTint":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setProgressBackgroundTintList(ColorStateList.valueOf(Colors.parse(view, value)));
}
break;
case "progressBackgroundTintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setProgressBackgroundTintMode(TINT_MODES.get(value));
}
break;
case "progressDrawable":
view.setProgressDrawable(Drawables.parse(view, value));
break;
case "progressTint":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setProgressTintList(ColorStateList.valueOf(Colors.parse(view, value)));
}
break;
case "progressTintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setProgressTintMode(TINT_MODES.get(value));
}
break;
case "secondaryProgress":
view.setSecondaryProgress(Integer.parseInt(value));
break;
case "secondaryProgressTint":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setSecondaryProgressTintList(ColorStateList.valueOf(Colors.parse(view, value)));
}
break;
case "secondaryProgressTintMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setSecondaryProgressTintMode(TINT_MODES.get(value));
}
break;
default:
return super.setAttr(view, attr, value, parent, attrs);
}
return true;
}
}

View File

@@ -0,0 +1,35 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.view.ViewGroup;
import android.widget.RadioGroup;
import com.stardust.autojs.core.ui.inflater.util.Ids;
import java.util.Map;
/**
* Created by Stardust on 2017/11/29.
*/
public class RadioGroupAttrSetter<V extends RadioGroup> extends LinearLayoutAttrSetter<V> {
private Integer mCheckedButton;
@Override
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
if (attr.equals("checkedButton")) {
mCheckedButton = Ids.parse(value);
return true;
} else {
return super.setAttr(view, attr, value, parent, attrs);
}
}
@Override
public void applyPendingAttributesAboutChildren(V view) {
if (mCheckedButton != null) {
view.check(mCheckedButton);
mCheckedButton = null;
}
}
}

View File

@@ -0,0 +1,90 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.content.Context;
import android.os.Build;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.SimpleAdapter;
import android.widget.Spinner;
import com.stardust.autojs.core.ui.inflater.ViewCreator;
import com.stardust.autojs.core.ui.inflater.util.Dimensions;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
import com.stardust.autojs.core.ui.inflater.util.Strings;
import com.stardust.autojs.core.ui.inflater.util.ValueMapper;
import java.util.List;
import java.util.Map;
/**
* Created by Stardust on 2017/11/29.
*/
public class SpinnerAttrSetter extends BaseViewAttrSetter<Spinner> {
protected static final ValueMapper<Integer> SPINNER_MODES = new ValueMapper<Integer>("spinnerMode")
.map("dialog", Spinner.MODE_DIALOG)
.map("dropdown", Spinner.MODE_DROPDOWN);
@Override
public boolean setAttr(Spinner view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
switch (attr) {
case "dropDownHorizontalOffset":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setDropDownHorizontalOffset(Dimensions.parseToIntPixel(value, view));
}
break;
case "dropDownSelector":
Exceptions.unsupports(view, attr, value);
break;
case "dropDownVerticalOffset":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setDropDownVerticalOffset(Dimensions.parseToIntPixel(value, view));
}
break;
case "dropDownWidth":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setDropDownWidth(Dimensions.parseToIntPixel(value, view));
}
break;
case "popupBackground":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setPopupBackgroundDrawable(Drawables.parse(view, value));
}
break;
case "prompt":
view.setPrompt(Strings.parse(view, value));
break;
case "entries":
view.setAdapter(new ArrayAdapter<>(view.getContext(),
android.R.layout.simple_spinner_dropdown_item, value.split("[|]")));
break;
default:
return super.setAttr(view, attr, value, parent, attrs);
}
return true;
}
@Nullable
@Override
public ViewCreator<Spinner> getCreator() {
return new ViewCreator<Spinner>() {
@Override
public Spinner create(Context context, Map<String, String> attrs) {
String mode = attrs.remove("android:spinnerMode");
if (mode == null) {
return new Spinner(context);
}
return new Spinner(context, SPINNER_MODES.get(mode));
}
};
}
private static class EntryAdapter extends SimpleAdapter {
public EntryAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) {
super(context, data, resource, from, to);
}
}
}

View File

@@ -0,0 +1,420 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.text.method.DigitsKeyListener;
import android.text.method.TextKeyListener;
import android.text.util.Linkify;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import com.stardust.autojs.core.ui.inflater.util.Colors;
import com.stardust.autojs.core.ui.inflater.util.Dimensions;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
import com.stardust.autojs.core.ui.inflater.util.Gravities;
import com.stardust.autojs.core.ui.inflater.util.Res;
import com.stardust.autojs.core.ui.inflater.util.Strings;
import com.stardust.autojs.core.ui.inflater.util.ValueMapper;
import java.util.Map;
/**
* Created by Stardust on 2017/11/3.
*/
public class TextViewAttrSetter<V extends TextView> extends BaseViewAttrSetter<V> {
private static final ValueMapper<Integer> AUTO_LINK_MASKS = new ValueMapper<Integer>("autoLink")
.map("all", Linkify.ALL)
.map("email", Linkify.EMAIL_ADDRESSES)
.map("map", Linkify.MAP_ADDRESSES)
.map("none", 0)
.map("phone", Linkify.PHONE_NUMBERS)
.map("web", Linkify.WEB_URLS);
private static final ValueMapper<TextUtils.TruncateAt> ELLIPSIZE = new ValueMapper<TextUtils.TruncateAt>("ellipsize")
.map("end", TextUtils.TruncateAt.END)
.map("marquee", TextUtils.TruncateAt.MIDDLE)
.map("none", null)
.map("start", TextUtils.TruncateAt.START)
.map("middle", TextUtils.TruncateAt.MIDDLE);
private static final ValueMapper<Integer> HYPHENATION_FREQUENCY = new ValueMapper<Integer>("hyphenationFrequency")
.map("full", 2)
.map("none", 0)
.map("normal", 1);
// TODO: 2017/11/4 IME FLAG
private static final ValueMapper<Integer> IME_OPTIONS = new ValueMapper<Integer>("imeOptions")
.map("actionDone", EditorInfo.IME_ACTION_DONE)
.map("actionGo", EditorInfo.IME_ACTION_DONE)
.map("actionNext", EditorInfo.IME_ACTION_DONE)
.map("actionNone", EditorInfo.IME_ACTION_DONE)
.map("actionPrevious", EditorInfo.IME_ACTION_DONE)
.map("actionSearch", EditorInfo.IME_ACTION_DONE)
.map("actionSend", EditorInfo.IME_ACTION_DONE)
.map("actionUnspecified", EditorInfo.IME_ACTION_DONE);
private static final ValueMapper<Integer> INPUT_TYPES = new ValueMapper<Integer>("inputType")
.map("date", 0x14)
.map("datetime", 0x4)
.map("none", 0x0)
.map("number", 0x2)
.map("numberDecimal", 0x2002)
.map("numberPassword", 0x12)
.map("numberSigned", 0x1002)
.map("phone", 0x3)
.map("text", 0x1)
.map("textAutoComplete", 0x10001)
.map("textAutoCorrect", 0x8001)
.map("textCapCharacters", 0x1001)
.map("textCapSentences", 0x4001)
.map("textCapWords", 0x2001)
.map("textEmailAddress", 0x21)
.map("textEmailSubject", 0x31)
.map("textFilter", 0xb1)
.map("textImeMultiLine", 0x40001)
.map("textLongMessage", 0x51)
.map("textMultiLine", 0x20001)
.map("textNoSuggestions", 0x80001)
.map("textPassword", 0x81)
.map("textPersonName", 0x61)
.map("textPhonetic", 0xc1)
.map("textPostalAddress", 0x71)
.map("textShortMessage", 0x41)
.map("textUri", 0x11)
.map("textVisiblePassword", 0x91)
.map("textWebEditText", 0xa1)
.map("textWebEmailAddress", 0xd1)
.map("textWebPassword", 0xe1)
.map("time", 0x24);
private static final ValueMapper<Integer> INPUT_TYPE_NUMERIC = new ValueMapper<Integer>("numeric")
.map("decimal", InputType.TYPE_NUMBER_FLAG_DECIMAL)
.map("number", InputType.TYPE_CLASS_NUMBER)
.map("signed", InputType.TYPE_NUMBER_FLAG_SIGNED);
private static final ValueMapper<Integer> TEXT_STYLES = new ValueMapper<Integer>("textStyle")
.map("bold", Typeface.BOLD)
.map("italic", Typeface.ITALIC)
.map("normal", Typeface.NORMAL);
private static final ValueMapper<TextKeyListener.Capitalize> CAPITALIZE = new ValueMapper<TextKeyListener.Capitalize>("capitalize")
.map("characters", TextKeyListener.Capitalize.CHARACTERS)
.map("none", TextKeyListener.Capitalize.NONE)
.map("sentences", TextKeyListener.Capitalize.SENTENCES)
.map("words", TextKeyListener.Capitalize.WORDS);
private boolean mAutoText;
private TextKeyListener.Capitalize mCapitalize;
private Drawable mDrawableBottom;
private Drawable mDrawableRight;
private Drawable mDrawableTop;
private Drawable mDrawableLeft;
private Integer mLineSpacingExtra;
private Integer mLineSpacingMultiplier;
private String mFontFamily;
private Integer mTextStyle;
private String mTypeface;
@Override
public boolean setAttr(V view, String attrName, String value, ViewGroup parent, Map<String, String> attrs) {
if (super.setAttr(view, attrName, value, parent, attrs)) {
return true;
}
switch (attrName) {
case "autoLink":
view.setAutoLinkMask(AUTO_LINK_MASKS.get(value));
break;
case "autoText":
mAutoText = Boolean.valueOf(value);
break;
case "capitalize":
mCapitalize = CAPITALIZE.get(value);
break;
case "cursorVisible":
view.setCursorVisible(Boolean.valueOf(value));
break;
case "digit":
if (value.equals("true")) {
view.setKeyListener(DigitsKeyListener.getInstance());
} else if (!value.equals("false")) {
view.setKeyListener(DigitsKeyListener.getInstance(value));
}
break;
case "drawableBottom":
mDrawableBottom = Drawables.parse(view, value);
break;
case "drawableTop":
mDrawableTop = Drawables.parse(view, value);
break;
case "drawableLeft":
mDrawableLeft = Drawables.parse(view, value);
break;
case "drawableRight":
mDrawableRight = Drawables.parse(view, value);
break;
case "drawablePadding":
view.setCompoundDrawablePadding(Dimensions.parseToIntPixel(value, view));
break;
case "drawableStart":
case "drawableEnd":
case "editable":
case "editorExtras":
Exceptions.unsupports(view, attrName, value);
case "elegantTextHeight":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setElegantTextHeight(Boolean.valueOf(value));
}
break;
case "ellipsize":
TextUtils.TruncateAt e = ELLIPSIZE.get(value);
if (e != null) {
view.setEllipsize(e);
}
break;
case "ems":
view.setEms(Integer.valueOf(value));
break;
case "fontFamily":
mFontFamily = value;
break;
case "fontFeatureSettings":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setFontFeatureSettings(value);
}
break;
case "freezesText":
view.setFreezesText(Boolean.valueOf(value));
break;
case "gravity":
view.setGravity(Gravities.parse(value));
break;
case "hint":
view.setHint(Strings.parse(view, value));
break;
case "hyphenationFrequency":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setHyphenationFrequency(HYPHENATION_FREQUENCY.get(value));
}
break;
case "imeActionId":
view.setImeActionLabel(view.getImeActionLabel(), Integer.valueOf(value));
break;
case "imeActionLabel":
view.setImeActionLabel(value, view.getImeActionId());
break;
case "imeOptions":
view.setImeOptions(IME_OPTIONS.split(value));
break;
case "includeFontPadding":
view.setIncludeFontPadding(Boolean.valueOf(value));
break;
case "inputMethod":
Exceptions.unsupports(view, attrName, value);
case "inputType":
view.setInputType(INPUT_TYPES.split(value));
break;
case "letterSpacing":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setLetterSpacing(Float.valueOf(value));
}
break;
case "lineSpacingExtra":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setLineSpacing(Dimensions.parseToIntPixel(value, view), view.getLineSpacingMultiplier());
} else {
mLineSpacingExtra = Dimensions.parseToIntPixel(value, view);
}
break;
case "lineSpacingMultiplier":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setLineSpacing(view.getLineSpacingExtra(), Dimensions.parseToIntPixel(value, view));
} else {
mLineSpacingMultiplier = Dimensions.parseToIntPixel(value, view);
}
break;
case "lines":
view.setLines(Integer.valueOf(value));
break;
case "linksClickable":
view.setLinksClickable(Boolean.valueOf(value));
break;
case "marqueeRepeatLimit":
view.setMarqueeRepeatLimit(value.equals("marquee_forever") ? Integer.MAX_VALUE : Integer.valueOf(value));
break;
case "maxEms":
view.setMaxEms(Integer.valueOf(value));
break;
case "maxHeight":
view.setMaxHeight(Integer.valueOf(value));
break;
case "maxLength":
view.setFilters(new InputFilter[]{new InputFilter.LengthFilter(Integer.valueOf(value))});
break;
case "maxLines":
view.setMaxLines(Integer.valueOf(value));
break;
case "maxWidth":
view.setMaxWidth(Integer.valueOf(value));
break;
case "minEms":
view.setMinEms(Integer.valueOf(value));
break;
case "minHeight":
view.setMinHeight(Dimensions.parseToIntPixel(value, view));
break;
case "minLines":
view.setMinLines(Integer.valueOf(value));
break;
case "minWidth":
view.setMinWidth(Dimensions.parseToIntPixel(value, view));
break;
case "numeric":
view.setInputType(INPUT_TYPE_NUMERIC.split(value) | InputType.TYPE_CLASS_NUMBER);
break;
case "password":
if (value.equals("true")) {
view.setInputType(view.getInputType() | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
break;
case "phoneNumber":
if (value.equals("true")) {
view.setInputType(view.getInputType() | InputType.TYPE_TEXT_VARIATION_PHONETIC);
}
break;
case "privateImeOptions":
view.setPrivateImeOptions(Strings.parse(view, value));
break;
case "scrollHorizontally":
view.setHorizontallyScrolling(Boolean.valueOf(value));
break;
case "selectAllOnFocus":
view.setSelectAllOnFocus(Boolean.valueOf(value));
break;
case "shadowColor":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setShadowLayer(view.getShadowRadius(), view.getShadowDx(), view.getShadowDy(), Colors.parse(view, value));
}
break;
case "shadowDx":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setShadowLayer(view.getShadowRadius(), Dimensions.parseToPixel(value, view), view.getShadowDy(), view.getShadowColor());
}
break;
case "shadowDy":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setShadowLayer(view.getShadowRadius(), view.getShadowDx(), Dimensions.parseToPixel(value, view), view.getShadowColor());
}
break;
case "shadowRadius":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setShadowLayer(Dimensions.parseToPixel(value, view), view.getShadowDx(), view.getShadowDy(), view.getShadowColor());
}
break;
case "singleLine":
view.setSingleLine(Boolean.valueOf(value));
break;
case "textAllCaps":
view.setAllCaps(Boolean.valueOf(value));
break;
case "textAppearance":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setTextAppearance(Res.parseStyle(view, value));
}
break;
case "textColorHighlight":
view.setHighlightColor(Colors.parse(view, value));
break;
case "textColorHint":
view.setHintTextColor(Colors.parse(view, value));
break;
case "textColorLink":
view.setLinkTextColor(Colors.parse(view, value));
break;
case "textIsSelectable":
view.setTextIsSelectable(Boolean.valueOf(value));
break;
case "textScaleX":
view.setTextScaleX(Dimensions.parseToPixel(value, view));
break;
case "textStyle":
mTextStyle = TEXT_STYLES.get(value);
break;
case "typeface":
mTypeface = value;
break;
case "text":
view.setText(Strings.parse(view, value));
break;
case "textColor":
view.setTextColor(Colors.parse(view.getContext(), value));
break;
case "textSize":
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, Dimensions.parseToPixel(value, view));
break;
default:
return false;
}
return true;
}
@Override
public void applyPendingAttributes(V view, ViewGroup parent) {
setTypeface(view);
setLineSpacing(view);
setDrawables(view);
setKeyListener(view);
}
private void setKeyListener(V view) {
if (mCapitalize != null) {
view.setKeyListener(TextKeyListener.getInstance(mAutoText, mCapitalize));
}
mCapitalize = null;
mAutoText = false;
}
private void setDrawables(V view) {
Drawable[] drawables = view.getCompoundDrawables();
view.setCompoundDrawables(
mDrawableLeft != null ? mDrawableLeft : drawables[0],
mDrawableTop != null ? mDrawableTop : drawables[1],
mDrawableRight != null ? mDrawableRight : drawables[2],
mDrawableBottom != null ? mDrawableBottom : drawables[3]
);
mDrawableLeft = mDrawableBottom = mDrawableRight = mDrawableTop = null;
}
private void setLineSpacing(V view) {
if (mLineSpacingExtra != null) {
view.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier == null ? 1 : mLineSpacingMultiplier);
} else if (mLineSpacingMultiplier != null) {
view.setLineSpacing(0, mLineSpacingMultiplier);
}
mLineSpacingMultiplier = mLineSpacingExtra = null;
}
private void setTypeface(V view) {
if (mFontFamily != null) {
//ignore typeface as android does
mTypeface = mFontFamily;
}
if (mTypeface != null) {
if (mTextStyle != null) {
view.setTypeface(Typeface.create(mTypeface, mTextStyle));
} else {
view.setTypeface(Typeface.create(mTypeface, view.getTypeface().getStyle()));
}
} else if (mTextStyle != null) {
view.setTypeface(view.getTypeface(), mTextStyle);
}
mTypeface = mFontFamily = null;
mTextStyle = null;
}
}

View File

@@ -0,0 +1,27 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.TimePicker;
import com.stardust.autojs.R;
import com.stardust.autojs.core.ui.inflater.ViewCreator;
/**
* Created by Stardust on 2017/11/29.
*/
public class TimePickerAttrSetter extends BaseViewAttrSetter<TimePicker> {
@Nullable
@Override
public ViewCreator<TimePicker> getCreator() {
return (context, attrs) -> {
String datePickerMode = attrs.remove("android:timePickerMode");
if (datePickerMode == null || !datePickerMode.equals("spinner")) {
return new TimePicker(context);
}
return (TimePicker) View.inflate(context, R.layout.time_picker_spinner, null);
};
}
}

View File

@@ -0,0 +1,39 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.support.v7.widget.Toolbar;
import android.view.ViewGroup;
import com.stardust.autojs.core.ui.inflater.util.Strings;
import java.util.Map;
/**
* Created by Stardust on 2017/11/5.
*/
public class ToolbarAttrSetter<V extends Toolbar> extends BaseViewAttrSetter<V> {
@Override
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
return super.setAttr(view, attr, value, parent, attrs);
}
@Override
public boolean setAttr(V view, String ns, String attrName, String value, ViewGroup parent, Map<String, String> attrs) {
if (super.setAttr(view, ns, attrName, value, parent, attrs)) {
return true;
}
if (!ns.equals("app")) {
return false;
}
switch (attrName) {
case "title":
view.setTitle(Strings.parse(view, value));
break;
default:
return false;
}
return true;
}
}

View File

@@ -0,0 +1,80 @@
package com.stardust.autojs.core.ui.inflater.attrsetter;
import android.animation.LayoutTransition;
import android.os.Build;
import android.view.ViewGroup;
import com.stardust.autojs.core.ui.inflater.util.ValueMapper;
import java.util.Map;
/**
* Created by Stardust on 2017/11/4.
*/
public class ViewGroupAttrSetter<V extends ViewGroup> extends BaseViewAttrSetter<V> {
private static final ValueMapper<Integer> PERSISTENT_DRAWING_CACHE = new ValueMapper<Integer>("persistentDrawingCache")
.map("all", ViewGroup.PERSISTENT_ALL_CACHES)
.map("animation", ViewGroup.PERSISTENT_ANIMATION_CACHE)
.map("none", 0)
.map("scrolling", ViewGroup.PERSISTENT_SCROLLING_CACHE);
private static final ValueMapper<Integer> LAYOUT_MODES = new ValueMapper<Integer>("layoutMode")
.map("clipBounds", 0) //ViewGroup.LAYOUT_MODE_CLIP_BOUNDS)
.map("opticalBounds", 1); //ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
private static final ValueMapper<Integer> DESCENDANT_FOCUSABILITY = new ValueMapper<Integer>("descendantFocusability")
.map("afterDescendants", ViewGroup.FOCUS_AFTER_DESCENDANTS)
.map("beforeDescendants", ViewGroup.FOCUS_BEFORE_DESCENDANTS)
.map("blocksDescendants", ViewGroup.FOCUS_BLOCK_DESCENDANTS);
@Override
public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map<String, String> attrs) {
switch (attr) {
case "addStatesFromChildren":
view.setAddStatesFromChildren(Boolean.valueOf(value));
break;
case "alwaysDrawnWithCache":
view.setAlwaysDrawnWithCacheEnabled(Boolean.valueOf(value));
break;
case "animateLayoutChanges":
view.setLayoutTransition(new LayoutTransition());
break;
case "animationCache":
view.setAnimationCacheEnabled(Boolean.valueOf(value));
break;
case "clipChildren":
view.setClipChildren(Boolean.valueOf(value));
break;
case "clipToPadding":
view.setClipToPadding(Boolean.valueOf(value));
break;
case "descendantFocusability":
view.setDescendantFocusability(DESCENDANT_FOCUSABILITY.get(value));
break;
case "layoutAnimation":
Exceptions.unsupports(view, attr, value);
break;
case "layoutMode":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
view.setLayoutMode(LAYOUT_MODES.get(value));
}
break;
case "persistentDrawingCache":
view.setPersistentDrawingCache(PERSISTENT_DRAWING_CACHE.get(value));
break;
case "splitMotionEvents":
view.setMotionEventSplittingEnabled(Boolean.valueOf(value));
break;
default:
return super.setAttr(view, attr, value, parent, attrs);
}
return true;
}
public void applyPendingAttributesAboutChildren(V view) {
}
}

View File

@@ -0,0 +1,28 @@
package com.stardust.autojs.core.ui.inflater.util;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.view.View;
/**
* Created by Stardust on 2017/11/3.
*/
public class Colors {
public static int parse(Context context, String color) {
Resources resources = context.getResources();
if (color.startsWith("@color/")) {
return resources.getColor(resources.getIdentifier(color.substring("@color/".length()), "color", context.getPackageName()));
}
if (color.startsWith("@android:color/")) {
return Color.parseColor(color.substring(15));
}
return Color.parseColor(color);
}
public static int parse(View view, String color){
return parse(view.getContext(), color);
}
}

View File

@@ -0,0 +1,74 @@
package com.stardust.autojs.core.ui.inflater.util;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.InflateException;
import android.view.View;
import android.view.ViewGroup;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by nick on 8/10/15.
* <p>
* Taken from http://stackoverflow.com/questions/8343971/how-to-parse-a-dimension-string-and-convert-it-to-a-dimension-value
*/
public class Dimensions {
private static final ValueMapper<Integer> UNITS = new ValueMapper<Integer>("unit")
.map("px", TypedValue.COMPLEX_UNIT_PX)
.map("dip", TypedValue.COMPLEX_UNIT_DIP)
.map("dp", TypedValue.COMPLEX_UNIT_DIP)
.map("sp", TypedValue.COMPLEX_UNIT_SP)
.map("pt", TypedValue.COMPLEX_UNIT_PT)
.map("in", TypedValue.COMPLEX_UNIT_IN)
.map("mm", TypedValue.COMPLEX_UNIT_MM);
private static final Pattern DIMENSION_PATTERN = Pattern.compile("([+-]?[0-9.]+)([a-zA-Z]*)");
public static int parseToPixel(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 parseToIntPixel(dimension, parent.getContext());
}
public static float parseToPixel(String dimension, View view) {
return parseToPixel(dimension, view.getContext());
}
public static float parseToPixel(String dimension, Context context) {
if (dimension.startsWith("?")) {
int[] attr = {context.getResources().getIdentifier(dimension.substring(1), "attr",
context.getPackageName())};
TypedArray ta = context.obtainStyledAttributes(attr);
float d = ta.getDimension(0, 0);
ta.recycle();
return d;
}
Matcher m = DIMENSION_PATTERN.matcher(dimension);
if (!m.matches()) {
throw new InflateException("dimension cannot be resolved: " + dimension);
}
int unit = m.groupCount() == 2 ? UNITS.get(m.group(2)) : TypedValue.COMPLEX_UNIT_PX;
float value = Integer.valueOf(m.group(1));
return TypedValue.applyDimension(unit, value, context.getResources().getDisplayMetrics());
}
public static int parseToIntPixel(String value, View view) {
return Math.round(parseToPixel(value, view));
}
public static int parseToIntPixel(String value, Context context) {
return Math.round(parseToPixel(value, context));
}
}

View File

@@ -0,0 +1,152 @@
package com.stardust.autojs.core.ui.inflater.util;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.Base64;
import android.view.View;
import android.widget.ImageView;
import com.stardust.autojs.core.ui.inflater.ImageLoader;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by Stardust on 2017/11/3.
*/
public class Drawables {
private static final Pattern DATA_PATTERN = Pattern.compile("data:(\\w+/\\w+);base64,(.+)");
private static ImageLoader sImageLoader = new ImageLoader() {
@Override
public void loadInto(final ImageView view, Uri uri) {
load(view, uri, view::setImageDrawable);
}
@Override
public void loadIntoBackground(final View view, Uri uri) {
load(view, uri, view::setBackground);
}
@Override
public Drawable load(View view, Uri uri) {
try {
URL url = new URL(uri.toString());
Bitmap bmp = BitmapFactory.decodeStream(url.openConnection().getInputStream());
return new BitmapDrawable(view.getResources(), bmp);
} catch (Exception e) {
return null;
}
}
@Override
public void load(View view, Uri uri, final DrawableCallback callback) {
load(view, uri, (BitmapCallback) bitmap -> callback.onLoaded(new BitmapDrawable(view.getResources(), bitmap)));
}
@Override
public void load(final View view, final Uri uri, final BitmapCallback callback) {
new Thread(() -> {
try {
URL url = new URL(uri.toString());
final Bitmap bmp = BitmapFactory.decodeStream(url.openConnection().getInputStream());
view.post(() -> callback.onLoaded(bmp));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
};
public static Drawable parse(Context context, String value) {
Resources resources = context.getResources();
if (value.startsWith("@color/") || value.startsWith("@android:color/") || value.startsWith("#")) {
return new ColorDrawable(Colors.parse(context, value));
}
if (value.startsWith("?")) {
int[] attr = {resources.getIdentifier(value.substring(1), "attr",
context.getPackageName())};
TypedArray ta = context.obtainStyledAttributes(attr);
Drawable drawable = ta.getDrawable(0 /* index */);
ta.recycle();
return drawable;
}
if (value.startsWith("file://")) {
return decodeImage(value.substring(7));
}
return resources.getDrawable(resources.getIdentifier(value, "drawable",
context.getPackageName()));
}
private static Drawable decodeImage(String path) {
return new BitmapDrawable(BitmapFactory.decodeFile(path));
}
public static Drawable parse(View view, String name) {
return parse(view.getContext(), name);
}
public static void loadInto(ImageView view, Uri uri) {
sImageLoader.loadInto(view, uri);
}
public static void loadIntoBackground(View view, Uri uri) {
sImageLoader.loadIntoBackground(view, uri);
}
public static <V extends ImageView> void setupWithImage(V view, String value) {
if (value.startsWith("http://") || value.startsWith("https://")) {
loadInto(view, Uri.parse(value));
} else if(value.startsWith("data:")) {
loadDataInto(view, value);
}else {
view.setImageDrawable(com.stardust.autojs.core.ui.inflater.util.Drawables.parse(view, value));
}
}
private static void loadDataInto(ImageView view, String data) {
Bitmap bitmap = loadData(data);
view.setImageBitmap(bitmap);
}
public static Bitmap loadData(String data){
Matcher matcher = DATA_PATTERN.matcher(data);
if(!matcher.matches() || matcher.groupCount() != 2){
return null;
}
String mimeType = matcher.group(1);
String base64 = matcher.group(2);
byte[] bytes = Base64.decode(base64, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
public static void setupWithViewBackground(View view, String value) {
if (value.startsWith("http://") || value.startsWith("https://")) {
loadIntoBackground(view, Uri.parse(value));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setBackground(com.stardust.autojs.core.ui.inflater.util.Drawables.parse(view, value));
} else {
view.setBackgroundDrawable(com.stardust.autojs.core.ui.inflater.util.Drawables.parse(view, value));
}
}
}
public static void setImageLoader(ImageLoader imageLoader) {
sImageLoader = imageLoader;
}
public static ImageLoader getImageLoader() {
return sImageLoader;
}
}

View File

@@ -0,0 +1,44 @@
package com.stardust.autojs.core.ui.inflater.util;
import android.view.Gravity;
/**
* Created by Stardust on 2017/11/3.
*/
public class Gravities {
public static int parse(String g) {
int gravity = Gravity.NO_GRAVITY;
String[] parts = g.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;
}
}

View File

@@ -0,0 +1,33 @@
package com.stardust.autojs.core.ui.inflater.util;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by Stardust on 2017/11/3.
*/
public class Ids {
private static AtomicInteger maxId = new AtomicInteger(20161209);
private static HashMap<String, Integer> ids = new HashMap<>();
public static String parseIdName(String idName) {
if (idName.startsWith("@+id/")) {
return idName.substring(5);
} else if (idName.startsWith("@id/")) {
return idName.substring(4);
}
return idName;
}
public static int parse(String name) {
name = parseIdName(name);
Integer id = ids.get(name);
if (id == null) {
id = maxId.incrementAndGet();
ids.put(name, id);
}
return id;
}
}

View File

@@ -0,0 +1,22 @@
package com.stardust.autojs.core.ui.inflater.util;
import android.content.Context;
import android.view.View;
/**
* Created by Stardust on 2017/11/5.
*/
public class Res {
public static int parseStyle(View view, String value) {
return parseStyle(view.getContext(), value);
}
public static int parseStyle(Context context, String value) {
// FIXME: 2017/11/5 Can or should it retrieve android.R.style or styleable?
if (value.startsWith("@style/")) {
value = value.substring(7);
}
return context.getResources().getIdentifier(value, "style", context.getPackageName());
}
}

View File

@@ -0,0 +1,26 @@
package com.stardust.autojs.core.ui.inflater.util;
import android.content.Context;
import android.content.res.Resources;
import android.view.View;
/**
* Created by Stardust on 2017/11/4.
*/
public class Strings {
public static String parse(Context context, String str) {
if (str.startsWith("@string/")) {
Resources resources = context.getResources();
return resources.getString(resources.getIdentifier(str, "string",
context.getPackageName()));
}
return str;
}
public static String parse(View view, String str) {
return parse(view.getContext(), str);
}
}

View File

@@ -0,0 +1,41 @@
package com.stardust.autojs.core.ui.inflater.util;
import android.view.InflateException;
import java.util.HashMap;
/**
* Created by Stardust on 2017/11/3.
*/
public class ValueMapper<V> {
private HashMap<String, V> mHashMap = new HashMap<>();
private String mAttrName;
public ValueMapper(String attrName) {
mAttrName = attrName;
}
public ValueMapper<V> map(String key, V value) {
mHashMap.put(key, value);
return this;
}
public V get(String key) {
V v = mHashMap.get(key);
if (v == null) {
throw new InflateException(String.format("unknown value for %s: %s", mAttrName, key));
}
return v;
}
public int split(String str){
int r = 0;
for(String s : str.split("\\|")){
r |= (Integer) get(s);
}
return r;
}
}

View File

@@ -1,13 +1,11 @@
package com.stardust.autojs.core.ui.widget;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import com.makeramen.roundedimageview.RoundedImageView;
import org.autojs.dynamiclayoutinflater.util.Drawables;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
/**
* Created by Stardust on 2017/11/30.
@@ -41,14 +39,15 @@ public class JsImageView extends RoundedImageView {
}
public void setSource(String uri) {
Drawables.loadInto(this, Uri.parse(uri));
Drawables.setupWithImage(this, uri);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
if (mCircle) {
setCornerRadius(Math.min(getWidth(), getHeight()) / 2);
setCornerRadius(getMeasuredWidth() / 2);
}
}
}

View File

@@ -31,6 +31,7 @@ import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
import com.stardust.autojs.core.accessibility.SimpleActionAutomator;
import com.stardust.autojs.runtime.api.UI;
import com.stardust.concurrent.VolatileDispose;
import com.stardust.lang.ThreadCompat;
import com.stardust.pio.UncheckedIOException;
import com.stardust.util.ClipboardUtil;
import com.stardust.autojs.core.util.ProcessShell;
@@ -324,7 +325,7 @@ public class ScriptRuntime {
public void onExit() {
//清除interrupt状态
Thread.interrupted();
ThreadCompat.interrupted();
//悬浮窗需要第一时间关闭以免出现恶意脚本全屏悬浮窗屏蔽屏幕并且在exit中写死循环的问题
ignoresException(floaty::closeAll);
try {
@@ -334,7 +335,7 @@ public class ScriptRuntime {
}
ignoresException(threads::shutDownAll);
ignoresException(events::recycle);
ignoresException(loopers::quitAll);
ignoresException(loopers::recycle);
ignoresException(() -> {
if (mRootShell != null) mRootShell.exitAndWaitFor();
mRootShell = null;

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:datePickerMode="spinner">
</DatePicker>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<TimePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:timePickerMode="spinner">
</TimePicker>