* [android] weex transition support on android platform, improve batch time when layout
Project: http://git-wip-us.apache.org/repos/asf/incubator-weex/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-weex/commit/6486bc91 Tree: http://git-wip-us.apache.org/repos/asf/incubator-weex/tree/6486bc91 Diff: http://git-wip-us.apache.org/repos/asf/incubator-weex/diff/6486bc91 Branch: refs/heads/release-0.16 Commit: 6486bc912e41b0e587053ed03935478bba185050 Parents: 946385a Author: jianbai.gbj <jianbai....@alibaba-inc.com> Authored: Fri Nov 10 21:11:28 2017 +0800 Committer: jianbai.gbj <jianbai....@alibaba-inc.com> Committed: Mon Nov 13 10:51:18 2017 +0800 ---------------------------------------------------------------------- .../java/com/taobao/weex/WXEnvironment.java | 1 - .../java/com/taobao/weex/common/Constants.java | 11 + .../com/taobao/weex/dom/DOMActionContext.java | 2 + .../taobao/weex/dom/DOMActionContextImpl.java | 17 +- .../java/com/taobao/weex/dom/WXDomHandler.java | 15 +- .../java/com/taobao/weex/dom/WXDomObject.java | 33 +- .../taobao/weex/dom/action/AnimationAction.java | 43 +- .../weex/dom/action/UpdateStyleAction.java | 20 +- .../weex/dom/transition/WXTransition.java | 626 +++++++++++++++++++ .../weex/ui/animation/TransformParser.java | 296 +++++++++ .../weex/ui/animation/WXAnimationBean.java | 5 - .../com/taobao/weex/utils/FunctionParser.java | 2 +- .../java/com/taobao/weex/utils/WXViewUtils.java | 5 + .../weex/dom/transition/WXTransitionTest.java | 96 +++ .../weex/ui/animation/TransformParserTest.java | 38 ++ 15 files changed, 1172 insertions(+), 38 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java b/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java index c042f34..d1e0779 100644 --- a/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java +++ b/android/sdk/src/main/java/com/taobao/weex/WXEnvironment.java @@ -68,7 +68,6 @@ public class WXEnvironment { public static boolean sDebugServerConnectable = false; public static boolean sRemoteDebugMode = false; public static String sRemoteDebugProxyUrl = ""; - public static boolean sDebugNetworkEventReporterEnable = false;//debugtool network switch public static long sJSLibInitTime = 0; public static long sSDKInitStart = 0;// init start timestamp http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/common/Constants.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/common/Constants.java b/android/sdk/src/main/java/com/taobao/weex/common/Constants.java index 32c91ba..dc67cd7 100644 --- a/android/sdk/src/main/java/com/taobao/weex/common/Constants.java +++ b/android/sdk/src/main/java/com/taobao/weex/common/Constants.java @@ -287,6 +287,8 @@ public class Constants { String UNSTICKY = "unsticky"; String STICKY = "sticky"; + String ON_TRANSITION_END = "transitionEnd"; + interface SLOT_LIFECYCLE{ String CREATE = "create"; String ATTACH = "attach"; @@ -318,4 +320,13 @@ public class Constants { String BANNER_DIGEST = "digest"; String SAVE_PATH = "v8"; } + + public interface TimeFunction{ + String LINEAR = "linear"; + String EASE_IN_OUT = "ease-in-out"; + String EASE_IN = "ease-in"; + String EASE_OUT = "ease-out"; + String EASE = "ease"; + String CUBIC_BEZIER = "cubic-bezier"; + } } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContext.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContext.java b/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContext.java index bbde73f..7668d3d 100644 --- a/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContext.java +++ b/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContext.java @@ -60,6 +60,8 @@ public interface DOMActionContext { boolean isDestory(); + void markDirty(); + WXSDKInstance getInstance(); WXDomObject getDomByRef(String ref); http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContextImpl.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContextImpl.java b/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContextImpl.java index 5a2e88d..a8f7615 100644 --- a/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContextImpl.java +++ b/android/sdk/src/main/java/com/taobao/weex/dom/DOMActionContextImpl.java @@ -185,6 +185,9 @@ class DOMActionContextImpl implements DOMActionContext { batchEvent.ph = "X"; WXTracing.submit(batchEvent); } + if(WXEnvironment.isApkDebugable()){ + WXLogUtils.d("mInstanceId " + mInstanceId + " batch used " + (System.currentTimeMillis() - start)); + } } void layout(WXDomObject rootDom) { @@ -212,6 +215,7 @@ class DOMActionContextImpl implements DOMActionContext { instance.cssLayoutTime(System.currentTimeMillis() - start); } + start = System.currentTimeMillis(); rootDom.traverseTree( new WXDomObject.Consumer() { @Override public void accept(WXDomObject dom) { @@ -220,10 +224,8 @@ class DOMActionContextImpl implements DOMActionContext { } dom.layoutAfter(); } - }); + }, new ApplyUpdateConsumer()); - start = System.currentTimeMillis(); - rootDom.traverseTree(new ApplyUpdateConsumer()); if (instance != null) { instance.applyUpdateTime(System.currentTimeMillis() - start); @@ -377,6 +379,15 @@ class DOMActionContextImpl implements DOMActionContext { } @Override + public void markDirty() { + if(!mDestroy){ + if(!mDirty){ + mDirty = true; + } + } + } + + @Override public WXSDKInstance getInstance() { return mWXRenderManager.getWXSDKInstance(mInstanceId); } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/dom/WXDomHandler.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/WXDomHandler.java b/android/sdk/src/main/java/com/taobao/weex/dom/WXDomHandler.java index ca79066..a11e820 100644 --- a/android/sdk/src/main/java/com/taobao/weex/dom/WXDomHandler.java +++ b/android/sdk/src/main/java/com/taobao/weex/dom/WXDomHandler.java @@ -35,6 +35,8 @@ public class WXDomHandler implements Handler.Callback { * The batch operation in dom thread will run at most once in 16ms. */ public static final int DELAY_TIME = 16;//ms + public static final int TRANSITION_DELAY_TIME = 2;//2ms, start transition as soon as + private WXDomManager mWXDomManager; private boolean mHasBatch = false; @@ -61,8 +63,14 @@ public class WXDomHandler implements Handler.Callback { if (!mHasBatch) { mHasBatch = true; - mWXDomManager.sendEmptyMessageDelayed(WXDomHandler.MsgType.WX_DOM_BATCH, DELAY_TIME); - } + if(what != WXDomHandler.MsgType.WX_DOM_BATCH) { + int delayTime = DELAY_TIME; + if(what == MsgType.WX_DOM_TRANSITION_BATCH){ + delayTime = TRANSITION_DELAY_TIME; + } + mWXDomManager.sendEmptyMessageDelayed(WXDomHandler.MsgType.WX_DOM_BATCH, delayTime); + } + } switch (what) { case MsgType.WX_EXECUTE_ACTION: mWXDomManager.executeAction(task.instanceId, (DOMAction) task.args.get(0), (boolean) task.args.get(1)); @@ -125,6 +133,9 @@ public class WXDomHandler implements Handler.Callback { public static final int WX_DOM_BATCH = 0xff; public static final int WX_CONSUME_RENDER_TASKS = 0xfa; + + public static final int WX_DOM_TRANSITION_BATCH = 0xfb; + @Deprecated public static final int WX_COMPONENT_SIZE= 0xff1; http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/dom/WXDomObject.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/WXDomObject.java b/android/sdk/src/main/java/com/taobao/weex/dom/WXDomObject.java index 03f3057..127309c 100644 --- a/android/sdk/src/main/java/com/taobao/weex/dom/WXDomObject.java +++ b/android/sdk/src/main/java/com/taobao/weex/dom/WXDomObject.java @@ -33,6 +33,7 @@ import com.taobao.weex.common.Constants.Name; import com.taobao.weex.dom.flex.CSSLayoutContext; import com.taobao.weex.dom.flex.CSSNode; import com.taobao.weex.dom.flex.Spacing; +import com.taobao.weex.dom.transition.WXTransition; import com.taobao.weex.ui.component.WXBasicComponentType; import com.taobao.weex.utils.WXLogUtils; import com.taobao.weex.utils.WXViewUtils; @@ -43,6 +44,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; + + /** * WXDomObject contains all the info about the given node, including style, attribute and event. * Unlike {@link com.taobao.weex.ui.component.WXComponent}, WXDomObject only contains info about @@ -87,6 +90,8 @@ public class WXDomObject extends CSSNode implements Cloneable,ImmutableDomObject /** package **/ WXEvent mEvents; + private WXTransition transition;; + @@ -165,6 +170,9 @@ public class WXDomObject extends CSSNode implements Cloneable,ImmutableDomObject return mEvents; } + public WXTransition getTransition() { + return transition; + } public @NonNull DomContext getDomContext() { return mDomContext; @@ -221,6 +229,7 @@ public class WXDomObject extends CSSNode implements Cloneable,ImmutableDomObject WXStyle styles = new WXStyle(); styles.putAll((JSONObject) style,false); this.mStyles = styles; + this.transition = WXTransition.fromMap(styles, this); } Object attr = map.get("attr"); if (attr != null && attr instanceof JSONObject) { @@ -453,18 +462,38 @@ public class WXDomObject extends CSSNode implements Cloneable,ImmutableDomObject if (styles == null || styles.isEmpty()) { return; } + if(transition != null){ + if(transition.hasTransitionProperty(styles)){ + transition.startTransition(styles); + } + } if (mStyles == null) { mStyles = new WXStyle(); } mStyles.putAll(styles,byPesudo); + if(transition == null){ + this.transition = WXTransition.fromMap(mStyles, this); + } super.dirty(); } - /** package **/ void applyStyleToNode() { + + public void applyStyle(Map<String, Object> styles){ + applyStyleToNode(styles); + } + + void applyStyleToNode() { + applyStyleToNode(getStyles()); + } + + /** package **/ void applyStyleToNode(Map<String, Object> updates) { + if(updates.size() == 0){ + return; + } WXStyle stylesMap = getStyles(); int vp = getViewPortWidth(); if (!stylesMap.isEmpty()) { - for(Map.Entry<String,Object> item:stylesMap.entrySet()) { + for(Map.Entry<String,Object> item: updates.entrySet()) { switch (item.getKey()) { case Constants.Name.ALIGN_ITEMS: setAlignItems(stylesMap.getAlignItems()); http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/dom/action/AnimationAction.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/action/AnimationAction.java b/android/sdk/src/main/java/com/taobao/weex/dom/action/AnimationAction.java index 5510ea9..93d376f 100644 --- a/android/sdk/src/main/java/com/taobao/weex/dom/action/AnimationAction.java +++ b/android/sdk/src/main/java/com/taobao/weex/dom/action/AnimationAction.java @@ -61,6 +61,13 @@ import com.taobao.weex.utils.WXViewUtils; import java.util.HashMap; import java.util.List; +import static com.taobao.weex.common.Constants.TimeFunction.CUBIC_BEZIER; +import static com.taobao.weex.common.Constants.TimeFunction.EASE; +import static com.taobao.weex.common.Constants.TimeFunction.EASE_IN; +import static com.taobao.weex.common.Constants.TimeFunction.EASE_IN_OUT; +import static com.taobao.weex.common.Constants.TimeFunction.EASE_OUT; +import static com.taobao.weex.common.Constants.TimeFunction.LINEAR; + class AnimationAction implements DOMAction, RenderAction { @@ -245,29 +252,31 @@ class AnimationAction implements DOMAction, RenderAction { String interpolator = mAnimationBean.timingFunction; if (!TextUtils.isEmpty(interpolator)) { switch (interpolator) { - case WXAnimationBean.EASE_IN: - return new AccelerateInterpolator(); - case WXAnimationBean.EASE_OUT: - return new DecelerateInterpolator(); - case WXAnimationBean.EASE_IN_OUT: - return new AccelerateDecelerateInterpolator(); - case WXAnimationBean.LINEAR: - return new LinearInterpolator(); + case EASE_IN: + return PathInterpolatorCompat.create(0.42f,0f, 1f,1f); + case EASE_OUT: + return PathInterpolatorCompat.create(0f,0f, 0.58f,1f); + case EASE_IN_OUT: + return PathInterpolatorCompat.create(0.42f,0f, 0.58f,1f); + case EASE: + return PathInterpolatorCompat.create(0.25f,0.1f, 0.25f,1f); + case LINEAR: + return PathInterpolatorCompat.create(0.0f,0f, 1f,1f); default: //Parse cubic-bezier try { SingleFunctionParser<Float> parser = new SingleFunctionParser<>( - mAnimationBean.timingFunction, - new SingleFunctionParser.FlatMapper<Float>() { - @Override - public Float map(String raw) { - return Float.parseFloat(raw); - } - }); - List<Float> params = parser.parse(WXAnimationBean.CUBIC_BEZIER); + mAnimationBean.timingFunction, + new SingleFunctionParser.FlatMapper<Float>() { + @Override + public Float map(String raw) { + return Float.parseFloat(raw); + } + }); + List<Float> params = parser.parse(CUBIC_BEZIER); if (params != null && params.size() == WXAnimationBean.NUM_CUBIC_PARAM) { return PathInterpolatorCompat.create( - params.get(0), params.get(1), params.get(2), params.get(3)); + params.get(0), params.get(1), params.get(2), params.get(3)); } else { return null; } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/dom/action/UpdateStyleAction.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/action/UpdateStyleAction.java b/android/sdk/src/main/java/com/taobao/weex/dom/action/UpdateStyleAction.java index a4dc8e9..bb19b32 100644 --- a/android/sdk/src/main/java/com/taobao/weex/dom/action/UpdateStyleAction.java +++ b/android/sdk/src/main/java/com/taobao/weex/dom/action/UpdateStyleAction.java @@ -73,16 +73,22 @@ class UpdateStyleAction extends TraceableAction implements DOMAction, RenderActi mPadding = domObject.getPadding(); mBorder = domObject.getBorder(); - Map<String, Object> animationMap = new ArrayMap<>(2); - animationMap.put(WXDomObject.TRANSFORM, mData.get(WXDomObject.TRANSFORM)); - animationMap.put(WXDomObject.TRANSFORM_ORIGIN, mData.get(WXDomObject.TRANSFORM_ORIGIN)); + if(mData.get(WXDomObject.TRANSFORM) != null || mData.get(WXDomObject.TRANSFORM_ORIGIN) != null){ + if(domObject.getTransition() == null) { + Map<String, Object> animationMap = new ArrayMap<>(2); + animationMap.put(WXDomObject.TRANSFORM, mData.get(WXDomObject.TRANSFORM)); + animationMap.put(WXDomObject.TRANSFORM_ORIGIN, mData.get(WXDomObject.TRANSFORM_ORIGIN)); + context.addAnimationForElement(mRef, animationMap); + } + } - context.addAnimationForElement(mRef, animationMap); if (!mData.isEmpty()) { - domObject.updateStyle(mData, mIsCausedByPesudo); - domObject.traverseTree(context.getApplyStyleConsumer()); - context.postRenderTask(this); + domObject.updateStyle(mData); + domObject.applyStyle(mData); + if(!mData.isEmpty()) { + context.postRenderTask(this); + } } if (instance != null) { http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/dom/transition/WXTransition.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/transition/WXTransition.java b/android/sdk/src/main/java/com/taobao/weex/dom/transition/WXTransition.java new file mode 100644 index 0000000..fc477bb --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/dom/transition/WXTransition.java @@ -0,0 +1,626 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.taobao.weex.dom.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.graphics.drawable.ColorDrawable; +import android.os.Handler; +import android.support.v4.util.ArrayMap; +import android.support.v4.util.ArraySet; +import android.support.v4.view.animation.PathInterpolatorCompat; +import android.text.TextUtils; +import android.util.Property; +import android.view.View; +import android.view.animation.Interpolator; + +import com.taobao.weex.WXEnvironment; +import com.taobao.weex.WXSDKManager; +import com.taobao.weex.common.Constants; +import com.taobao.weex.dom.DOMActionContext; +import com.taobao.weex.dom.WXDomHandler; +import com.taobao.weex.dom.WXDomObject; +import com.taobao.weex.dom.flex.Spacing; +import com.taobao.weex.ui.animation.BackgroundColorProperty; +import com.taobao.weex.ui.animation.TransformParser; +import com.taobao.weex.ui.component.WXComponent; +import com.taobao.weex.utils.SingleFunctionParser; +import com.taobao.weex.utils.WXLogUtils; +import com.taobao.weex.utils.WXResourceUtils; +import com.taobao.weex.utils.WXUtils; +import com.taobao.weex.utils.WXViewUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import static com.taobao.weex.common.Constants.TimeFunction.CUBIC_BEZIER; +import static com.taobao.weex.common.Constants.TimeFunction.EASE; +import static com.taobao.weex.common.Constants.TimeFunction.EASE_IN; +import static com.taobao.weex.common.Constants.TimeFunction.EASE_IN_OUT; +import static com.taobao.weex.common.Constants.TimeFunction.EASE_OUT; +import static com.taobao.weex.common.Constants.TimeFunction.LINEAR; + +/** + * transition on dom thread + * transition-property: height; + *Â transition-duration: .3s; + *Â transition-delay: .05s; + *Â transition-timing-function: ease-in-out; + * + * Created by furture on 2017/10/18. + */ +public class WXTransition { + + public static final String TRANSITION_PROPERTY = "transitionProperty"; + public static final String TRANSITION_DURATION = "transitionDuration"; + public static final String TRANSITION_DELAY = "transitionDelay"; + public static final String TRANSITION_TIMING_FUNCTION = "transitionTimingFunction"; + public static final Pattern PROPERTY_SPLIT_PATTERN = Pattern.compile("\\||,"); + + + /** + * layout animation property + * */ + private static final Set<String> LAYOUT_PROPERTIES = new ArraySet<>(); + static { + LAYOUT_PROPERTIES.add(Constants.Name.WIDTH); + LAYOUT_PROPERTIES.add(Constants.Name.HEIGHT); + LAYOUT_PROPERTIES.add(Constants.Name.MARGIN_TOP); + LAYOUT_PROPERTIES.add(Constants.Name.MARGIN_BOTTOM); + LAYOUT_PROPERTIES.add(Constants.Name.MARGIN_LEFT); + LAYOUT_PROPERTIES.add(Constants.Name.MARGIN_RIGHT); + } + + /** + * transform animation property, use android system animaton ability + * */ + private static final Set<String> TRANSFORM_PROPERTIES = new ArraySet<>(); + static { + TRANSFORM_PROPERTIES.add(Constants.Name.OPACITY); + TRANSFORM_PROPERTIES.add(Constants.Name.BACKGROUND_COLOR); + TRANSFORM_PROPERTIES.add(Constants.Name.TRANSFORM); + } + + private List<String> properties; + private Interpolator interpolator; + private float duration; + private float delay; + private WXDomObject domObject; + private Handler handler; + private ValueAnimator layoutValueAnimator; + private Map<String, Object> layoutPendingUpdates; + private ObjectAnimator transformAnimator; + private Map<String, Object> transformPendingUpdates; + private Runnable transitionEndEvent; + private Map<String, Object> targetStyles; + private Runnable animationRunnable; + + + public WXTransition() { + this.properties = new ArrayList<>(4); + this.handler = new Handler(); + this.layoutPendingUpdates = new ArrayMap<>(); + this.transformPendingUpdates = new ArrayMap<>(); + this.targetStyles = new ArrayMap<>(); + } + + /** + * create transition from map styles if style contains transitionProperty + * */ + public static WXTransition fromMap(Map<String, Object> style, WXDomObject domObject){ + if(style.get(TRANSITION_PROPERTY) == null){ + return null; + } + String propertyString = WXUtils.getString(style.get(TRANSITION_PROPERTY), null); + if(propertyString == null){ + return null; + } + WXTransition transition = new WXTransition(); + String[] propertiesArray = PROPERTY_SPLIT_PATTERN.split(propertyString); + for(String property : propertiesArray){ + String trim = property.trim(); + if(TextUtils.isEmpty(trim)){ + continue; + } + if(!(LAYOUT_PROPERTIES.contains(trim) || TRANSFORM_PROPERTIES.contains(trim))){ + if(WXEnvironment.isApkDebugable()){ + WXLogUtils.e("WXTransition Property Not Supported" + trim + " in " + propertyString); + } + continue; + } + transition.properties.add(trim); + } + if(transition.properties.isEmpty()){ + return null; + } + transition.duration = parseTimeMillis(style, TRANSITION_DURATION, 1); + transition.delay = parseTimeMillis(style, TRANSITION_DELAY, 0); + transition.interpolator = createTimeInterpolator(WXUtils.getString(style.get(TRANSITION_TIMING_FUNCTION), null)); + transition.domObject = domObject; + return transition; + } + + + /** + * check updates has transition property + * */ + public boolean hasTransitionProperty(Map<String, Object> styles){ + for(String property : properties){ + if(styles.containsKey(property)){ + return true; + } + } + return false; + } + + + /** + * start transition animation, updates maybe split two different updates, + * because javascript will send multi update on same transition, we assume that updates in 8ms is one transition + * */ + public void startTransition(Map<String, Object> updates){ + final View taregtView = getTargetView(); + if(taregtView == null){ + return; + } + for(String property : properties){ + if(updates.containsKey(property)){ + Object targetValue = updates.remove(property); + if(LAYOUT_PROPERTIES.contains(property)) { + layoutPendingUpdates.put(property, targetValue); + }else if(TRANSFORM_PROPERTIES.contains(property)){ + transformPendingUpdates.put(property, targetValue); + } + } + } + int delay = WXUtils.getNumberInt(domObject.getAttrs().get("actionDelay"), 16); + if(animationRunnable != null) { + handler.removeCallbacks(animationRunnable); + } + if(animationRunnable == null){ + animationRunnable = new Runnable() { + @Override + public void run() { + doTransitionAnimation(); + animationRunnable = null; + } + }; + } + handler.postDelayed(animationRunnable, delay); + } + + /** + * doTransitionAnimation include transform and layout animation. + * 1. put pre transition updates from target style to dom style + * 2. do transform animation and layout animation + * */ + private void doTransitionAnimation(){ + final View taregtView = getTargetView(); + if(taregtView == null){ + return; + } + if(targetStyles.size() > 0){ + for(String property : properties){ + if(!(LAYOUT_PROPERTIES.contains(property) || TRANSFORM_PROPERTIES.contains(property))){ + continue; + } + if(layoutPendingUpdates.containsKey(property)){ + continue; + } + if(transformPendingUpdates.containsKey(property)){ + continue; + } + synchronized (targetStyles){ + if(targetStyles.containsKey(property)){ + //reset pre transition style + Object targetValue = targetStyles.remove(property); + domObject.getStyles().put(property, targetValue); + WXComponent component = getComponent(); + if(component != null + && component.getDomObject() != null){ + component.getDomObject().getStyles().put(property, targetValue); + } + } + } + } + } + + + + if(transitionEndEvent != null){ + taregtView.removeCallbacks(transitionEndEvent); + } + if(transitionEndEvent == null){ + transitionEndEvent = new Runnable(){ + @Override + public void run() { + transitionEndEvent = null; + WXComponent component = getComponent(); + if(component != null && domObject.getEvents().contains(Constants.Event.ON_TRANSITION_END)){ + component.fireEvent(Constants.Event.ON_TRANSITION_END); + } + } + }; + } + + taregtView.post(new Runnable() { + @Override + public void run() { + doPendingTransformAnimation(); + } + }); + doPendingLayoutAnimation(); + } + + + /** + * transform, opacity, backgroundcolor which not effect layout use android system animation in main thread. + * */ + private void doPendingTransformAnimation() { + if(transformAnimator != null){ + transformAnimator.cancel(); + transformAnimator = null; + } + if(transformPendingUpdates.size() == 0){ + return; + } + final View taregtView = getTargetView(); + if(taregtView == null){ + return; + } + List<PropertyValuesHolder> holders = new ArrayList<>(8); + String transform = WXUtils.getString(transformPendingUpdates.remove(Constants.Name.TRANSFORM), null); + if(!TextUtils.isEmpty(transform)){ + Map<Property<View,Float>, Float> properties = TransformParser.parseTransForm(transform, (int)domObject.getLayoutWidth(), (int)domObject.getLayoutHeight(), domObject.getViewPortWidth()); + PropertyValuesHolder[] transformHolders = TransformParser.toHolders(properties); + for(PropertyValuesHolder holder : transformHolders){ + holders.add(holder); + } + } + + for(String property : properties){ + if(!TRANSFORM_PROPERTIES.contains(property)){ + continue; + } + if(!transformPendingUpdates.containsKey(property)){ + continue; + } + Object value = transformPendingUpdates.remove(property); + synchronized (targetStyles) { + targetStyles.put(property, value); + } + switch (property){ + case Constants.Name.OPACITY:{ + holders.add(PropertyValuesHolder.ofFloat(View.ALPHA, taregtView.getAlpha(), WXUtils.getFloat(value, 1.0f))); + } + break; + case Constants.Name.BACKGROUND_COLOR:{ + int fromColor = WXResourceUtils.getColor(WXUtils.getString(domObject.getStyles().getBackgroundColor(), null), 0); + int toColor = WXResourceUtils.getColor(WXUtils.getString(value, null), 0); + if(WXViewUtils.getBorderDrawable(taregtView) != null){ + fromColor = WXViewUtils.getBorderDrawable(taregtView).getColor(); + }else if (taregtView.getBackground() instanceof ColorDrawable) { + fromColor = ((ColorDrawable) taregtView.getBackground()).getColor(); + } + holders.add(PropertyValuesHolder.ofObject(new BackgroundColorProperty(), new ArgbEvaluator(), fromColor,toColor)); + } + break; + default:break; + } + } + transformPendingUpdates.clear(); + transformAnimator = ObjectAnimator.ofPropertyValuesHolder(taregtView, holders.toArray(new PropertyValuesHolder[holders.size()])); + transformAnimator.setDuration((long) duration); + transformAnimator.setStartDelay((long) delay); + if(interpolator != null) { + transformAnimator.setInterpolator(interpolator); + } + transformAnimator.addListener(new AnimatorListenerAdapter() { + boolean hasCancel = false; + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + hasCancel = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if(hasCancel){ + return; + } + super.onAnimationEnd(animation); + WXTransition.this.onTransitionAnimationEnd(); + if(WXEnvironment.isApkDebugable()){ + WXLogUtils.d("WXTransition transform onTransitionAnimationEnd " + domObject.getRef()); + } + } + }); + transformAnimator.start(); + } + + + public void doPendingLayoutAnimation(){ + if(layoutValueAnimator != null){ + layoutValueAnimator.cancel(); + layoutValueAnimator = null; + } + if(layoutPendingUpdates.size() == 0){ + return; + } + PropertyValuesHolder[] holders = new PropertyValuesHolder[layoutPendingUpdates.size()]; + int index = 0; + for(String property : properties){ + if(!LAYOUT_PROPERTIES.contains(property)){ + continue; + } + if(layoutPendingUpdates.containsKey(property)){ + Object targetValue = layoutPendingUpdates.remove(property); + synchronized (targetStyles) { + targetStyles.put(property, targetValue); + } + holders[index] = createLayoutPropertyValueHolder(property, targetValue); + index++; + } + } + layoutPendingUpdates.clear(); + doLayoutPropertyValuesHolderAnimation(holders); + } + + + private PropertyValuesHolder createLayoutPropertyValueHolder(String property, Object value){ + PropertyValuesHolder holder = null; + switch (property){ + case Constants.Name.WIDTH:{ + holder = PropertyValuesHolder.ofFloat(Constants.Name.WIDTH, domObject.getLayoutWidth(), + WXViewUtils.getRealPxByWidth(WXUtils.getFloat(value), domObject.getViewPortWidth())); + } + break; + case Constants.Name.HEIGHT:{ + holder = PropertyValuesHolder.ofFloat(Constants.Name.HEIGHT, domObject.getLayoutHeight(), + WXViewUtils.getRealPxByWidth(WXUtils.getFloat(value), domObject.getViewPortWidth())); + } + break; + case Constants.Name.MARGIN_TOP:{ + holder = PropertyValuesHolder.ofFloat(Constants.Name.MARGIN_TOP, domObject.getMargin().get(Spacing.TOP), + WXViewUtils.getRealPxByWidth(WXUtils.getFloatByViewport(value, domObject.getViewPortWidth()), domObject.getViewPortWidth())); + } + break; + case Constants.Name.MARGIN_LEFT:{ + holder = PropertyValuesHolder.ofFloat(Constants.Name.MARGIN_LEFT, domObject.getMargin().get(Spacing.LEFT), + WXViewUtils.getRealPxByWidth(WXUtils.getFloatByViewport(value, domObject.getViewPortWidth()), domObject.getViewPortWidth())); + } + break; + case Constants.Name.MARGIN_RIGHT:{ + holder = PropertyValuesHolder.ofFloat(Constants.Name.MARGIN_RIGHT, domObject.getMargin().get(Spacing.RIGHT), + WXViewUtils.getRealPxByWidth(WXUtils.getFloatByViewport(value, domObject.getViewPortWidth()), domObject.getViewPortWidth())); + } + break; + case Constants.Name.MARGIN_BOTTOM:{ + holder = PropertyValuesHolder.ofFloat(Constants.Name.MARGIN_BOTTOM, domObject.getMargin().get(Spacing.BOTTOM), + WXViewUtils.getRealPxByWidth(WXUtils.getFloatByViewport(value, domObject.getViewPortWidth()), domObject.getViewPortWidth())); + } + break; + default: + break; + } + if(holder == null){ + holder = PropertyValuesHolder.ofFloat(property, 1, 1); + } + return holder; + } + + private void doLayoutPropertyValuesHolderAnimation(PropertyValuesHolder[] holders){ + layoutValueAnimator = ValueAnimator.ofPropertyValuesHolder(holders); + layoutValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + PropertyValuesHolder holders[] = animation.getValues(); + for(PropertyValuesHolder holder : holders){ + String property = holder.getPropertyName(); + switch (property){ + case Constants.Name.WIDTH:{ + domObject.setStyleWidth((Float) animation.getAnimatedValue(property)); + } + break; + case Constants.Name.HEIGHT:{ + domObject.setStyleHeight((Float) animation.getAnimatedValue(property)); + } + break; + case Constants.Name.MARGIN_TOP:{ + domObject.setMargin(Spacing.TOP, (Float) animation.getAnimatedValue(property)); + } + break; + case Constants.Name.MARGIN_LEFT:{ + domObject.setMargin(Spacing.LEFT, (Float) animation.getAnimatedValue(property)); + } + break; + case Constants.Name.MARGIN_RIGHT:{ + domObject.setMargin(Spacing.RIGHT, (Float) animation.getAnimatedValue(property)); + } + break; + case Constants.Name.MARGIN_BOTTOM:{ + domObject.setMargin(Spacing.BOTTOM, (Float) animation.getAnimatedValue(property)); + } + break; + default: + break; + } + } + + DOMActionContext domActionContext = WXSDKManager.getInstance().getWXDomManager().getDomContext(domObject.getDomContext().getInstanceId()); + if(domActionContext == null){ + return; + } + domActionContext.markDirty(); + WXSDKManager.getInstance().getWXDomManager().sendEmptyMessageDelayed(WXDomHandler.MsgType.WX_DOM_TRANSITION_BATCH, 0); + if(WXEnvironment.isApkDebugable()){ + WXLogUtils.d("WXTransition send layout batch msg"); + } + } + }); + layoutValueAnimator.addListener(new AnimatorListenerAdapter() { + + boolean hasCancel = false; + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + hasCancel = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if(hasCancel){ + return; + } + super.onAnimationEnd(animation); + if(WXEnvironment.isApkDebugable()){ + WXLogUtils.d("WXTransition layout onTransitionAnimationEnd " + domObject.getRef()); + } + WXTransition.this.onTransitionAnimationEnd(); + } + }); + if(interpolator != null) { + layoutValueAnimator.setInterpolator(interpolator); + } + layoutValueAnimator.setStartDelay((long) (delay)); + layoutValueAnimator.setDuration((long) (duration)); + layoutValueAnimator.start(); + } + + private synchronized void onTransitionAnimationEnd(){ + if(transitionEndEvent != null){ + View view = getTargetView(); + if(view != null && transitionEndEvent != null){ + view.post(transitionEndEvent); + } + transitionEndEvent = null; + } + synchronized (targetStyles){ + if(targetStyles.size() > 0){ + WXComponent component = getComponent(); + for(String property : properties) { + if(targetStyles.containsKey(property)){ + Object targetValue = targetStyles.remove(property); + domObject.getStyles().put(property, targetValue); + if(component != null && component.getDomObject() != null){ + component.getDomObject().getStyles().put(property, targetValue); + } + } + } + targetStyles.clear(); + } + } + } + + private WXComponent getComponent(){ + DOMActionContext domActionContext = WXSDKManager.getInstance().getWXDomManager().getDomContext(domObject.getDomContext().getInstanceId()); + if(domActionContext != null){ + WXComponent component = domActionContext.getCompByRef(domObject.getRef()); + return component; + } + return null; + } + + + private View getTargetView(){ + if(domObject.getDomContext() == null){ + return null; + } + DOMActionContext domActionContext = WXSDKManager.getInstance().getWXDomManager().getDomContext(domObject.getDomContext().getInstanceId()); + if(domActionContext != null){ + WXComponent component = domActionContext.getCompByRef(domObject.getRef()); + if(component != null && component.getHostView() != null) { + return component.getHostView(); + } + } + return null; + } + + + + + /** + * get time millis + * */ + private static float parseTimeMillis(Map<String, Object> style, String key, float defaultValue){ + String duration = WXUtils.getString(style.get(key), null); + if(duration != null){ + duration = duration.replaceAll("s", ""); + } + if(TextUtils.isEmpty(duration)){ + return defaultValue; + } + try{ + return Float.parseFloat(duration); + }catch (NumberFormatException e){ + return defaultValue; + } + } + + /** + * create interpolcator same with web + * http://www.w3school.com.cn/cssref/pr_transition-timing-function.asp + * */ + private static Interpolator createTimeInterpolator(String interpolator) { + if (!TextUtils.isEmpty(interpolator)) { + switch (interpolator) { + case EASE_IN: + return PathInterpolatorCompat.create(0.42f,0f, 1f,1f); + case EASE_OUT: + return PathInterpolatorCompat.create(0f,0f, 0.58f,1f); + case EASE_IN_OUT: + return PathInterpolatorCompat.create(0.42f,0f, 0.58f,1f); + case EASE: + return PathInterpolatorCompat.create(0.25f,0.1f, 0.25f,1f); + case LINEAR: + return PathInterpolatorCompat.create(0.0f,0f, 1f,1f); + default: + try { + //Parse cubic-bezier + SingleFunctionParser<Float> parser = new SingleFunctionParser<>( + interpolator, + new SingleFunctionParser.FlatMapper<Float>() { + @Override + public Float map(String raw) { + return Float.parseFloat(raw); + } + }); + List<Float> params = parser.parse(CUBIC_BEZIER); + if (params != null && params.size() == 4) { + return PathInterpolatorCompat.create( + params.get(0), params.get(1), params.get(2), params.get(3)); + } + } catch (RuntimeException e) { + if(WXEnvironment.isApkDebugable()) { + WXLogUtils.e("WXTransition", e); + } + } + } + } + return PathInterpolatorCompat.create(0.25f,0.1f, 0.25f,1f); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/ui/animation/TransformParser.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/animation/TransformParser.java b/android/sdk/src/main/java/com/taobao/weex/ui/animation/TransformParser.java new file mode 100644 index 0000000..decc78e --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/animation/TransformParser.java @@ -0,0 +1,296 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.taobao.weex.ui.animation; + +import android.animation.PropertyValuesHolder; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.ArrayMap; +import android.text.TextUtils; +import android.util.Pair; +import android.util.Property; +import android.view.View; + +import com.taobao.weex.WXEnvironment; +import com.taobao.weex.common.Constants; +import com.taobao.weex.ui.animation.CameraDistanceProperty; +import com.taobao.weex.ui.animation.WXAnimationBean; +import com.taobao.weex.utils.FunctionParser; +import com.taobao.weex.utils.WXDataStructureUtil; +import com.taobao.weex.utils.WXUtils; +import com.taobao.weex.utils.WXViewUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Created by furture on 2017/10/24. + */ + +public class TransformParser { + + public final static String WX_TRANSLATE = "translate"; + public final static String WX_TRANSLATE_X = "translateX"; + public final static String WX_TRANSLATE_Y = "translateY"; + public final static String WX_ROTATE = "rotate"; + public final static String WX_ROTATE_X ="rotateX"; + public final static String WX_ROTATE_Y ="rotateY"; + public final static String WX_SCALE = "scale"; + public final static String WX_SCALE_X = "scaleX"; + public final static String WX_SCALE_Y = "scaleY"; + + public final static String BACKGROUND_COLOR = Constants.Name.BACKGROUND_COLOR; + public final static String WIDTH = Constants.Name.WIDTH; + public final static String HEIGHT = Constants.Name.HEIGHT; + public final static String TOP = "top"; + public final static String BOTTOM = "bottom"; + public final static String RIGHT = "right"; + public final static String LEFT = "left"; + public final static String CENTER = "center"; + private static final String HALF = "50%"; + private static final String FULL = "100%"; + private static final String ZERO = "0%"; + private static final String PX = "px"; + private static final String DEG = "deg"; + public static Map<String, List<Property<View,Float>>> wxToAndroidMap = new ArrayMap<>(); + + + static { + wxToAndroidMap.put(WX_TRANSLATE, Arrays.asList + (View.TRANSLATION_X, View.TRANSLATION_Y)); + wxToAndroidMap.put(WX_TRANSLATE_X, Collections.singletonList(View.TRANSLATION_X)); + wxToAndroidMap.put(WX_TRANSLATE_Y, Collections.singletonList(View.TRANSLATION_Y)); + wxToAndroidMap.put(WX_ROTATE, Collections.singletonList(View.ROTATION)); + wxToAndroidMap.put(WX_ROTATE_X, Collections.singletonList(View.ROTATION_X)); + wxToAndroidMap.put(WX_ROTATE_Y, Collections.singletonList(View.ROTATION_Y)); + wxToAndroidMap.put(WX_SCALE, Arrays.asList(View.SCALE_X, View.SCALE_Y)); + wxToAndroidMap.put(WX_SCALE_X, Collections.singletonList(View.SCALE_X)); + wxToAndroidMap.put(WX_SCALE_Y, Collections.singletonList(View.SCALE_Y)); + wxToAndroidMap.put(Constants.Name.PERSPECTIVE, Collections.singletonList(CameraDistanceProperty.getInstance())); + wxToAndroidMap = Collections.unmodifiableMap(wxToAndroidMap); + } + + public static PropertyValuesHolder[] toHolders(Map<Property<View,Float>, Float> transformMap){ + PropertyValuesHolder[] holders = new PropertyValuesHolder[transformMap.size()]; + int i=0; + for (Map.Entry<Property<View, Float>, Float> entry : transformMap.entrySet()) { + holders[i] = PropertyValuesHolder.ofFloat(entry.getKey(), entry.getValue()); + i++; + } + return holders; + } + + public static Map<Property<View,Float>, Float> parseTransForm(@Nullable String rawTransform, final int width, + final int height, final int viewportW) { + if (!TextUtils.isEmpty(rawTransform)) { + FunctionParser<Property<View,Float>, Float> parser = new FunctionParser<> + (rawTransform, new FunctionParser.Mapper<Property<View,Float>, Float>() { + @Override + public Map<Property<View,Float>, Float> map(String functionName, List<String> raw) { + if (raw != null && !raw.isEmpty()) { + if (wxToAndroidMap.containsKey(functionName)) { + return convertParam(width, height,viewportW, wxToAndroidMap.get(functionName), raw); + } + } + return new HashMap<>(); + } + + private Map<Property<View,Float>, Float> convertParam(int width, int height,int viewportW, + @NonNull List<Property<View,Float>> propertyList, + @NonNull List<String> rawValue) { + + Map<Property<View,Float>, Float> result = WXDataStructureUtil.newHashMapWithExpectedSize(propertyList.size()); + List<Float> convertedList = new ArrayList<>(propertyList.size()); + if (propertyList.contains(View.ROTATION) || + propertyList.contains(View.ROTATION_X) || + propertyList.contains(View.ROTATION_Y)) { + convertedList.addAll(parseRotationZ(rawValue)); + }else if (propertyList.contains(View.TRANSLATION_X) || + propertyList.contains(View.TRANSLATION_Y)) { + convertedList.addAll(parseTranslation(propertyList, width, height, rawValue,viewportW)); + } else if (propertyList.contains(View.SCALE_X) || + propertyList.contains(View.SCALE_Y)) { + convertedList.addAll(parseScale(propertyList.size(), rawValue)); + } + else if(propertyList.contains(CameraDistanceProperty.getInstance())){ + convertedList.add(parseCameraDistance(rawValue)); + } + if (propertyList.size() == convertedList.size()) { + for (int i = 0; i < propertyList.size(); i++) { + result.put(propertyList.get(i), convertedList.get(i)); + } + } + return result; + } + + private List<Float> parseScale(int size, @NonNull List<String> rawValue) { + List<Float> convertedList = new ArrayList<>(rawValue.size() * 2); + List<Float> rawFloat = new ArrayList<>(rawValue.size()); + for (String item : rawValue) { + rawFloat.add(WXUtils.fastGetFloat(item)); + } + convertedList.addAll(rawFloat); + if (size != 1 && rawValue.size() == 1) { + convertedList.addAll(rawFloat); + } + return convertedList; + } + + private @NonNull List<Float> parseRotationZ(@NonNull List<String> rawValue) { + List<Float> convertedList = new ArrayList<>(1); + int suffix; + for (String raw : rawValue) { + if ((suffix = raw.lastIndexOf(DEG)) != -1) { + convertedList.add(WXUtils.fastGetFloat(raw.substring(0, suffix))); + } else { + convertedList.add((float) Math.toDegrees(Double.parseDouble(raw))); + } + } + return convertedList; + } + + /** + * As "translate(50%, 25%)" or "translate(25px, 30px)" both are valid, + * parsing translate is complicated than other method. + * Add your waste time here if you try to optimize this method like {@link #parseScale(int, List)} + * Time: 0.5h + */ + private List<Float> parseTranslation(List<Property<View,Float>> propertyList, + int width, int height, + @NonNull List<String> rawValue,int viewportW) { + List<Float> convertedList = new ArrayList<>(2); + String first = rawValue.get(0); + if (propertyList.size() == 1) { + parseSingleTranslation(propertyList, width, height, convertedList, first,viewportW); + } else { + parseDoubleTranslation(width, height, rawValue, convertedList, first,viewportW); + } + return convertedList; + } + + private void parseSingleTranslation(List<Property<View,Float>> propertyList, int width, int height, + List<Float> convertedList, String first,int viewportW) { + if (propertyList.contains(View.TRANSLATION_X)) { + convertedList.add(parsePercentOrPx(first, width,viewportW)); + } else if (propertyList.contains(View.TRANSLATION_Y)) { + convertedList.add(parsePercentOrPx(first, height,viewportW)); + } + } + + private void parseDoubleTranslation(int width, int height, + @NonNull List<String> rawValue, + List<Float> convertedList, String first,int viewportW) { + String second; + if (rawValue.size() == 1) { + second = first; + } else { + second = rawValue.get(1); + } + convertedList.add(parsePercentOrPx(first, width,viewportW)); + convertedList.add(parsePercentOrPx(second, height,viewportW)); + } + + private Float parseCameraDistance(List<String> rawValue){ + float ret=Float.MAX_VALUE; + if(rawValue.size() == 1){ + float value = WXViewUtils.getRealPxByWidth(WXUtils.getFloat(rawValue.get(0)), viewportW); + float scale = WXEnvironment.getApplication().getResources().getDisplayMetrics().density; + if (!Float.isNaN(value) && value > 0) { + ret = value * scale; + } + } + return ret; + } + }); + return parser.parse(); + } + return new LinkedHashMap<>(); + } + + private static Pair<Float, Float> parsePivot(@Nullable String transformOrigin, + int width, int height, int viewportW) { + if (!TextUtils.isEmpty(transformOrigin)) { + int firstSpace = transformOrigin.indexOf(FunctionParser.SPACE); + if (firstSpace != -1) { + int i = firstSpace; + for (; i < transformOrigin.length(); i++) { + if (transformOrigin.charAt(i) != FunctionParser.SPACE) { + break; + } + } + if (i < transformOrigin.length() && transformOrigin.charAt(i) != FunctionParser.SPACE) { + List<String> list = new ArrayList<>(2); + list.add(transformOrigin.substring(0, firstSpace).trim()); + list.add(transformOrigin.substring(i, transformOrigin.length()).trim()); + return parsePivot(list, width, height,viewportW); + } + } + } + return null; + } + + private static Pair<Float, Float> parsePivot(@NonNull List<String> list, int width, int height,int viewportW) { + return new Pair<>( + parsePivotX(list.get(0), width,viewportW), parsePivotY(list.get(1), height,viewportW)); + } + + private static float parsePivotX(String x, int width,int viewportW) { + String value = x; + if (WXAnimationBean.Style.LEFT.equals(x)) { + value = ZERO; + } else if (WXAnimationBean.Style.RIGHT.equals(x)) { + value = FULL; + } else if (WXAnimationBean.Style.CENTER.equals(x)) { + value = HALF; + } + return parsePercentOrPx(value, width,viewportW); + } + + private static float parsePivotY(String y, int height,int viewportW) { + String value = y; + if (WXAnimationBean.Style.TOP.equals(y)) { + value = ZERO; + } else if (WXAnimationBean.Style.BOTTOM.equals(y)) { + value = FULL; + } else if (WXAnimationBean.Style.CENTER.equals(y)) { + value = HALF; + } + return parsePercentOrPx(value, height,viewportW); + } + + private static float parsePercentOrPx(String raw, int unit,int viewportW) { + final int precision = 1; + int suffix; + if ((suffix = raw.lastIndexOf(WXUtils.PERCENT)) != -1) { + return parsePercent(raw.substring(0, suffix), unit, precision); + } else if ((suffix = raw.lastIndexOf(PX)) != -1) { + return WXViewUtils.getRealPxByWidth(WXUtils.fastGetFloat(raw.substring(0, suffix), precision),viewportW); + } + return WXViewUtils.getRealPxByWidth(WXUtils.fastGetFloat(raw, precision),viewportW); + } + + private static float parsePercent(String percent, int unit, int precision) { + return WXUtils.fastGetFloat(percent, precision) / 100 * unit; + } +} http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/ui/animation/WXAnimationBean.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/animation/WXAnimationBean.java b/android/sdk/src/main/java/com/taobao/weex/ui/animation/WXAnimationBean.java index f3f7be5..e66b270 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/animation/WXAnimationBean.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/animation/WXAnimationBean.java @@ -45,11 +45,6 @@ import java.util.Map.Entry; public class WXAnimationBean { - public final static String LINEAR = "linear"; - public final static String EASE_IN_OUT = "ease-in-out"; - public final static String EASE_IN = "ease-in"; - public final static String EASE_OUT = "ease-out"; - public final static String CUBIC_BEZIER = "cubic-bezier"; public final static int NUM_CUBIC_PARAM = 4; public long delay; public long duration; http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/utils/FunctionParser.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/utils/FunctionParser.java b/android/sdk/src/main/java/com/taobao/weex/utils/FunctionParser.java index ba9103e..2756071 100644 --- a/android/sdk/src/main/java/com/taobao/weex/utils/FunctionParser.java +++ b/android/sdk/src/main/java/com/taobao/weex/utils/FunctionParser.java @@ -84,7 +84,7 @@ public class FunctionParser<K, V> { lexer.moveOn(); return value; } - throw new WXInterpretationException("Token doesn't match"); + throw new WXInterpretationException(token + "Token doesn't match" + lexer.source); } private enum Token { http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/main/java/com/taobao/weex/utils/WXViewUtils.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/utils/WXViewUtils.java b/android/sdk/src/main/java/com/taobao/weex/utils/WXViewUtils.java index 4a1b304..da90490 100644 --- a/android/sdk/src/main/java/com/taobao/weex/utils/WXViewUtils.java +++ b/android/sdk/src/main/java/com/taobao/weex/utils/WXViewUtils.java @@ -136,6 +136,11 @@ public class WXViewUtils { return getScreenWidth(WXEnvironment.sApplication); } + @Deprecated + public static int setScreenWidth(int screenWidth) { + return mScreenWidth = screenWidth; + } + public static float getScreenDensity(Context ctx){ if(ctx != null){ try{ http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/test/java/com/taobao/weex/dom/transition/WXTransitionTest.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/test/java/com/taobao/weex/dom/transition/WXTransitionTest.java b/android/sdk/src/test/java/com/taobao/weex/dom/transition/WXTransitionTest.java new file mode 100644 index 0000000..7374026 --- /dev/null +++ b/android/sdk/src/test/java/com/taobao/weex/dom/transition/WXTransitionTest.java @@ -0,0 +1,96 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.taobao.weex.dom.transition; + +import android.app.Application; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.taobao.weappplus_sdk.BuildConfig; +import com.taobao.weex.dom.WXDomObject; +import com.taobao.weex.utils.WXViewUtils; + +import junit.framework.Assert; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by furture on 2017/10/18. + */ +@RunWith(RobolectricTestRunner.class) +@Config(constants = BuildConfig.class, sdk = 19) +@PowerMockIgnore( {"org.mockito.*", "org.robolectric.*", "android.*"}) +public class WXTransitionTest extends Application{ + + + + @Test + public void testFrom(){ + WXViewUtils.setScreenWidth(750); + WXDomObject domObject = new WXDomObject(); + domObject.setViewPortWidth(750); + JSONObject map = JSON.parseObject("{\n" + + " \"style\": {\n" + + " \"width\": 600, \n" + + " \"marginLeft\": 75, \n" + + " \"marginTop\": 35, \n" + + " \"marginBottom\": 35, \n" + + " \"flexDirection\": \"column\", \n" + + " \"justifyContent\": \"center\", \n" + + " \"borderWidth\": 2, \n" + + " \"borderStyle\": \"solid\", \n" + + " \"borderColor\": \"rgb(0,180,255)\", \n" + + " \"backgroundColor\": \"rgba(0,180,255,0.2)\", \n" + + " \"transitionProperty\": \"height\", \n" + + " \"transitionDuration\": 300, \n" + + " \"transitionDelay\": 50, \n" + + " \"transitionTimingFunction\": \"ease-in-out\"\n" + + " }\n" + + "}"); + domObject.parseFromJson(map); + Assert.assertNotNull("transition success",domObject.getTransition()); + WXTransition transition = domObject.getTransition(); + Map<String, Object> updates = new HashMap(); + updates.put("height", "1000"); + Assert.assertTrue(transition.hasTransitionProperty(updates)); + transition.startTransition(updates); + + + + } + + @Test + public void testSplit(){ + + Assert.assertTrue(Arrays.equals(new String[]{"height", "width"}, WXTransition.PROPERTY_SPLIT_PATTERN.split("height|width"))); + Assert.assertTrue(Arrays.equals(new String[]{"height", "width"}, WXTransition.PROPERTY_SPLIT_PATTERN.split("height,width"))); + + + System.out.println(Arrays.toString(WXTransition.PROPERTY_SPLIT_PATTERN.split("height|width"))); + System.out.println(Arrays.toString(WXTransition.PROPERTY_SPLIT_PATTERN.split("height,width"))); + } +} http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/6486bc91/android/sdk/src/test/java/com/taobao/weex/ui/animation/TransformParserTest.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/test/java/com/taobao/weex/ui/animation/TransformParserTest.java b/android/sdk/src/test/java/com/taobao/weex/ui/animation/TransformParserTest.java new file mode 100644 index 0000000..28b94ab --- /dev/null +++ b/android/sdk/src/test/java/com/taobao/weex/ui/animation/TransformParserTest.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.taobao.weex.ui.animation; + +import android.util.Property; +import android.view.View; + +import junit.framework.TestCase; + +import java.util.Map; + +/** + * Created by furture on 2017/10/24. + */ + +public class TransformParserTest extends TestCase { + + public void testParseTransform(){ + Map<Property<View,Float>, Float> transforms = TransformParser.parseTransForm("rotate(7deg) translate(1, 2)", 100, 100, 750); + System.out.println(transforms.size()); + } +}