ConstraintLayout2.0一篇写不完之极坐标布局与动画
相对于一般布局方式的笛卡尔坐标系,MotionLayout还拓展了ConstraintLayout中的相对中心布局方式,我们暂且称之为「极坐标布局」方式。
极坐标布局方式在某些场景下,比笛卡尔坐标系的建立更加方便,特别是涉及到一些圆周运动和相对中心点运动的场景。
Rotational OnSwipe
在OnSwipe的基础上,极坐标方式拓展了运动的方向,给dragDirection增加了dragClockwise和dragAnticlockwise参数,用于设置OnSwipe的顺时针滑动和逆时针滑动,这两个属性,在设置rotationCenterId后才会生效。那么借助这个,就可以很方便的实现一些圆形路径的滑动效果和动画。
通过下面这个例子,我们来看下Rotational OnSwipe的使用方法。
首先,极坐标的布局还是借助ConstraintLayout,代码如下所示。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#cfc"
app:layoutDescription="@xml/motion_01_dial_scene"
app:motionDebug="SHOW_ALL">
<TextView
android:id="@+id/number1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textSize="24sp"
app:layout_constraintCircle="@id/dial"
app:layout_constraintCircleAngle="73"
app:layout_constraintCircleRadius="112dp"
app:layout_constraintTag="hop" />
......
<TextView
android:id="@+id/number0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="24sp"
app:layout_constraintCircle="@id/dial"
app:layout_constraintCircleAngle="172"
app:layout_constraintCircleRadius="112dp"
app:layout_constraintTag="hop" />
<ImageView
android:id="@+id/dial"
android:layout_width="300dp"
android:layout_height="300dp"
android:src="@drawable/dial"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTag="center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.8" />
<ImageView
android:id="@+id/dialhook"
android:layout_width="70dp"
android:layout_height="70dp"
android:src="@drawable/dial_hook"
app:layout_constraintCircle="@id/dial"
app:layout_constraintCircleAngle="122"
app:layout_constraintCircleRadius="112dp"
app:layout_constraintTag="hop" />
</androidx.constraintlayout.motion.widget.MotionLayout>
极坐标布局就是借助layout_constraintCircle、layout_constraintCircleAngle、layout_constraintCircleRadius来确定圆心、角度和半径,从而实现极坐标的布局,接下来,再通过OnSwipe来实现圆形滑动效果。
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
motion:defaultDuration="2000">
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@+id/dial">
<Transform android:rotation="0" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint android:id="@+id/dial">
<Transform android:rotation="300" />
</Constraint>
</ConstraintSet>
<Transition
motion:autoTransition="animateToStart"
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
motion:motionInterpolator="easeIn">
<OnSwipe
motion:dragDirection="dragClockwise"
motion:dragScale=".9"
motion:maxAcceleration="10"
motion:maxVelocity="50"
motion:onTouchUp="autoCompleteToStart"
motion:rotationCenterId="@id/dial" />
<KeyFrameSet>
</KeyFrameSet>
</Transition>
</MotionScene>
核心就在OnSwipe中,设置rotationCenterId后,再设置滑动的方向为顺时针即可,展示如下所示。
Relative Animation
在MotionLayout中,它进一步加强了在动画中对极坐标运动的支持,特别是一些极坐标的相对运动动画,可以通过MotionLayout,以非常简单的方式表现出来。我们举个简单的例子,一个行星环绕的动画,如下所示。
我们可以发现,这个动画的轨迹是非常复杂的,太阳以自己为中心自传,地球绕着太阳旋转的同时还在自传,月球绕着地球旋转,卫星绕着地球旋转的同时,逐渐远离地球,靠近月球。
这样一个复杂的极坐标动画效果,虽然借助ConstraintLayout可以很方便的实现定位布局,但是运动时,却无法继续保持极坐标的依赖关系,所以,这里需要使用MotionLayout来维持运动时的极坐标约束关系。
首先,使用ConstraintLayout来完成起始布局的建立,代码如下所示。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF003b60"
app:layoutDescription="@xml/motion_01_motion_scene"
app:motionDebug="SHOW_ALL">
<ImageView
android:id="@+id/sun"
android:layout_width="180dp"
android:layout_height="180dp"
android:src="@drawable/sun"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
<TextView
android:id="@+id/rocket"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="?"
android:textSize="28sp"
app:layout_constraintCircle="@id/earth"
app:layout_constraintCircleAngle="0"
app:layout_constraintCircleRadius="60dp" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/moon"
android:layout_width="16dp"
android:layout_height="16dp"
android:rotation="-240"
android:src="@drawable/moon"
app:layout_constraintCircle="@id/earth"
app:layout_constraintCircleAngle="0"
app:layout_constraintCircleRadius="180dp" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/earth"
android:layout_width="160dp"
android:layout_height="160dp"
android:src="@drawable/earth"
app:layout_constraintCircle="@id/sun"
app:layout_constraintCircleAngle="315"
app:layout_constraintCircleRadius="200dp" />
</androidx.constraintlayout.motion.widget.MotionLayout>
接下来,在Scene文件中,设置相对运动关系,代码如下所示。
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@id/earth">
<Motion motion:animateRelativeTo="@+id/sun" />
</Constraint>
<Constraint android:id="@id/moon">
<Motion motion:animateRelativeTo="@+id/earth" />
</Constraint>
<Constraint android:id="@+id/rocket">
<Motion
motion:animateRelativeTo="@+id/earth"
motion:motionPathRotate="45" />
</Constraint>
</ConstraintSet>
借助animateRelativeTo来实现Motion中的相对中心点,使用motionPathRotate来设置旋转的角度。
Motion标签中的motionPathRotate和Constraint标签中的transitionPathRotate的作用,都是让其相对于Path旋转一定角度。
MotionLayout中新增的属性非常多,大家可以参考我的这些文章,从各个方面,逐个击破MotionLayout的各个难点。