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

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

通过一番搜索,可以使用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也是可以的,但我还是没清楚原因……

布局代码如下:




    

        

            

            

            

            

            

            

        

    

    

        

    



  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,因此我选择在此时获取一些属性值。

你可能感兴趣的:(安卓开发心得)