MotionLayout_ 打开动画新世界大门 (part II),20道高频面试题(含答案)

}

public static void setAttributes(View view, HashMap<String, ConstraintAttribute> map) {
Class<? extends View> viewClass = view.getClass();
Iterator var3 = map.keySet().iterator();

while(var3.hasNext()) {
String name = (String)var3.next();
ConstraintAttribute constraintAttribute = (ConstraintAttribute)map.get(name);
String methodName = “set” + name;

try {
Method method;
switch(constraintAttribute.mType) {
case COLOR_TYPE:
method = viewClass.getMethod(methodName, Integer.TYPE);
method.invoke(view, constraintAttribute.mColorValue);
break;
case COLOR_DRAWABLE_TYPE:
method = viewClass.getMethod(methodName, Drawable.class);
ColorDrawable drawable = new ColorDrawable();
drawable.setColor(constraintAttribute.mColorValue);
method.invoke(view, drawable);
break;
case INT_TYPE:
method = viewClass.getMethod(methodName, Integer.TYPE);
method.invoke(view, constraintAttribute.mIntegerValue);
break;
case FLOAT_TYPE:
method = viewClass.getMethod(methodName, Float.TYPE);
method.invoke(view, constraintAttribute.mFloatValue);
break;
case STRING_TYPE:
method = viewClass.getMethod(methodName, CharSequence.class);
method.invoke(view, constraintAttribute.mStringValue);
break;
case BOOLEAN_TYPE:
method = viewClass.getMethod(methodName, Boolean.TYPE);
method.invoke(view, constraintAttribute.mBooleanValue);
break;
case DIMENSION_TYPE:
method = viewClass.getMethod(methodName, Float.TYPE);
method.invoke(view, constraintAttribute.mFloatValue);
}
} catch (NoSuchMethodException var9) {
Log.e(“TransitionLayout”, var9.getMessage());
Log.e(“TransitionLayout”, " Custom Attribute “” + name + “” not found on " + viewClass.getName());
Log.e(“TransitionLayout”, viewClass.getName() + " must have a method " + methodName);
} catch (IllegalAccessException var10) {
Log.e(“TransitionLayout”, " Custom Attribute “” + name + “” not found on " + viewClass.getName());
var10.printStackTrace();
} catch (InvocationTargetException var11) {
Log.e(“TransitionLayout”, " Custom Attribute “” + name + “” not found on " + viewClass.getName());
var11.printStackTrace();
}
}

}

首先在 MotionLayout 中,如果是自定义属性,那么会执行 ConstraintSet 类中的 applyCustomAttributes 方法,接着会调用 ConstraintAttribute 类中的 setAttributes 方法,就如上代码中所写的那样,它会根据属性名称组装成对应的 set 方法,然后通过反射调用。是不是有种恍然大悟的感觉?话说,这样的机制是不是好像哪里见到过?没错,正是属性动画

KeyCycle

什么是 KeyCycle 呢?下面是来自 Gal Maoz 的总结:

A KeyCycle is a highly-detailed, custom-made interpolator for a specific view, whereas the interpolator is influencing the entire scene, with a large focus on repetitive actions (hence the cycle in the name).

简单来说,KeyCycle 是针对特定视图的非常详细的定制化插值器。它比较适合我们常说的波形或周期运动场景,比如实现控件的抖动动画或者周期性的循环动画。

keycycle结构图

如上图所示,KeyCycle 主要由以上几个属性组成,前两个相信大家都比较熟悉了,这里不必多说,另外 view properties 正如之前的 KeyAttribute 结构图中所描述的那样,代表View的各种属性,如 rotation、translation、alpha 等等。 这里主要介绍另外三个比较重要且具有特色的属性:

  • wavePeriod:这个表示在当前场景位置下需要执行动画的波(周期)的数量。这样说可能不太容易理解,别急,我们待会举个例子说明。
  • waveOffset:表示当前控件需要变化的属性的偏移量,即 view properties 所对应的初始值或者基准值。例如,如果我们在动画执行的某个位置设置了 scaleX 为 0.3,而设置了 waveOffset 值为 1,那么,动画执行到该位置,控件的实际宽度会变为 1 + 0.3 = 1.3,也就是会扩大为 1.3 倍,而不是缩小为之前的 0.3 倍。
  • waveShape:这个属性比较好理解,即波的形状,常见的值有:sin、cos、sawtooth 等,更多可参考官网API:developer.android.com/reference/a…

下面举个简单的例子帮助理解,以下面这个效果为例:

按钮回弹效果

