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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章