我们设置了 colorFilter
属性,作用相当于 tint
,重新运行后,可以看到如下效果:
需要我们注意的是,这里的自定义属性的 attributeName
对应的值并不一定是在 xml 布局文件中控件对应的属性名称,而是在对应控件中拥有 setter 设置的属性名称。怎么理解呢?其实归根结底 CustomAttribute 内部还是利用的反射,从下面的部分源码中就能够察觉到:
public void applyCustomAttributes(ConstraintLayout constraintLayout) {
int count = constraintLayout.getChildCount();
for(int i = 0; i < count; ++i) {
View view = constraintLayout.getChildAt(i);
int id = view.getId();
if (!this.mConstraints.containsKey(id)) {
Log.v(“ConstraintSet”, "id unknown " + Debug.getName(view));
} else {
if (this.mForceId && id == -1) {
throw new RuntimeException(“All children of ConstraintLayout must have ids to use ConstraintSet”);
}
if (this.mConstraints.containsKey(id)) {
ConstraintSet.Constraint constraint = (ConstraintSet.Constraint)this.mConstraints.get(id);
ConstraintAttribute.setAttributes(view, constraint.mCustomConstraints);
}
}
}
}
…
public static void setAttributes(View view, HashMap
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 呢?下面是来自 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
主要由以上几个属性组成,前两个相信大家都比较熟悉了,这里不必多说,另外 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 波形变化的快捷工具来查看它波形变化过程:
如此一来,我们就很直观地看到上图中描绘的波形变化过程了,的确是三个周期没有错,并且是以正弦 sin 来变化的。
关于这款工具的使用,大家可以前往:github.com/googlearchi… 上下载,然后通过执行 java -jar [xx/CycleEditor.jar]
即可看到可视化界面,然后将 KeyFrameSet 部分的代码 copy 到编辑栏,然后点击 File -> parse xml 即可看到代码对应的波形走势。如下所示:
我们来看看下面这个效果:
这个Q弹的效果就是基于 KeyCycle 实现的,我们来看看它的场景实现:
我们在动画路径上添加一些关键帧,并稍微改变控件的旋转角度,配合 keyCycle 就能达到上面的弹性动画,大家可以自己动手尝试体验一下。
很多时候,我们的控件并不只是单一的个体,而是需要与其他控件产生“交互上的关联”,常见地,Android 的Material design components 全家桶中提供了一套“优雅灵动”的组件,相信大家都体验过了,那么,我们的 MotionLayout 可以与它们碰撞出怎样的火花呢?
Material design 组件库中提供了一个 AppBarLayout 组件,我们经常使用它来配合 CoordinatorLayout 控件实现一些简单的交互动作,例如头部导航栏的伸缩效果,各位应该或多或少都用到过,这里不再介绍。下面我们就从 AppBarLayout 开始,看看如何实现与 MotionLayout 的联动。首先,我们先来看下面这个简单的效果:
我们知道,通过 CoordinatorLayout
和 AppBarLayout
也可以实现类似的交互效果,但显然 MotionLayout 会更加灵活多变。其实上面的动画效果很简单,只是在 AppBarLayout 高度变化过程中改变背景色、标题的位置和大小即可,对应的 MotionScene
文件代码如下所示:
结合以上效果图,我们很容易理解上面的场景实现代码,那么,我们再来看下布局文件:
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”>
android:layout_width=“match_parent”
android:layout_height=“260dp”
android:theme="@style/AppTheme.AppBarOverlay">
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”>
我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。
需要的小伙伴私信【学习】我免费分享给你,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)
Android学习PDF+架构视频+面试文档+源码笔记
部分资料一览:
Android学习的系统对应视频
Android进阶的系统对应学习资料
F%99%E4%BA%9B%EF%BC%9F%E5%A6%82%E4%BD%95%E9%9D%A2%E8%AF%95%E6%8B%BF%E9%AB%98%E8%96%AA%EF%BC%81.md)
部分资料一览:
[外链图片转存中…(img-S5xT0dnI-1646479538681)]
[外链图片转存中…(img-JkjXFruu-1646479538682)]
Android学习的系统对应视频
Android进阶的系统对应学习资料
[外链图片转存中…(img-9DAoNEMq-1646479538682)]