Weex Android SDK源码分析之Module(animation)

Posted 王永迪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Weex Android SDK源码分析之Module(animation)相关的知识,希望对你有一定的参考价值。

前言

module 怎能少得了动画呢~

代码解读

weex code

API 接口

transition(node, options, callback)
Arguments 参数

node(Node):将要动画的元素。

options(object):操作选项

styles(object):指定要应用的过渡效果的样式的名称和值。
color(string):色彩的元素时,animaiton完成。
transform(object):变换函数被应用到元素。支持下列值。
translate/ translatex / translatey(字符串):translate的元素到新的位置。该值可以是像素或百分比
rotate(string):单位为度。
scale(string):放大或缩小元素。
duration(number):指定一个过渡动画需要完成的毫秒数。默认情况下,该值是毫秒,这意味着没有动画会发生。
timingfuncion(string):用来描述的方式被过渡效果影响的中间值的计算。默认值为 linear, 也可以是一个 ease-in, ease-out, ease-in-out, linear or cubic-bezier(x1, y1, x2, y2).
delay(number):指定一个变化的要求,是转变和过渡效果的启动性能之间等待的毫秒数。默认情况下,该值为0毫秒。
transform-origin(string):尺度和旋转的中心。该值可以是x、y的像素或关键字,如 left, right, bottom, top, center。

Callback 
过渡完成后调用的回调回调函数。
Example 实例

<template>
  <div class="ct">
    <div id="test"></div>
  </div>
</template>

<script>
  module.exports = 
    ready: function () 
      var animation = require('@weex-module/animation');
      var testEl = this.$el('test');
      animation.transition(testEl, 
        styles: 
          color: '#FF0000',
          transform: 'translate(1, 1)'
        ,
        duration: 0, //ms
        timingFunction: 'ease',
        transform-origin: 'center center',
        delay: 0 //ms
      , function () 
        nativeLog('animation finished.')
      )
    
  
</script>

android code

一、注册

WXModuleManager.registerModule("animation", WXAnimationModule.class, true);

二、动画WXAnimationBean类

看的出来与weex定义的options 一一对应

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 long delay;// 延迟时间
  public long duration; // 显示时间
  public String timingFunction; // 过渡效果
  public Style styles; //样式

  public static class Style 

    // 支持动画样式
    public final static String ANDROID_TRANSLATION_X = "translationX";
    public final static String ANDROID_TRANSLATION_Y = "translationY";
    public final static String ANDROID_ROTATION = "rotation";
    public final static String ANDROID_SCALE_X = "scaleX";
    public final static String ANDROID_SCALE_Y = "scaleY";
    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_SCALE_X = "scaleX";
    public final static String WX_SCALE_Y = "scaleY";
    public final static String WX_SCALE = "scale";
    public final static String ALPHA = "alpha";
    public final static String BACKGROUND_COLOR = "backgroundColor";
    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";

    // 动画 key map
    public static Map<String, List<String>> wxToAndroidMap = new HashMap<>();

    static 
      wxToAndroidMap.put(WX_TRANSLATE, Arrays.asList
          (ANDROID_TRANSLATION_X, ANDROID_TRANSLATION_Y));
      wxToAndroidMap.put(WX_TRANSLATE_X, Collections.singletonList(ANDROID_TRANSLATION_X));
      wxToAndroidMap.put(WX_TRANSLATE_Y, Collections.singletonList(ANDROID_TRANSLATION_Y));
      wxToAndroidMap.put(WX_ROTATE, Collections.singletonList(ANDROID_ROTATION));
      wxToAndroidMap.put(WX_SCALE, Arrays.asList(ANDROID_SCALE_X, ANDROID_SCALE_Y));
      wxToAndroidMap.put(WX_SCALE_X, Collections.singletonList(ANDROID_SCALE_X));
      wxToAndroidMap.put(WX_SCALE_Y, Collections.singletonList(ANDROID_SCALE_Y));
      wxToAndroidMap = Collections.unmodifiableMap(wxToAndroidMap);
    

    public String opacity; // 透明度
    public String backgroundColor; //背景
    public String transform;// 动画效果
    public String transformOrigin;//过渡效果
    private Map<String, Float> transformMap = new HashMap<>(); // 动画map
    private Pair<Float, Float> pivot; // 中心点坐标

    public Map<String, Float> getTransformMap() 
      return transformMap;
    

    public void setTransformMap(Map<String, Float> transformMap) 
      this.transformMap = transformMap;
    

    public Pair<Float, Float> getPivot() 
      return pivot;
    

    public void setPivot(Pair<Float, Float> pivot) 
      this.pivot = pivot;
    
  

