2017年5月7号, ConstraintLayout 1.0.2发布. 近一年之后, 2018年4月12号, ConstraintLayout 1.1.0终于发布(下方可能会缩写ConstraintLayout为 ctlay ). Google的ConstraintLayout开发人员说他们花了这么多时间, 不仅是因为1.1.0有众多新功能, 还因为他们还要花时间来做AndroidStudio上ConstraintLayout的预览等功能. 对于我们开发来说, 1.1.0有诸多好用的功能, 掘金上其实已经有不少文章在介绍了. 如:
[译] 带你领略 ConstraintLayout 1.1 的新功能
约束布局(ConstraintLayout)1.1.0的新特性
今天主要是讲解1.1.x中的Circle Position布局的问题.
1. 以前要写个圆形布局的话...
以前在写圆形布局时是比较麻烦的, 得自己计算sin, cos. 而且有经验的同学肯定还记得Math.sin(num)中的num是弧度的, 不是角度. 即是用π来表示180°. 所以我们在计算角度时还得先把角度转成弧度, 如把90°转成π/2.
除此以外, 要是还有圆形布局还要配合动画, 那就更复杂了, 还要涉及到半径, 轨道等等.
举一个例子, 鸿洋在2015年初写过一篇文章Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单, 里面为了实现一个圆形菜单, 用了大量的数学计算, 我来摘录一部分, 大家随意感受一下.
left = layoutRadius
/ 2
+ (int) Math.round(tmp
* Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f
* cWidth);
return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
2. 现在有了ConstraintLayout
所幸的是, 现在我们有了ConstraintLayout 1.1.0, 圆形布局变得超容易了.
现在我们可以对ConstraintLayout中的任意子View加上下面三个属性:
- app:layout_constraintCircle — 值为一个id. 即是本子View以这个id的View为圆心
- app:layout_constraintCircleAngle — 角度. 是用角度, 而不是弧度. 这就方便多了.
- app:layout_constraintCircleRadius — 半径
举个例子: 我现在要让闹钟的按钮, 在hamburger按钮的正右边, 如下图所示:
那我们要做的就是在fabAlarm这个FloatingActionBar里加上三句:
<FloatingActionBar android:id="@+id/fabAlarm"
app:layout_constraintCircle="@+id/fabHamburger"
app:layout_constraintCircleRadius="100dp"
app:layout_constraintCircleAngle="90"
/>
同时注意, 经我测试, -45度不会是在圆心的左上角, circleAngle只接收正数. 所以你要有-45度的效果, 就应该使用315度.
3. 现在来做一个圆形菜单动画
早些年, 有一个应用叫Path, 它有一个圆弧菜单, 在当时还算蛮新颖的. 效果类似这样的:
(图出自 github/saurabharora90/MaterialArcMenu)
经过上面讲解, 这个动画就变容易了吧. 我们来分析一下, 几个菜单都是以270度, 300度, 330度, 360度的效果. 而整个弹出的效果其实就是radius在一直变大的效果嘛.
4. xml布局
这样分析了一下, 那我们布局好xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_height="match_parent"
>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabTotal"
android:layout_width="55dp" android:layout_height="55dp"
android:layout_margin="16dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent"
android:src="@drawable/ic_menu" android:tint="#fff"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabMenuAlarm"
android:layout_width="50dp" android:layout_height="50dp"
app:layout_constraintCircle="@+id/fabTotal"
app:layout_constraintCircleRadius="0dp"
app:layout_constraintCircleAngle="0"
android:src="@drawable/ic_alarm" android:tint="#fff"
android:visibility="invisible"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabMenuAutorenew"
android:layout_width="50dp" android:layout_height="50dp"
app:layout_constraintCircle="@+id/fabTotal"
app:layout_constraintCircleRadius="0dp"
app:layout_constraintCircleAngle="315"
android:src="@drawable/ic_autorenew" android:tint="#fff"
android:visibility="invisible"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabMenuBuild"
android:layout_width="50dp" android:layout_height="50dp"
app:layout_constraintCircle="@+id/fabTotal"
app:layout_constraintCircleRadius="0dp"
app:layout_constraintCircleAngle="270"
android:src="@drawable/ic_build" android:tint="#fff"
android:visibility="invisible"
/>
</android.support.constraint.ConstraintLayout>
5. 做动画
每次按一下处于圆心的fab时, 我们就开启动画
fun open() {
startAnim(0, 135, true)
}
private fun startAnim(start: Int, end: Int, isVisible: Boolean) {
val endRadius = end.dp2px(this) //一个extension方法, 把int转成dp的格式
val anim = ValueAnimator.ofInt(start, endRadius)
anim.duration = 1000
anim.interpolator = BounceInterpolator()
anim.addUpdateListener { valueAnimator ->
val radius: Int = valueAnimator.animatedValue as Int
menuViews.forEach { view ->
val lp = view.layoutParams as ConstraintLayout.LayoutParams
lp.circleRadius = radius
view.layoutParams = lp
}
}
anim.start()
}
关键几点就是:
- 对LayoutParams.circleRadius做动画, 从0到最终位置end.
- 为了结尾有一个回弹的效果, 我们给animator加的interpolator是BounceInterpolator()
同时, 关闭菜单就可以使用:
fun close() {
startAnim(135, 0, false)
}
6. 最终效果
总体效果因为只是个demo, 所以只是粗糙的效果. 但相信大家也都了解了, 如何用ConstraintLayout来做圆形动画了. 比如说太阳系行星环绕的布局与动画, 也可以使用类似的技巧来做.
其实这也是我想说的一点, ConstraintLayout除了让你的布局层次锐减(提升了性能), 同时还很方便做动画. 要是配合ConstraintSet, ConstraintLayout.LayoutParams, Transition这些, 那可以使用很少的代码来做到复杂的动画. 我稍后也准备在这一块继续讲解些ConstraintLayout做动画的案例, 敬请关注.