Android 进阶——Material Design新控件之AppBarLayout+Toolbar+CollapsingToolbarLayout实现动态变化的头部(八)

引言

前面系列文章总结了Material Design 兼容库提供大部分新控件的使用,如果你看完前一篇关于Android进阶——Material Design新控件之利用CoordinatorLayout协同多控件交互(七)的文章,你会发现Material Design不仅仅是提供了一种统一的设计标准,同时还提供了对应的一套控件,相比于传统的控件增强了交互功能及动画效果,使得原来需要自己用很多代码去实现的效果,现在只需要使用对应的控件即可,而且很多控件都借助了“Behavior”机制,系列文章链接:

一、Toolbar

1、Toolbar概述

ToolBar直接继承ViewGroup是对原来ActionBar的整合,可以看成ActionBar的升级和替代者,简而言之就是ToolBar 内部支持了更多配置的属性以及设计了这些元素的事件监听接口,以及在ToolBar内部支持以下元素:

  • Navigation Button,可以用于侧边栏的弹出按钮,也可以作为返回按钮
  • Logo
  • Title和SubTitle
  • 任意自定义布局
  • Action Menu溢出菜单
    在这里插入图片描述

2、Toolbar的应用

1、在XMl中配置Toolbar

很多属性都可以直接在xml中配置使用,当然也可以通过对应的方法,只有溢出菜单需要在代码中动态生成。

<?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"
    tools:context=".view.ToolBarActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:background="@color/colorPrimaryDark"
        app:logo="@mipmap/ic_logo"
        app:navigationIcon="@mipmap/ic_navig"
        app:subtitle="          next"
        app:titleTextAppearance="@style/AppTheme"
        app:subtitleTextColor="@android:color/white"
        app:title="       Material Design"
        app:titleTextColor="@android:color/white">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="32dp"
            android:background="@color/backgroundColor"
            android:orientation="vertical">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@mipmap/ic_logo" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="自定义View" />
        </LinearLayout>

    </android.support.v7.widget.Toolbar>

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

2、简单使用Toolbar


public class ToolBarActivity extends AppCompatActivity {
    private Toolbar toolbar;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_toolbar);
        initToolBar();
    }

    private void initToolBar() {
        toolbar = findViewById(R.id.toolbar);
        //设置溢出菜单
        toolbar.inflateMenu(R.menu.layout_toolbar_menu);
        //设置navigationIcon
        toolbar.setNavigationIcon(getResources().getDrawable(R.mipmap.more));
        //给navigationIcon注册点击时事件
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(ToolBarActivity.this,"点击我啦",Toast.LENGTH_LONG).show();
            }
        });
        //给溢出菜单注册点击事件
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                switch (menuItem.getItemId()){
                    case R.id.github:
                        Toast.makeText(ToolBarActivity.this,"点击info啦",Toast.LENGTH_LONG).show();
                        break;
                    case R.id.about:
                        Toast.makeText(ToolBarActivity.this,"点击about啦",Toast.LENGTH_LONG).show();
                        break;
                    case R.id.more:
                        Toast.makeText(ToolBarActivity.this,"点击more啦",Toast.LENGTH_LONG).show();
                        break;
                        default:
                            break;
                }
                return false;
            }
        });
    }
}

二、AppBarLayout概述

AppBarLayout继承自LinearLayout,可以看成加强型的竖直线性布局(不支持水平布局),相比传统的线性布局,AppBarLayout 增加了滑动手势的支持以及提供了MD 风格的动画和视觉效果(比如说浮层效果,立体感、交互特效),通过给其子View上app:layout_scrollFlags属性并配合CoordinatorLayout可以使得对应的子View接收到可滚动的View滑动手势改变时的事件

1、layout_scrollFlags

