由于业务需要,仿小米手机的“小爱同学”这个应用的首页上划动效。
通过一番搜索,可以使用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>
app:elevation
,用于设置AppBarLayout
阴影高度,这里不需要阴影,设为0dp
。android:minHeight
,用于设置CollapsingToolbarLayout
收缩后的高度。app:layout_scrollFlags
,设置成scroll
可以使CollapsingToolbarLayout
跟随NestedScrollView
的滑动,设置成exitUntilCollapsed
,可以使CollapsingToolbarLayout
先收缩直到折叠。app:layout_collapseMode
,设置成pin
,可以使右上角的设置按钮不随CollapsingToolbarLayout
的收缩而滑动,即使其固定住位置。- 因为
CollapsingToolbarLayout
实际上是一个FrameLayout
,因此其子View均需要使用绝对布局。 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,因此我选择在此时获取一些属性值。