前言
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