文章大綱
引言
前面系列文章總結了Material Design 兼容庫提供大部分新控件的使用,如果你看完前一篇關於Android進階——Material Design新控件之利用CoordinatorLayout協同多控件交互(七)的文章,你會發現Material Design不僅僅是提供了一種統一的設計標準,同時還提供了對應的一套控件,相比於傳統的控件增強了交互功能及動畫效果,使得原來需要自己用很多代碼去實現的效果,現在只需要使用對應的控件即可,而且很多控件都藉助了“Behavior”機制,系列文章鏈接:
- Android進階——Material Design新控件之初識TabLayout(一)
- Android進階——Material Design新控件之TabLayout製作可滾動的Tabs頁面(二)
- Android進階——Material Design新控件之Snackbar(三)
- Android進階——Material Design新控件之TextInputLayout(四)
- Android進階——Material Design新控件之FloatingActionButton(五)
- Android進階——Material Design新控件之NavigationView(六)
- Android進階——Material Design新控件之利用CoordinatorLayout協同多控件交互(七)
- Android 進階——Material Design新控件之利用AppBarLayout實現動態變化的頭部(八)
一、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。