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)