* [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());
+    }
+}

Reply via email to