仿“小爱同学”首页上划动效

由于业务需要,仿小米手机的“小爱同学”这个应用的首页上划动效。

通过一番搜索,可以使用Material Design来实现这个效果,具体来说就是使用
CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+NestedScrollView

这几个类的介绍可以参考以下几篇文章:
细说 AppbarLayout,如何理解可折叠 Toolbar 的定制 - frank 的专栏 - CSDN博客
玩转AppBarLayout,更酷炫的顶部栏 - 简书
【Android】5.x炫酷标题栏动画使用理解 - 简书

使用这几个类需要在app模块的build.gradle添加

implementation 'com.android.support:design:27.1.1'

然而在布局添加了AppBarLayout之后,运行时报以下错误:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.****.osassistant/com.****.osassistant.app.welcome.WelcomeActivity}: android.view.InflateException: Binary XML file line #12: Binary XML file line #12: Error inflating class android.support.design.widget.AppBarLayout
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2818)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2903)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1610)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6602)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:453)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:821)
     Caused by: android.view.InflateException: Binary XML file line #12: Binary XML file line #12: Error inflating class android.support.design.widget.AppBarLayout
     Caused by: android.view.InflateException: Binary XML file line #12: Error inflating class android.support.design.widget.AppBarLayout
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Constructor.newInstance0(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:334)
        at android.view.LayoutInflater.createView(LayoutInflater.java:647)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:790)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
        at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:426)
        at android.app.Activity.setContentView(Activity.java:2687)

说实话我对这个报错实在是不明所以,Google搜索一遍后,也就发现这个:
c# - Binary XML file line #1: Error inflating class android.support.design.widget.AppBarLayout - Stack Overflow
使用AppCompatActivity代替Activity,再设置Theme.AppCompat,再运行,还真的不报错了……
后来发现,不使用AppCompatActivity,只设置Theme.AppCompat也是可以的,但我还是没清楚原因……

布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:background="#F0F2F7"
    tools:context="com.****.osassistant.app.welcome.WelcomeActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="211dp"
        android:background="#F0F2F7"
        app:elevation="0dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:minHeight="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/setting_iv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="end"
                android:layout_marginTop="12dp"
                android:layout_marginEnd="12dp"
                android:src="@mipmap/setting_black"
                app:layout_collapseMode="pin"/>

            <ImageView
                android:id="@+id/decor_iv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:src="@mipmap/group_decor"
                android:layout_gravity="center_horizontal"/>

            <ImageView
                android:id="@+id/icon_iv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="34dp"
                android:src="@mipmap/group"
                android:layout_gravity="center_horizontal"/>

            <TextView
                android:id="@+id/welcome_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="144dp"
                android:layout_gravity="center_horizontal"
                android:text="@string/welcome"
                android:textColor="#E6000000"
                android:textSize="18sp"/>

            <TextView
                android:id="@+id/welcome_text2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="175dp"
                android:layout_gravity="center_horizontal"
                android:text="@string/welcome_again"
                android:textColor="#80000000"
                android:textSize="14sp"/>

            <View
                android:id="@+id/welcome_divider"
                android:layout_width="match_parent"
                android:layout_height="0.5dp"
                android:layout_gravity="bottom"
                android:background="#D6D6D6"/>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <!-- content -->

    </android.support.v4.widget.NestedScrollView>

</android.support.design.widget.CoordinatorLayout>

  1. app:elevation,用于设置AppBarLayout阴影高度,这里不需要阴影,设为0dp
  2. android:minHeight,用于设置CollapsingToolbarLayout收缩后的高度。
  3. app:layout_scrollFlags,设置成scroll可以使CollapsingToolbarLayout跟随NestedScrollView的滑动,设置成exitUntilCollapsed,可以使CollapsingToolbarLayout先收缩直到折叠。
  4. app:layout_collapseMode,设置成pin,可以使右上角的设置按钮不随CollapsingToolbarLayout的收缩而滑动,即使其固定住位置。
  5. 因为CollapsingToolbarLayout实际上是一个FrameLayout,因此其子View均需要使用绝对布局。
  6. app:layout_behavior用于设置NestedScrollView的行为,这里的行为是绑定AppBarLayout

动画代码:

private void initAnim() {
        mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                if (verticalOffset == 0 && !init) {
                    barHeight = appBarLayout.getHeight();
                    barMinHeight = mCollapsingToolbarLayout.getMinimumHeight();
                    iconHeight = mIconIv.getHeight();
                    decorHeight = mDecorIv.getHeight();
                    iconY = mIconIv.getY();
                    iconNewY = barHeight - barMinHeight + iconNewMarginTop - (iconHeight - iconMinHeight) / 2;
                    init = true;
                }
                float percentage = (float) (barHeight - barMinHeight + verticalOffset) / (barHeight - barMinHeight);
                float scale = (percentage * (iconHeight - iconMinHeight) + iconMinHeight) / iconHeight;
                mIconIv.setScaleX(scale);
                mIconIv.setScaleY(scale);
                float y = (float) (-verticalOffset) / (barHeight - barMinHeight) * (iconNewY - iconY) + iconY;
                mIconIv.setY(y);

                mDecorIv.setScaleX(scale);
                mDecorIv.setScaleY(scale);
                mDecorIv.setY(y - (decorHeight - iconHeight) / 2);
                mDecorIv.setAlpha(percentage);

                mWelcome.setVisibility(verticalOffset == 0 ? View.VISIBLE : View.GONE);
                mWelcome2.setVisibility(verticalOffset == 0 ? View.VISIBLE : View.GONE);
                mDivider.setVisibility(verticalOffset == barMinHeight - barHeight ? View.VISIBLE : View.GONE);
                mCollapsingToolbarLayout.setBackgroundColor(verticalOffset == barMinHeight - barHeight ? Color.parseColor("#FEFEFE") : Color.parseColor("#F0F2F7"));
            }
        });
    }

这个函数主要指定CollapsingToolbarLayout内各个子View在其收缩时的大小及位置,以实现动画的效果。本来想看看有没有动画的类可以直接实现效果的,然而没找到,只能手动去算各个子View的实时大小及位置,如果读者有更好的方案可以讨论一下。
这里对AppBarLayout加了一个监听器,当其发生位移时会回调onOffsetChanged,其中verticalOffset在未发生位移时值为0,在发生位移时,由于是向上位移,因此值为负。值得注意的是,这个回调在初始化完成之后就会回调一次,其中verticalOffset的值为0,因此我选择在此时获取一些属性值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值