对应的 KeyFrameSet 代码如下所示:

根据动画效果结合代码可以知道,我们这个放大的Q弹的效果只是改变了 scaleX 这个属性,并且让它“摇摆了”大概三个来回(周期),恰好 wavePeriod 属性值为 3。也许动画不太方便察觉,这样,我们借助于 Google 提供的专门用来查看 KeyCycle 波形变化的快捷工具来查看它波形变化过程:

CycleEditor展示波形图

如此一来,我们就很直观地看到上图中描绘的波形变化过程了,的确是三个周期没有错,并且是以正弦 sin 来变化的。

关于这款工具的使用,大家可以前往:github.com/googlearchi… 上下载,然后通过执行 java -jar [xx/CycleEditor.jar] 即可看到可视化界面,然后将 KeyFrameSet 部分的代码 copy 到编辑栏,然后点击 File -> parse xml 即可看到代码对应的波形走势。如下所示:

CycleEditor编辑页面

我们来看看下面这个效果:

keycycle应用

这个Q弹的效果就是基于 KeyCycle 实现的,我们来看看它的场景实现:

<?xml version="1.0" encoding="utf-8"?>











我们在动画路径上添加一些关键帧,并稍微改变控件的旋转角度,配合 keyCycle 就能达到上面的弹性动画,大家可以自己动手尝试体验一下。

MotionLayout 的联动性

很多时候,我们的控件并不只是单一的个体,而是需要与其他控件产生“交互上的关联”,常见地,Android 的Material design components 全家桶中提供了一套“优雅灵动”的组件,相信大家都体验过了,那么,我们的 MotionLayout 可以与它们碰撞出怎样的火花呢?

一切从“头”开始

Material design 组件库中提供了一个 AppBarLayout 组件,我们经常使用它来配合 CoordinatorLayout 控件实现一些简单的交互动作,例如头部导航栏的伸缩效果,各位应该或多或少都用到过,这里不再介绍。下面我们就从 AppBarLayout 开始,看看如何实现与 MotionLayout 的联动。首先,我们先来看下面这个简单的效果:

与appbar联动

我们知道,通过 CoordinatorLayoutAppBarLayout 也可以实现类似的交互效果,但显然 MotionLayout 会更加灵活多变。其实上面的动画效果很简单,只是在 AppBarLayout 高度变化过程中改变背景色、标题的位置和大小即可,对应的 MotionScene 文件代码如下所示:

<?xml version="1.0" encoding="utf-8"?>















结合以上效果图,我们很容易理解上面的场景实现代码,那么,我们再来看下布局文件:

<?xml version="1.0" encoding="utf-8"?>

<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=“@+id/content”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:fitsSystemWindows=“false”
android:background=“@android:color/white”
xmlns:app=“http://schemas.android.com/apk/res-auto”>
<com.google.android.material.appbar.AppBarLayout
android:id=“@+id/appBarLayout”
android:layout_width=“match_parent”
android:layout_height=“260dp”
android:theme=“@style/AppTheme.AppBarOverlay”>
<com.moos.constraint.widget.MotionToolBar
android:id=“@+id/motionLayout”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
app:motionDebug=“NO_DEBUG”
app:layoutDescription=“@xml/motion_scene_simple_appbar”
android:minHeight=“52dp”
app:layout_scrollFlags=“scroll|enterAlways|snap|exitUntilCollapsed”>


</com.moos.constraint.widget.MotionToolBar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width=“match_parent”
android:layout_height=“match_parent”
app:layout_behavior=“com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior”>

</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

观察上面布局文件,其实代码与传统 CoordinatorLayout & AppBarLayout 交互的代码大同小异,只不过我们在 AppBarLayout 内部添加了一个 MotionToolBar 控件,这其实是个 MotionLayout,只不过内部根据 AppBarLayout 伸缩的高度动态改变动画进度而已,我们来看下具体实现:

class MotionToolBar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr), AppBarLayout.OnOffsetChangedListener {

override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) {
Log.e(“MotionToolBar”, “onOffsetChanged: ----->$verticalOffset, scroll range–> ${appBarLayout?.totalScrollRange}”)
val seekPosition = -verticalOffset / (appBarLayout?.totalScrollRange!!.toFloat()/5*3)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-zasvWrRW-1710945381001)]
[外链图片转存中…(img-bo36MCzP-1710945381001)]
[外链图片转存中…(img-QkHaoerd-1710945381002)]
[外链图片转存中…(img-b9rhsZrS-1710945381002)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-QQLRrZ2U-1710945381002)]

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值