layout_scrollFlags是AppbarLayout提供给其子View使用的属性(也可以通过setScrollFlags方法设置),其中layout_scrollFlags的值是scroll,enterAlways,enterAlwaysCollapsed,exitUntilCollapsed,snap组合构成五种动效:

  • scroll——子View将会随着可滚动View(如ScrollView、ListView、RecycleView、NestedScrollView等)一起滚动,就好像子View 是属于ScrollView的一部分一样。

  • scroll | enterAlways—— 当ScrollView 向下滑动时,子View 将直接向下滑动,而不管ScrollView 是否在滑动。必须要与scroll 搭配使用,否者是不能滑动的。

  • scroll|enterAlways|enterAlwaysCollapsed_ enterAlwaysCollapsed 是对enterAlways 的补充,当ScrollView 向下滑动的时候,滑动View(也就是设置了enterAlwaysCollapsed 的View)下滑至折叠的高度(是通过View的minimum height (最小高度)指定的),当ScrollView 到达滑动范围的结束值的时候,滑动View剩下的部分开始滑动。

  • exitUntilCollapsed——当ScrollView 滑出屏幕时(即滑出边界时),滑动View先响应滑动事件,滑动至折叠高度,即通过minimum height 设置的最小高度后,就固定不动了,再把滑动事件交给 scrollview 继续滑动。

  • snap——在滚动结束后,如果view只是部分可见,它将滑动到最近的边界。比如view的底部只有25%可见,它将滚动离开屏幕,而如果底部有75%可见,它将滚动到完全显示。

2、AppBarLayout的方法

  • public void addOnOffsetChangedListener——当AppbarLayout 的偏移发生改变的时候回调。

  • public final int getTotalScrollRange——返回AppbarLayout 所有子View的滑动范围

  • public void removeOnOffsetChangedListener——移除监听器

  • public void setExpanded (boolean expanded, boolean animate)——设置AppbarLayout 是展开状态还是折叠状态,animate 参数控制切换到新的状态时是否需要动画

  • public void setExpanded (boolean expanded)——设置AppbarLayout 是展开状态还是折叠状态,默认有动画

3、AppBarLayout的使用

AppBarLayout的布局略。

public class AppbarActivity extends AppCompatActivity {
    private AppBarLayout appbar_layout;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_appbar);
        initView();
    }

    private void initView() {
        appbar_layout = findViewById(R.id.appbar_layout);
        //当AppbarLayout 的偏移发生改变的时候回调,也就是子View滑动
        appbar_layout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
                //
            }
        });
        //返回子View的可滑动距离
        appbar_layout.getTotalScrollRange();
        //移除偏移监听器
        appbar_layout.removeOnOffsetChangedListener(null);
    }
}

三、CollapsingToolbarLayout

CollapsingToolbarLayout继承自FrameLayout,顾名思义折叠工具栏布局,常作为AppBarLayout·的子View使用。当Collapsing title布局全部可见的时候,title 是最大的,当布局开始滑出屏幕,title 将变得越来越小,可以通过setTitle(CharSequence) 来设置要显示的标题。

当Toolbar 和CollapsingToolbarLayout 同时设置了title时,不会显示Toolbar中的title,只是显示CollapsingToolbarLayout 的title;但如果要显示Toolbar 的title,可在代码中添加如下代码:collapsingToolbarLayout.setTitle("")。另外必须给CollapsingToolbarLayout设置一个具体值(wrap_parent无效或者toolbar设置一个值来撑大CollapsingToolbarLayout也无效)

  • 设置Content scrim(内容纱布)——当CollapsingToolbarLayout滑动到一个确定的阀值时将显示或者隐藏内容纱布,可以通过setContentScrim(Drawable)方法来设置纱布的图片。

  • 设置Status bar scrim(状态栏纱布)——当CollapsingToolbarLayout滑动到一个确定的阀值时,状态栏显示或隐藏纱布,你可以通过setStatusBarScrim(Drawable)来设置Status bar scrim(状态栏纱布)

  • Pinned position children(固定子View的位置)——子View可以固定在全局空间内,这对于实现了折叠并且允许通过滚动布局来固定Toolbar 这种情况非常有用

  • Parallax scrolling children(有视差地滚动子View)——在布局中配置app:layout_collapseMode="parallax"让CollapsingToolbarLayout 的子View 可以有视差的滚动