二、WXModuleManager 部分方法


public class WXAnimationModule extends WXModule 

    // 添加动画样式
    public static void applyTransformStyle(Map<String, Object> style, WXComponent component) 
        if (component != null) 
            View target = component.getRealView();
            if (style != null && target != null) 
                Object transform = style.get("transform");
                if (transform instanceof String &&
                     !TextUtils.isEmpty((String) transform) && target.getLayoutParams() != null) 
                    String transformOrigin;
                    try 
                        transformOrigin = (String) component.mDomObj.style.get("transformOrigin");
                     catch (NullPointerException e) 
                        transformOrigin = null;
                    
                    WXAnimationBean animationBean = new WXAnimationBean();
                    animationBean.styles = new WXAnimationBean.Style();
                    animationBean.styles.setPivot(
                            WXAnimationModule.parsePivot(transformOrigin, target.getLayoutParams()));
                    animationBean.styles.setTransformMap(
                    WXAnimationModule.parseTransForm((String) transform, target.getLayoutParams()));
                    Animator animator = WXAnimationModule.createAnimator(animationBean, target);
                    if (animator != null) 
                        animator.setDuration(0);
                        animator.start();
                    
                
            
        
    

    // ** 解析动画bean
    public static
    WXAnimationBean parseAnimation(@Nullable String animation, ViewGroup.LayoutParams layoutParams) 
        try 
            WXAnimationBean animationBean = JSONObject.parseObject(animation, WXAnimationBean.class);
            if (animationBean != null && animationBean.styles != null) 
                WXAnimationBean.Style style = animationBean.styles;
                style.setTransformMap(parseTransForm(style.transform, layoutParams));
                style.setPivot(parsePivot(style.transformOrigin, layoutParams));
            
            return animationBean;
         catch (RuntimeException e) 
            WXLogUtils.e(WXLogUtils.getStackTrace(e));
            return null;
        
    

    // ** 创建动画
    public static
    ObjectAnimator createAnimator(@NonNull WXAnimationBean animation, @NonNull View target) 
        WXAnimationBean.Style style = animation.styles;
        if (style != null) 
            ObjectAnimator animator;
            List<PropertyValuesHolder> holders = new LinkedList<>();
            if (style.getTransformMap() != null) 
                if (style.getTransformMap().isEmpty()) 
                    holders.addAll(moveBackToOrigin());
                 else 
                    for (Map.Entry<String, Float> entry : style.getTransformMap().entrySet()) 
                        holders.add(PropertyValuesHolder.ofFloat(entry.getKey(), entry.getValue()));
                    
                
            
            if (!TextUtils.isEmpty(style.opacity)) 
                holders.add(PropertyValuesHolder.ofFloat(WXAnimationBean.Style.ALPHA,
                                                         Float.valueOf(style.opacity)));
            
            if (!TextUtils.isEmpty(style.backgroundColor)) 
                if (target.getBackground() instanceof WXBackgroundDrawable) 
                    holders.add(PropertyValuesHolder.ofObject(
                            WXAnimationBean.Style.BACKGROUND_COLOR, new ArgbEvaluator(),
                            ((WXBackgroundDrawable) target.getBackground()).getColor(),
                            WXResourceUtils.getColor(style.backgroundColor)));
                 else if (target.getBackground() instanceof ColorDrawable) 
                    holders.add(PropertyValuesHolder.ofObject(
                            WXAnimationBean.Style.BACKGROUND_COLOR, new ArgbEvaluator(),
                            ((ColorDrawable) target.getBackground()).getColor(),
                            WXResourceUtils.getColor(style.backgroundColor)));
                
            
            if (style.getPivot() != null) 
                Pair<Float, Float> pair = style.getPivot();
                target.setPivotX(pair.first);
                target.setPivotY(pair.second);
            
            animator = ObjectAnimator.ofPropertyValuesHolder(
                    target, holders.toArray(new PropertyValuesHolder[holders.size()]));
            animator.setStartDelay(animation.delay);
            return animator;
         else 
            return null;
        
    

    // ** 创建动画监听器
    public static Animator.AnimatorListener createAnimatorListener
            (final WXSDKInstance mWXSDKInstance, final String callBack) 
        if (!TextUtils.isEmpty(callBack)) 
            return new AnimatorListenerAdapter() 
                @Override
                public void onAnimationEnd(Animator animation) 
                    if (mWXSDKInstance == null) 
                        WXLogUtils.e("WXRenderStatement-onAnimationEnd mWXSDKInstance == null NPE");
                     else 
                        // ** 动画结束后回调
                        WXSDKManager.getInstance().callback(mWXSDKInstance.getInstanceId(),
                                                            callBack,
                                                            new HashMap<String, Object>());
                    
                
            ;
         else 
            return null;
        
    

    // 创建动画过去效果
    public static
    Interpolator createTimeInterpolator(@NonNull WXAnimationBean animation) 
        String interpolator = animation.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();
            
        
        return null;
    

    // 解析动画
    private static Map<String, Float> parseTransForm
        (String rawTransform, final ViewGroup.LayoutParams layoutParams) 
        if (!TextUtils.isEmpty(rawTransform)) 
            FunctionParser<Float> parser = new FunctionParser<>
                    (rawTransform, new FunctionParser.Mapper<Float>() 
                        @Override
                        public Map<String, Float> map(String functionName, List<String> raw) 
                            Map<String, Float> result = new HashMap<>();
                            if (raw != null && !raw.isEmpty()) 
                                // 判断是否有此类型动画
                                if (WXAnimationBean.Style.wxToAndroidMap.containsKey(functionName)) 
                                    // 根据参数转换动画
                                    result.putAll(convertParam(
                                            layoutParams, 
                                            WXAnimationBean.Style.wxToAndroidMap.get(functionName),
                                            raw));
                                
                            
                            return result;
                        

                    );
            return parser.parse();
        
        return new LinkedHashMap<>();
    

    private static String parsePercentOrPx(String raw, int unit) 
        String lower = raw.toLowerCase();
        if (lower.endsWith("%")) 
            return parsePercent(raw, unit);
         else if (lower.endsWith("px")) 
            return Float.toString(
                WXViewUtils.getRealPxByWidth(Float.parseFloat(raw.replace("px", ""))));
        
        return Float.toString(WXViewUtils.getRealPxByWidth(Float.parseFloat(raw)));
    

    // 按百分比进行转化
    private static String parsePercent(String percent, int unit) 
        return Float.toString(Float.parseFloat(percent.replace("%", "")) / 100 * unit);
    

    // 解析坐标
    private static Pair<Float, Float> parsePivot(@Nullable String transformOrigin,
                                                 ViewGroup.LayoutParams layoutParams) 
        String[] split;
        List<String> list;
        if (!TextUtils.isEmpty(transformOrigin) &&
            ((split = transformOrigin.split("\\\\s+")).length >= 2)) 
            list = Arrays.asList(split).subList(0, 2);
            return parsePivot(list, layoutParams);
         else 
            return parsePivot(
            Arrays.asList(WXAnimationBean.Style.CENTER, WXAnimationBean.Style.CENTER), layoutParams);
        
    

    private static Pair<Float, Float> parsePivot
        (@NonNull List<String> list, ViewGroup.LayoutParams layoutParams) 
        return new Pair<>(parsePivotX(list.get(0), layoutParams), 
                parsePivotY(list.get(1), layoutParams));
    

    private static float parsePivotX(String x, ViewGroup.LayoutParams layoutParams) 
        String value = x;
        if (TextUtils.equals(x, WXAnimationBean.Style.LEFT)) 
            value = "0%";
         else if (TextUtils.equals(x, WXAnimationBean.Style.RIGHT)) 
            value = "100%";
         else if (TextUtils.equals(x, WXAnimationBean.Style.CENTER)) 
            value = "50%";
        
        value = parsePercentOrPx(value, layoutParams.width);
        return Float.parseFloat(value);
    

    private static float parsePivotY(String y, ViewGroup.LayoutParams layoutParams) 
        String value = y;
        if (TextUtils.equals(y, WXAnimationBean.Style.TOP)) 
            value = "0%";
         else if (TextUtils.equals(y, WXAnimationBean.Style.BOTTOM)) 
            value = "100%";
         else if (TextUtils.equals(y, WXAnimationBean.Style.CENTER)) 
            value = "50%";
        
        value = parsePercentOrPx(value, layoutParams.height);
        return Float.parseFloat(value);
    

    // 参数转化
    private static Map<String, Float> convertParam(ViewGroup.LayoutParams layoutParams,
                                                   @NonNull List<String> nameSet,
                                                   @NonNull List<String> rawValue) 
        Map<String, Float> result = new HashMap<>();
        List<String> convertedList = new ArrayList<>();
        if (nameSet.contains(WXAnimationBean.Style.ANDROID_ROTATION)) 
            convertedList.addAll(parseRotation(rawValue));
         else if (nameSet.contains(WXAnimationBean.Style.ANDROID_TRANSLATION_X) ||
                   nameSet.contains(WXAnimationBean.Style.ANDROID_TRANSLATION_Y)) 
            convertedList.addAll(parseTranslation(nameSet, layoutParams, rawValue));
         else if (nameSet.contains(WXAnimationBean.Style.ANDROID_SCALE_X) ||
                   nameSet.contains(WXAnimationBean.Style.ANDROID_SCALE_Y)) 
            convertedList.addAll(parseScale(nameSet.size(), rawValue));
        
        if (nameSet.size() == convertedList.size()) 
            for (int i = 0; i < nameSet.size(); i++) 
                result.put(nameSet.get(i), Float.parseFloat(convertedList.get(i)));
            
        
        return result;
    

    private static List<String> parseTranslation
        (List<String> nameSet, ViewGroup.LayoutParams layoutParams,@NonNull List<String> rawValue) 
        List<String> convertedList = new ArrayList<>();
        String first = rawValue.get(0);
        if (nameSet.size() == 1) 
            parseSingleTranslation(nameSet, layoutParams, convertedList, first);
         else 
            parseDoubleTranslation(layoutParams, rawValue, convertedList, first);
        
        return convertedList;
    

    private static void parseSingleTranslation (List<String> nameSet, 
        ViewGroup.LayoutParams layoutParams, List<String> convertedList, String first) 
        if (nameSet.contains(WXAnimationBean.Style.ANDROID_TRANSLATION_X)) 
            convertedList.add(parsePercentOrPx(first, layoutParams.width));
         else if (nameSet.contains(WXAnimationBean.Style.ANDROID_TRANSLATION_Y)) 
            convertedList.add(parsePercentOrPx(first, layoutParams.height));
        
    

    private static void parseDoubleTranslation(ViewGroup.LayoutParams layoutParams,
         @NonNull List<String> rawValue, List<String> convertedList, String first) 
        String second;
        if (rawValue.size() == 1) 
            second = first;
         else 
            second = rawValue.get(1);
        
        if (layoutParams != null) 
            convertedList.add(parsePercentOrPx(first, layoutParams.width));
            convertedList.add(parsePercentOrPx(second, layoutParams.height));
        
    

    // 解析缩放
    private static List<String> parseScale(int size, @NonNull List<String> rawValue) 
        List<String> convertedList = new ArrayList<>();
        convertedList.addAll(rawValue);
        if (size != 1 && rawValue.size() == 1) 
            convertedList.addAll(rawValue);
        
        return convertedList;
    

    // 解析旋转
    private static List<String> parseRotation(@NonNull List<String> rawValue) 
        List<String> convertedList = new ArrayList<>();
        String lower;
        for (String raw : rawValue) 
            lower = raw.toLowerCase();
            if (lower.endsWith("deg")) 
                convertedList.add(lower.replace("deg", ""));
             else 
                convertedList.add(
                Double.valueOf(Math.toDegrees(Double.parseDouble(raw))).toString());
            
        
        return convertedList;
    

    private static List<PropertyValuesHolder> moveBackToOrigin() 
        List<PropertyValuesHolder> holders = new LinkedList<>();
        holders.add(PropertyValuesHolder.ofFloat(WXAnimationBean.Style.ANDROID_TRANSLATION_X, 0));
        holders.add(PropertyValuesHolder.ofFloat(WXAnimationBean.Style.ANDROID_TRANSLATION_Y, 0));
        holders.add(PropertyValuesHolder.ofFloat(WXAnimationBean.Style.ANDROID_SCALE_X, 1));
        holders.add(PropertyValuesHolder.ofFloat(WXAnimationBean.Style.ANDROID_SCALE_Y, 1));
        holders.add(PropertyValuesHolder.ofFloat(WXAnimationBean.Style.ANDROID_ROTATION, 0));
        return holders;
    

    // 接受weex桥接回调方法
    @WXModuleAnno
    public void transition(String ref, String animation, String callBack) 
        WXSDKManager.getInstance().getWXRenderManager().
        startAnimation(mWXSDKInstance.getInstanceId(), ref, animation, callBack);
    

