阅读 232

ConstraintLayout动画 之 圆形动画

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的新特性

ConstraintLayout 1.1.1 最详细使用

今天主要是讲解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()
    }


复制代码

关键几点就是:

  1. 对LayoutParams.circleRadius做动画, 从0到最终位置end.
  2. 为了结尾有一个回弹的效果, 我们给animator加的interpolator是BounceInterpolator()

同时, 关闭菜单就可以使用:

    fun close() {
        startAnim(135, 0, false)
    }
复制代码

6. 最终效果

总体效果因为只是个demo, 所以只是粗糙的效果. 但相信大家也都了解了, 如何用ConstraintLayout来做圆形动画了. 比如说太阳系行星环绕的布局与动画, 也可以使用类似的技巧来做.

其实这也是我想说的一点, ConstraintLayout除了让你的布局层次锐减(提升了性能), 同时还很方便做动画. 要是配合ConstraintSet, ConstraintLayout.LayoutParams, Transition这些, 那可以使用很少的代码来做到复杂的动画. 我稍后也准备在这一块继续讲解些ConstraintLayout做动画的案例, 敬请关注.

关注下面的标签,发现更多相似文章
评论