app:layout_collapseParallaxMultiplier=“0.7” 这个参数是设置视差范围的,0-1,越大视差越大

四、AppBarLayout+Toolbar+CollapsingToolbarLayout

1、定义布局

<?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.support.design.widget.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapse_layout"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            >
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@mipmap/default_header"
                app:layout_collapseMode="parallax"
                />
            <android.support.v7.widget.Toolbar
                android:id="@+id/appbar_layout_toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:title="AppbarLayout"
                app:titleTextColor="@android:color/white"
                app:navigationIcon="@mipmap/ic_navig"
                app:layout_collapseMode="pin"
                />
        </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">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <View
                android:layout_width="match_parent"
                android:layout_height="88dp"
                android:background="#FF7FFFD4"
                />
            <View
                android:layout_width="match_parent"
                android:layout_height="88dp"
                android:background="#FF458B74"/>
            <View
                android:layout_width="match_parent"
                android:layout_height="88dp"
                android:background="#FF00CED1"/>
            <View
                android:layout_width="match_parent"
                android:layout_height="88dp"
                android:background="#FF7FFF00"/>
            <View
                android:layout_width="match_parent"
                android:layout_height="88dp"
                android:background="#FFCD5C5C"/>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

2、监听对应事件实现动效

/**
 * 折叠控件
 */
public class CollapsingToolbarLayoutActivity extends AppCompatActivity {
    private Toolbar toolbar;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_collapsing_toolbar);
        initView();
    }

    private void initView(){
        initToolBar();
        //设置沉浸式状态栏
        StatusBarUtils.setTranslucentImageHeader(this,0,toolbar);

        AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
        final CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.collapse_layout);
        collapsingToolbarLayout.setTitle("");
        collapsingToolbarLayout.setCollapsedTitleTextColor(getResources().getColor(R.color.white));
        collapsingToolbarLayout.setExpandedTitleColor(getResources().getColor(R.color.white));
        collapsingToolbarLayout.setExpandedTitleColor(Color.TRANSPARENT);
        //设置纱布
        collapsingToolbarLayout.setContentScrim(getResources().getDrawable(R.mipmap.collapse_header));
        //监听appBarLayout的偏移
        appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                if(Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()){
                    toolbar.setTitleTextColor(getResources().getColor(R.color.white));
                    collapsingToolbarLayout.setTitle("AppbarLayout");
                }else{
                    collapsingToolbarLayout.setTitle("");
                }
            }
        });
    }

    private void initToolBar() {
        toolbar =  findViewById(R.id.appbar_layout_toolbar);
        //设置标题颜色
        toolbar.setTitleTextColor(Color.TRANSPARENT);
        //设置溢出菜单
        toolbar.inflateMenu(R.menu.layout_toolbar_menu);
        //设置navigationIcon
        toolbar.setNavigationIcon(getResources().getDrawable(R.mipmap.more));
        //给navigationIcon注册点击时事件
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(CollapsingToolbarLayoutActivity.this,"点击Navagtion button",Toast.LENGTH_LONG).show();
            }
        });
        //给溢出菜单注册点击事件
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                switch (menuItem.getItemId()){
                    case R.id.github:
                        Toast.makeText(CollapsingToolbarLayoutActivity.this,"点击info啦",Toast.LENGTH_LONG).show();
                        break;
                    case R.id.about:
                        Toast.makeText(CollapsingToolbarLayoutActivity.this,"点击about啦",Toast.LENGTH_LONG).show();
                        break;
                    case R.id.more:
                        Toast.makeText(CollapsingToolbarLayoutActivity.this,"点击more啦",Toast.LENGTH_LONG).show();
                        break;
                    default:
                        break;
                }
                return false;
            }
        });
    }
}

public class StatusBarUtils {


