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;
时间: 2024-10-21 05:15:33

Weex Android SDK源码分析之Module(animation)的相关文章

[Android]Volley源码分析(四)

上篇中有提到NetworkDispatcher是通过mNetwork(Network类型)来进行网络访问的,现在来看一下关于Network是如何进行网络访问的. Network部分的类图: Network有一个实现类BasicNetwork,它有一个mHttpStack的属性,实际的网络请求是由这个mHttpStack来进行的,看BasicNetwork的performRequest()方法, 1 @Override 2 public NetworkResponse performRequest

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte

Android HttpURLConnection源码分析

Android HttpURLConnection源码分析 之前写过HttpURLConnection与HttpClient的区别及选择.后来又分析了Volley的源码. 最近又遇到了问题,想在Volley中针对HttpURLConnection添加连接池的功能,开始有点懵了,不知道HttpURLConnection要怎么加连接池, 虽然感觉这是没必要的,但是心底确拿不出依据.所以研究下HttpURLConnection的源码进行分析. 在使用的时候都是通过URL.openConnection(

Android HandlerThread 源码分析

HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我又有一 个耗时任务需要执行,我们不得不重新创建线程去执行该耗时任务.然而,这样就存在一个性能问题:多次创建和销毁线程是很耗 系统资源的.为了解这种问题,我们可以自己构建一个循环线程Looper Thread,当有耗时任务投放到该循环线程中时,线程执行耗 时任务,执行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来.这样一来就避免了多次

[Android]Fragment源码分析(一) 构造

Fragment是Android3.0之后提供的api,被大家广泛所熟知的主要原因还是因为随即附带的ViewPager控件.虽然我并不喜欢用它,但是它确实是一个相对不错的控件.还是我的一贯作风,我将从源码上向大家展示什么是Fragment.我们先写一个简单的代码对Fragment有个直观的认识:(为了保证我们方便调试,我们可以直接使用V4提供的源码包) FragmentTransaction t = getSupportFragmentManager().beginTransaction();

android 从源码分析为什么Listview初次显示时没滚动却自动调用onScroll方法的原因

我们做Listview的分批加载时,需要为Listview调用setOnScrollListener(具体代码可见我上一篇博客) 可是,我们会发现,当运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?(补充:此时onScrollStateChanged并不会调用) 我们先看setOnScrollListener源码: public void setOnScrollListener(OnScrollListener l) { mOnScrollListener =

[Android]Fragment源码分析(三) 事务

Fragment管理中,不得不谈到的就是它的事务管理,它的事务管理写的非常的出彩.我们先引入一个简单常用的Fragment事务管理代码片段: FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); ft.add(R.id.fragmentContainer, fragment, "tag"); ft.addToBackStack("<span style="fo

[Android]Volley源码分析(叁)Network

如果各位看官仔细看过我之前的文章,实际上Network这块的只是点小功能的补充.我们来看下NetworkDispatcher的核心处理逻辑: <span style="font-size:18px;">while (true) { try { // Take a request from the queue. request = mQueue.take(); } catch (InterruptedException e) { // We may have been int

[Android]Volley源码分析(肆)应用

通过前面的讲述,相信你已经对Volley的原理有了一定了解.本章将举一些我们能在应用中直接用到的例子,第一个例子是 NetworkImageView类,其实NetworkImageView顾名思义就是将异步的操作封装在了控件本身,这种设计可以充分保留控件的移植性和维护性.NetworkImageView通过调用setImageUrl来指定具体的url: public void setImageUrl(String url, ImageLoader imageLoader) { mUrl = ur