三、渲染管理器(WXRenderManager)

// 渲染声明队列
private ConcurrentHashMap<String, WXRenderStatement> mRegistries;

// 从登记的渲染声明中拿到当前的需要执行动画的animation控件,开启动画
public void startAnimation(String instanceId, String ref, String animation, String callBack) 
    WXRenderStatement statement = mRegistries.get(instanceId);
    if (statement == null) 
        return;
    
    statement.startAnimation(ref, animation, callBack);

四、渲染声明(WXRenderStatement)

// 存储组件map
private Map<String, WXComponent> mRegistry;

// 从map中取到相应控件
void startAnimation(String ref, String animation, String callBack) 
        WXComponent component = mRegistry.get(ref);
        if (component == null || component.getRealView() == null) 
            return;
        
        try 
            // 获取 animationBean
            WXAnimationBean animationBean = 
            WXAnimationModule.parseAnimation(animation, component.getRealView().getLayoutParams());
            if (animationBean != null) 
                // 创建 animator 对象
                Animator animator = 
                    WXAnimationModule.createAnimator(animationBean, component.getRealView());
                if (animator != null) 
                    // 创建监听
                    Animator.AnimatorListener animatorListener = 
                        WXAnimationModule.createAnimatorListener(mWXSDKInstance, callBack);
                    // 创建插补器 (过渡效果)
                    Interpolator interpolator = 
                        WXAnimationModule.createTimeInterpolator(animationBean);
                    if (animatorListener != null) 
                        animator.addListener(animatorListener);
                    
                    if (interpolator != null) 
                        animator.setInterpolator(interpolator);
                    
                    animator.setDuration(animationBean.duration);
                    animator.start();
                
            
         catch (RuntimeException e) 
            WXLogUtils.e(WXLogUtils.getStackTrace(e));
        
    

流程 :

    1、js 调用 本地module方法进行绘制;
    2、调用渲染声明生成动画bean;
    3、创建动画、设置监听、创建过渡效果;
    4、开启动画,结束回调callback;

以上是关于Weex Android SDK源码分析之Module(animation)的主要内容,如果未能解决你的问题,请参考以下文章

Weex Android SDK源码分析之Module(modal)

Weex Android SDK源码分析之Module(webview)

Weex Android SDK源码分析之Module(animation)

Weex Android SDK源码分析之Module(animation)

Weex Android SDK源码分析之界面渲染(上)

Weex系列之Module源码解析