    public static void setColor(Activity activity, @ColorInt int color, int statusBarAlpha){
        //先设置的全屏模式
        setFullScreen(activity);
        //在透明状态栏的垂直下方放置一个和状态栏同样高宽的view
        addStatusBarBehind(activity,color,statusBarAlpha);
    }
    /**
     * 添加了一个状态栏(实际上是个view),放在了状态栏的垂直下方
     */
    public static void addStatusBarBehind(Activity activity, @ColorInt int color, int statusBarAlpha) {
        //获取windowphone下的decorView
        ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
        int       count     = decorView.getChildCount();
        //判断是否已经添加了statusBarView
        if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
            decorView.getChildAt(count - 1).setBackgroundColor(calculateStatusColor(color, statusBarAlpha));
        } else {
            //新建一个和状态栏高宽的view
            StatusBarView statusView = createStatusBarView(activity, color, statusBarAlpha);
            decorView.addView(statusView);
        }
        setRootView(activity);
    }

    public static void setTranslucentImageHeader(Activity activity, int alpha,View needOffsetView){
        setFullScreen(activity);
        //获取windowphone下的decorView
        ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
        int       count     = decorView.getChildCount();
        //判断是否已经添加了statusBarView
        if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
            decorView.getChildAt(count - 1).setBackgroundColor(Color.argb(alpha, 0, 0, 0));
        } else {
            //新建一个和状态栏高宽的view
            StatusBarView statusView = createTranslucentStatusBarView(activity, alpha);
            decorView.addView(statusView);
        }

        if (needOffsetView != null) {
            ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams();
            layoutParams.setMargins(0, getStatusBarHeight(activity), 0, 0);
        }

    }



    private static StatusBarView createTranslucentStatusBarView(Activity activity, int alpha) {
        // 绘制一个和状态栏一样高的矩形
        StatusBarView statusBarView = new StatusBarView(activity);
        LinearLayout.LayoutParams params =
                new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
        statusBarView.setLayoutParams(params);
        statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
        return statusBarView;
    }

    /**
     * 设置根布局参数
     */
    private static void setRootView(Activity activity) {
        ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
        //rootview不会为状态栏流出状态栏空间
        ViewCompat.setFitsSystemWindows(rootView,true);
        rootView.setClipToPadding(true);
    }
    private static StatusBarView createStatusBarView(Activity activity, int color, int alpha) {
        // 绘制一个和状态栏一样高的矩形
        StatusBarView statusBarView = new StatusBarView(activity);
        LinearLayout.LayoutParams params =
                new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
        statusBarView.setLayoutParams(params);
        statusBarView.setBackgroundColor(calculateStatusColor(color, alpha));
        return statusBarView;
    }

    /**
     * 获取状态栏高度
     *
     * @param context context
     * @return 状态栏高度
     */
    private static int getStatusBarHeight(Context context) {
        // 获得状态栏高度
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        return context.getResources().getDimensionPixelSize(resourceId);
    }
    /**
     * 计算状态栏颜色
     *
     * @param color color值
     * @param alpha alpha值
     * @return 最终的状态栏颜色
     */
    private static int calculateStatusColor(int color, int alpha) {
        float a = 1 - alpha / 255f;
        int red = color >> 16 & 0xff;
        int green = color >> 8 & 0xff;
        int blue = color & 0xff;
        red = (int) (red * a + 0.5);
        green = (int) (green * a + 0.5);
        blue = (int) (blue * a + 0.5);
        return 0xff << 24 | red << 16 | green << 8 | blue;
    }

    public static  void setFullScreen(Activity activity){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
                    | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
        }else
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // 设置透明状态栏,这样才能让 ContentView 向上
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }

    public static class StatusBarView extends View {

        public StatusBarView(Context context) {
            super(context);
        }

        public StatusBarView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public StatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    }
}

在这里插入图片描述

理论上Material Design库都是应该放在CoordinatorLayout下才会发挥最大的效果的,因为CoordinatorLayout是相当于给他们提供了交互的能力,核心还是Behavior。

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/103196960