From 2d287f720c0975ef29d946ef6e0d992a94b6d0e2 Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Thu, 4 Jan 2018 16:22:38 +0800 Subject: [PATCH] * refactor: move library dynamic layout inflater to autojs module * fix: ScriptExecuteActivity memory leak * fix: InterruptedException on script exit event --- .../图片与图色处理/实时显示触摸点颜色.js | 2 +- .../java/com/stardust/scriptdroid/App.java | 4 +- autojs/build.gradle | 1 - .../autojs/core/image/ScreenCapturer.java | 13 +- .../stardust/autojs/core/looper/Loopers.java | 43 +- .../autojs/core/ui/ConvertLayoutInflater.java | 13 +- .../stardust/autojs/core/ui/JsViewHelper.java | 5 +- .../ui/inflater/DynamicLayoutInflater.java | 236 +++++++ .../autojs/core/ui/inflater/ImageLoader.java | 33 + .../core/ui/inflater/ViewAttrSetter.java | 25 + .../autojs/core/ui/inflater/ViewCreator.java | 16 + .../attrsetter/BaseViewAttrSetter.java | 602 ++++++++++++++++++ .../attrsetter/DatePickerAttrSetter.java | 93 +++ .../ui/inflater/attrsetter/Exceptions.java | 33 + .../attrsetter/FrameLayoutAttrSetter.java | 40 ++ .../attrsetter/ImageViewAttrSetter.java | 106 +++ .../attrsetter}/JsImageViewAttrSetter.java | 24 +- .../attrsetter/LinearLayoutAttrSetter.java | 51 ++ .../inflater/attrsetter/MapAttrbuteSet.java | 138 ++++ .../attrsetter/ProgressBarAttrSetter.java | 119 ++++ .../attrsetter/RadioGroupAttrSetter.java | 35 + .../attrsetter/SpinnerAttrSetter.java | 90 +++ .../attrsetter/TextViewAttrSetter.java | 420 ++++++++++++ .../attrsetter/TimePickerAttrSetter.java | 27 + .../attrsetter/ToolbarAttrSetter.java | 39 ++ .../attrsetter/ViewGroupAttrSetter.java | 80 +++ .../autojs/core/ui/inflater/util/Colors.java | 28 + .../core/ui/inflater/util/Dimensions.java | 74 +++ .../core/ui/inflater/util/Drawables.java | 152 +++++ .../core/ui/inflater/util/Gravities.java | 44 ++ .../autojs/core/ui/inflater/util/Ids.java | 33 + .../autojs/core/ui/inflater/util/Res.java | 22 + .../autojs/core/ui/inflater/util/Strings.java | 26 + .../core/ui/inflater/util/ValueMapper.java | 41 ++ .../autojs/core/ui/widget/JsImageView.java | 9 +- .../autojs/runtime/ScriptRuntime.java | 5 +- .../main/res/layout/date_picker_spinner.xml | 7 + .../main/res/layout/time_picker_spinner.xml | 7 + 38 files changed, 2677 insertions(+), 59 deletions(-) create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ImageLoader.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ViewAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ViewCreator.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/BaseViewAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/DatePickerAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/Exceptions.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/FrameLayoutAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ImageViewAttrSetter.java rename autojs/src/main/java/com/stardust/autojs/core/ui/{xml => inflater/attrsetter}/JsImageViewAttrSetter.java (55%) create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/LinearLayoutAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/MapAttrbuteSet.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ProgressBarAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/RadioGroupAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/SpinnerAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/TextViewAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/TimePickerAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ToolbarAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ViewGroupAttrSetter.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Colors.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Dimensions.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Drawables.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Gravities.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Ids.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Res.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Strings.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/ValueMapper.java create mode 100644 autojs/src/main/res/layout/date_picker_spinner.xml create mode 100644 autojs/src/main/res/layout/time_picker_spinner.xml diff --git a/app/src/main/assets/sample/图片与图色处理/实时显示触摸点颜色.js b/app/src/main/assets/sample/图片与图色处理/实时显示触摸点颜色.js index b59e094f..2f3cfd10 100644 --- a/app/src/main/assets/sample/图片与图色处理/实时显示触摸点颜色.js +++ b/app/src/main/assets/sample/图片与图色处理/实时显示触摸点颜色.js @@ -3,6 +3,6 @@ console.show(); events.observeTouch(); events.setTouchEventTimeout(30); events.on("touch", function(point){ - var c = colors.toString(images.pixel(captureScreen(), point.x, point.y); + var c = colors.toString(images.pixel(captureScreen(), point.x, point.y)); log("(" + point.x + ", " + point.y + "): " + c); }); \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/App.java b/app/src/main/java/com/stardust/scriptdroid/App.java index b7651d31..ecc16c62 100644 --- a/app/src/main/java/com/stardust/scriptdroid/App.java +++ b/app/src/main/java/com/stardust/scriptdroid/App.java @@ -15,6 +15,8 @@ import com.raizlabs.android.dbflow.config.FlowConfig; import com.raizlabs.android.dbflow.config.FlowManager; import com.raizlabs.android.dbflow.runtime.DirectModelNotifier; import com.squareup.leakcanary.LeakCanary; +import com.stardust.autojs.core.ui.inflater.ImageLoader; +import com.stardust.autojs.core.ui.inflater.util.Drawables; import com.stardust.scriptdroid.autojs.AutoJs; import com.stardust.scriptdroid.autojs.key.GlobalKeyObserver; import com.stardust.scriptdroid.network.GlideApp; @@ -26,8 +28,6 @@ import com.stardust.theme.ThemeColor; import com.stardust.theme.ThemeColorManager; import com.stardust.util.UiHandler; -import org.autojs.dynamiclayoutinflater.ImageLoader; -import org.autojs.dynamiclayoutinflater.util.Drawables; import java.lang.ref.WeakReference; diff --git a/autojs/build.gradle b/autojs/build.gradle index fbf3f2d8..4c8e5713 100644 --- a/autojs/build.gradle +++ b/autojs/build.gradle @@ -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', { diff --git a/autojs/src/main/java/com/stardust/autojs/core/image/ScreenCapturer.java b/autojs/src/main/java/com/stardust/autojs/core/image/ScreenCapturer.java index 47ba2864..5fa5b513 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/image/ScreenCapturer.java +++ b/autojs/src/main/java/com/stardust/autojs/core/image/ScreenCapturer.java @@ -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(); } diff --git a/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java b/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java index ea200943..899c57c0 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java @@ -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()); } diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/ConvertLayoutInflater.java b/autojs/src/main/java/com/stardust/autojs/core/ui/ConvertLayoutInflater.java index a125ae55..c698c9bf 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/ConvertLayoutInflater.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/ConvertLayoutInflater.java @@ -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. diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/JsViewHelper.java b/autojs/src/main/java/com/stardust/autojs/core/ui/JsViewHelper.java index 600afa51..d726db7d 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/JsViewHelper.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/JsViewHelper.java @@ -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; /** diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java new file mode 100644 index 00000000..8058cfcc --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java @@ -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 + *

+ * Licensed under the MIT License: + *

+ * 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: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * 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> mViewAttrSetters = new HashMap<>(); + private Map> 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 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 setter = (ViewAttrSetter) 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 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 getAttributesMap(Node currentNode) { + NamedNodeMap attributeMap = currentNode.getAttributes(); + int attributeCount = attributeMap.getLength(); + HashMap 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 setter, Map attrs, ViewGroup parent) { + if (setter != null) { + for (Map.Entry 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()); + } + + } + + +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ImageLoader.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ImageLoader.java new file mode 100644 index 00000000..4dfb8597 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ImageLoader.java @@ -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); + +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ViewAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ViewAttrSetter.java new file mode 100644 index 00000000..dd2b93c5 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ViewAttrSetter.java @@ -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 { + + boolean setAttr(V view, String attrName, String value, ViewGroup parent, Map attrs); + + boolean setAttr(V view, String ns, String attrName, String value, ViewGroup parent, Map attrs); + + void applyPendingAttributes(V view, ViewGroup parent); + + @Nullable + ViewCreator getCreator(); +} \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ViewCreator.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ViewCreator.java new file mode 100644 index 00000000..2e7e9c88 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/ViewCreator.java @@ -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 create(Context context, Map attrs); + +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/BaseViewAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/BaseViewAttrSetter.java new file mode 100644 index 00000000..72f6c5d0 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/BaseViewAttrSetter.java @@ -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 implements ViewAttrSetter { + + + protected static final ValueMapper TINT_MODES = new ValueMapper("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 DRAWABLE_CACHE_QUALITIES = new ValueMapper("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 IMPORTANT_FOR_ACCESSIBILITY = new ValueMapper("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 LAYOUT_DIRECTIONS = new ValueMapper("layoutDirection") + .map("inherit", 2) + .map("locale", 3) + .map("ltr", 0) + .map("rtl", 1); + private static final ValueMapper SCROLLBARS_STYLES = new ValueMapper("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 SCROLL_INDICATORS = new ValueMapper("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 VISIBILITY = new ValueMapper("visibility") + .map("visible", View.VISIBLE) + .map("invisible", View.INVISIBLE) + .map("gone", View.GONE); + private static final ValueMapper TEXT_DIRECTIONS = new ValueMapper("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 TEXT_ALIGNMENTS = new ValueMapper("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 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 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 getCreator() { + return null; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/DatePickerAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/DatePickerAttrSetter.java new file mode 100644 index 00000000..e08f1ac4 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/DatePickerAttrSetter.java @@ -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 { + + 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 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 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; + }; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/Exceptions.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/Exceptions.java new file mode 100644 index 00000000..81537d4e --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/Exceptions.java @@ -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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/FrameLayoutAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/FrameLayoutAttrSetter.java new file mode 100644 index 00000000..dd208d9c --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/FrameLayoutAttrSetter.java @@ -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 extends ViewGroupAttrSetter { + + private Integer mGravity; + + @Override + public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map 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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ImageViewAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ImageViewAttrSetter.java new file mode 100644 index 00000000..81ac3888 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ImageViewAttrSetter.java @@ -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 extends BaseViewAttrSetter { + + @Override + public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map 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); + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/xml/JsImageViewAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/JsImageViewAttrSetter.java similarity index 55% rename from autojs/src/main/java/com/stardust/autojs/core/ui/xml/JsImageViewAttrSetter.java rename to autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/JsImageViewAttrSetter.java index 3f869abb..7af0de77 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/xml/JsImageViewAttrSetter.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/JsImageViewAttrSetter.java @@ -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 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; diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/LinearLayoutAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/LinearLayoutAttrSetter.java new file mode 100644 index 00000000..63222d36 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/LinearLayoutAttrSetter.java @@ -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 extends ViewGroupAttrSetter { + + private static final ValueMapper ORIENTATIONS = new ValueMapper("orientation") + .map("vertical", LinearLayout.VERTICAL) + .map("horizontal", LinearLayout.HORIZONTAL); + + @Override + public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map 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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/MapAttrbuteSet.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/MapAttrbuteSet.java new file mode 100644 index 00000000..9d2b2604 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/MapAttrbuteSet.java @@ -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 mMap; + private ArrayList mKeys; + private ArrayList mValues; + + public MapAttrbuteSet(Map map) { + mMap = map; + mKeys = new ArrayList<>(map.size()); + mValues = new ArrayList<>(map.size()); + for (Map.Entry 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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ProgressBarAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ProgressBarAttrSetter.java new file mode 100644 index 00000000..c11e6e33 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ProgressBarAttrSetter.java @@ -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 extends BaseViewAttrSetter { + + @Override + public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map 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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/RadioGroupAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/RadioGroupAttrSetter.java new file mode 100644 index 00000000..e8d5dc23 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/RadioGroupAttrSetter.java @@ -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 extends LinearLayoutAttrSetter { + + private Integer mCheckedButton; + + @Override + public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map 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; + } + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/SpinnerAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/SpinnerAttrSetter.java new file mode 100644 index 00000000..1536940c --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/SpinnerAttrSetter.java @@ -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 { + + protected static final ValueMapper SPINNER_MODES = new ValueMapper("spinnerMode") + .map("dialog", Spinner.MODE_DIALOG) + .map("dropdown", Spinner.MODE_DROPDOWN); + + @Override + public boolean setAttr(Spinner view, String attr, String value, ViewGroup parent, Map 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 getCreator() { + return new ViewCreator() { + @Override + public Spinner create(Context context, Map 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> data, int resource, String[] from, int[] to) { + super(context, data, resource, from, to); + } + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/TextViewAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/TextViewAttrSetter.java new file mode 100644 index 00000000..f26d6cde --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/TextViewAttrSetter.java @@ -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 extends BaseViewAttrSetter { + + private static final ValueMapper AUTO_LINK_MASKS = new ValueMapper("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 ELLIPSIZE = new ValueMapper("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 HYPHENATION_FREQUENCY = new ValueMapper("hyphenationFrequency") + .map("full", 2) + .map("none", 0) + .map("normal", 1); + + // TODO: 2017/11/4 IME FLAG + private static final ValueMapper IME_OPTIONS = new ValueMapper("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 INPUT_TYPES = new ValueMapper("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 INPUT_TYPE_NUMERIC = new ValueMapper("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 TEXT_STYLES = new ValueMapper("textStyle") + .map("bold", Typeface.BOLD) + .map("italic", Typeface.ITALIC) + .map("normal", Typeface.NORMAL); + + private static final ValueMapper CAPITALIZE = new ValueMapper("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 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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/TimePickerAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/TimePickerAttrSetter.java new file mode 100644 index 00000000..11befba1 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/TimePickerAttrSetter.java @@ -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 { + + @Nullable + @Override + public ViewCreator 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); + }; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ToolbarAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ToolbarAttrSetter.java new file mode 100644 index 00000000..25c4422d --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ToolbarAttrSetter.java @@ -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 extends BaseViewAttrSetter { + + + @Override + public boolean setAttr(V view, String attr, String value, ViewGroup parent, Map attrs) { + return super.setAttr(view, attr, value, parent, attrs); + } + + @Override + public boolean setAttr(V view, String ns, String attrName, String value, ViewGroup parent, Map 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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ViewGroupAttrSetter.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ViewGroupAttrSetter.java new file mode 100644 index 00000000..18f4dd0a --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/attrsetter/ViewGroupAttrSetter.java @@ -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 extends BaseViewAttrSetter { + + private static final ValueMapper PERSISTENT_DRAWING_CACHE = new ValueMapper("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 LAYOUT_MODES = new ValueMapper("layoutMode") + .map("clipBounds", 0) //ViewGroup.LAYOUT_MODE_CLIP_BOUNDS) + .map("opticalBounds", 1); //ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS); + + private static final ValueMapper DESCENDANT_FOCUSABILITY = new ValueMapper("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 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) { + + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Colors.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Colors.java new file mode 100644 index 00000000..eafa4b09 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Colors.java @@ -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); + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Dimensions.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Dimensions.java new file mode 100644 index 00000000..d3cdcc29 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Dimensions.java @@ -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. + *

+ * 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 UNITS = new ValueMapper("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)); + } +} + + + + diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Drawables.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Drawables.java new file mode 100644 index 00000000..e9f9c819 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Drawables.java @@ -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 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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Gravities.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Gravities.java new file mode 100644 index 00000000..a56711b5 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Gravities.java @@ -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; + } + +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Ids.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Ids.java new file mode 100644 index 00000000..d660e0f3 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Ids.java @@ -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 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; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Res.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Res.java new file mode 100644 index 00000000..6a8076f8 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Res.java @@ -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()); + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Strings.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Strings.java new file mode 100644 index 00000000..4437e006 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/Strings.java @@ -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); + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/ValueMapper.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/ValueMapper.java new file mode 100644 index 00000000..7892180e --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/util/ValueMapper.java @@ -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 { + + private HashMap mHashMap = new HashMap<>(); + private String mAttrName; + + public ValueMapper(String attrName) { + mAttrName = attrName; + } + + public ValueMapper 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; + } + +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/widget/JsImageView.java b/autojs/src/main/java/com/stardust/autojs/core/ui/widget/JsImageView.java index fb589bd5..0a8a9f08 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/widget/JsImageView.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/widget/JsImageView.java @@ -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); } } } diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java index f772e636..ec4ea76c 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java @@ -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; diff --git a/autojs/src/main/res/layout/date_picker_spinner.xml b/autojs/src/main/res/layout/date_picker_spinner.xml new file mode 100644 index 00000000..e27b660b --- /dev/null +++ b/autojs/src/main/res/layout/date_picker_spinner.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/autojs/src/main/res/layout/time_picker_spinner.xml b/autojs/src/main/res/layout/time_picker_spinner.xml new file mode 100644 index 00000000..a4a6db51 --- /dev/null +++ b/autojs/src/main/res/layout/time_picker_spinner.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file