ToolBar基礎使用
Toolbar是Android 5.0推出的一個Material Design風格的導航控件 ,與之前的Actionbar 相比,Toolbar更靈活,它實質就是個ViewGroup。Toolbar可定製性很強:
這是一個典型的ToolBar例子,示例代碼網上一大堆了,此處就不贅述了,需要注意的是,上圖的clock是個在ToolBar中的自定義控件。
在使用時,必須先隱藏系統原先的導航欄,建議用Theme.AppCompat.Light.NoActionBar做app的theme。從上圖可以看出,其實自定義view的空間是比較小的,同時,一般我們使用的是android.support.v7.widget.Toolbar,因此在xml定義TooBar屬性(如title)時,需要使用的是http://schemas.android.com/apk/res-auto的title。
還有一點就是,如果要修改更多按鈕(上圖最右側的那個黑色的三小點按鈕),可以先在xml中設置Toolbar的主題android:theme=”@style/Theme.ToolBar.Sample”:
<style name="Theme.ToolBar.Sample" parent="Theme.AppCompat.Light.NoActionBar">
<item name="actionOverflowButtonStyle">@style/ActionButton.Overflow.Sample</item>
</style>
<style name="ActionButton.Overflow.Sample" parent="android:style/Widget.Holo.Light.ActionButton.Overflow">
<item name="android:src">@mipmap/menu</item>
</style>
如此就能修改圖標爲@mipmap/menu了,更詳細的使用可參照官方文檔,此外,部分屬性設置可參照這裏。實際使用中,ToolBar往往不能滿足我們的需求(太醜),因而需要一些更酷炫,更強定製化的控件。
可伸縮頭部標題欄
先上效果圖
FloatingActionButton與Snackbar
不詳述,直接上代碼:
android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:clickable="true"
android:layout_marginRight="50dp"
app:layout_anchor="@+id/appbar"
app:layout_anchorGravity="bottom|right"
app:elevation="6dp" // 默認狀態下FAB的陰影大小
app:pressedTranslationZ="12dp" // 點擊時候FAB的陰影大小
app:backgroundTint="#0058f1" // 設置FAB的背景顏色
app:rippleColor="#ff0000" // 設置FAB點擊時的背景顏色
android:src="@mipmap/ic_launcher" //設置FAB的圖標,大小應爲24dp
app:fabSize="normal" //normal或mini,對應的FAB大小分別爲56dp和40dp
app:borderWidth="0dp" //該屬性尤爲重要,如果不設置0dp,那麼在4.1的sdk上FAB會顯示爲正方形,而且在5.0以後的sdk沒有陰影效果 />
fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v,
// 最多兩行文字
"最多兩行文字最多兩行文字最多兩行文字最多兩行文字最多兩行文字最多兩行文字最多兩行文字", Snackbar.LENGTH_SHORT)
// 文本內容右側的可點擊區域,會擠佔左側文本
.setAction("可點擊區域,會擠佔左側文本", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(AppBarActivity.this, "點擊了可點擊區域", Toast.LENGTH_SHORT).show();
}
}).show();
}
});
CoordinatorLayout
思前想後,還是先把最核心的東西提到前面來寫了。在上例中,fab會保持在頭部的底線上跟隨頭部運動,並最終消失,按照以往的實現方法,肯定是寫一大堆代碼,去監聽頭部高度的變化,再去設置fab的屬性,這樣的話,代碼的耦合度一定很大,而使用CoordinatorLayout的話,只需在fab中加入:
<android.support.design.widget.CoordinatorLayout ...>
<android.support.design.widget.AppBarLayout android:id="@+id/appbar" ...>
<android.support.design.widget.CollapsingToolbarLayout ...>
...
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
...
<android.support.design.widget.FloatingActionButton
app:layout_anchor="@+id/appbar"
app:layout_anchorGravity="bottom|right"
.../>
CoordinatorLayout主要用途就是協調子View的佈局,簡單來說就是讓一個Child的View,根據另一個叫做Dependency的View的運動(如位置移動等),來改變自身的屬性。上例中,通過使用app:layout_anchor和app:layout_anchorGravity,可以將FloatingActionButton 放在AppBarLayout 的右下角,並且AppBarLayout 變化時,FloatingActionButton 始終會跟隨運動,一直保持在這個相對位置。
這個運動,本質上來說,是通過Behavior作用的。Behavior是CoordinatorLayout體系的核心,Child發生變化的具體執行的代碼都是放在Behavior裏面的。假設我們現在要實現一個效果:
隨着頭部收起,圓形圖標從右下角運動到左上角,並逐漸縮小,我們先定義其Behavior:
// 繼承CoordinatorLayout.Behavior類
public class IVBehavior extends CoordinatorLayout.Behavior<ImageView> {
// 必須重寫帶雙參的構造器,因爲從xml反射需要調用。
public IVBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
// 左右邊距15
MAX_LEFT_DETAL = ViewUtil.getScreenWidth() - ViewUtil.dp2px(130);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, ImageView child, View dependency) {
// 判斷parent下被依賴的子view
return dependency != null && dependency.getId() == R.id.tb;
}
int initTBY = 0;
int MAX_LEFT_DETAL;
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, ImageView child, View dependency) {
//記錄開始的Y座標 也就是toolbar起始Y座標
if (initTBY == 0) {
initTBY = (int) dependency.getY();
}
//計算toolbar從開始移動到最後的百分比
float percent = dependency.getY() / initTBY;
// 大小變化
int width = ViewUtil.dp2px(40 + 60 * percent);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = width;
lp.height = width;
child.setLayoutParams(lp);
// 位置變化
child.setY(ViewUtil.dp2px(10 + 150 * percent));
child.setX(ViewUtil.dp2px(15) + MAX_LEFT_DETAL * percent);
return true;
}
}
再在此ImageView中定義:
<ImageView
app:layout_behavior=".IVBehavior"
...
/>
解決了(-_-)…實現Behavior,主要就是步驟就是:
1. 繼承CoordinatorLayout.Behavior,T爲Child的View類型;
2. 實現雙參構造方法(Context context, AttributeSet attrs),以在xml中能被調用;
3. 重寫layoutDependsOn,判斷CoordinatorLayout 中,Child依賴的View,即Dependency;
4. 重寫onDependentViewChanged,根據dependency屬性的值或變化,設定當前Child的各種需要變化的屬性。
5. 在xml中定義app:layout_behavior。
可以看到,通過使用Behavior,將Child和Dependency之間的關係完全解耦了,不需要跟傳統做法一樣,需要往Dependency中注入各種監聽了。
AppBarLayout
AppBarLayout繼承LinearLayout,垂直方向,一般是綁定CoordinatorLayout(parent)使用的。它可以讓你定製,當CoordinatorLayout內某個可滾動View的滾動手勢發生變化時,AppBarLayout內部的子View實現各種動作(主要是伸縮)。簡單來說,就是如上面例子中,隨着CoordinatorLayout下部的列表上下推時,頭部AppBarLayout的伸縮的效果。簡化代碼如下:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f6f6f6">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff">
<TextView
android:layout_width="match_parent"
android:layout_height="150dp"
android:minHeight="50dp"
android:background="#ff0000"
android:gravity="center"
android:text="1"
android:textColor="#fff"
android:textSize="20sp"
android:textStyle="bold"
app:layout_scrollFlags="scroll"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ToolBar圖標必須爲......很長的文本"
android:textSize="32sp"/>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
可滾動View(上例中的NestedScrollView)通過app:layout_behavior=”@string/appbar_scrolling_view_behavior”與AppBarLayout進行關聯,其實就是用了系統定義的ScrollingViewBehavior,從而在AppBarLayout伸縮時,NestedScrollView會跟隨變化。
至於AppBarLayout的伸縮效果,則是其子View使用app:layout_scrollFlags來設置動作的:
1、scroll:以下的所有其他屬性,想要生效的話,都必須連着這個值一起使用。發生向下滾動事件時,優先滾動下部滾動View。
app:layout_scrollFlags="scroll"
2、enterAlways:發生向下滾動事件時,優先滾動上部伸縮View。
app:layout_scrollFlags="scroll|enterAlways"
3、enterAlwaysCollapsed:配合enterAlways使用,向下滾動時,上部伸縮View先向下滾動最小高度值,然後下部滾動View開始滾動,到達邊界時,上部伸縮View再向下滾動,直至顯示完全。
android:layout_height="150dp"
android:minHeight="50dp"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
4、exitUntilCollapsed:向上滾動時,上部伸縮View向上滾動退出直至最小高度,然後下部滾動View開始滾動,即上部伸縮View不會全部退出屏幕。
android:layout_height="150dp"
android:minHeight="50dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
5、snap:鬆開手指時,上部伸縮View會吸附到最相近的“完全效果”,類似ViewPager的效果。snap可以與上述的幾個效果一起使用。
android:layout_height="150dp"
android:minHeight="50dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
CollapsingToolbarLayout
說穿了就是對Toolbar的再包裝,必須得是AppBarLayout的直接子View,主要用來做伸縮:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f6f6f6">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#ffffff">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:id="@+id/collapsingToolbarLayout"
android:layout_height="match_parent"
app:collapsedTitleGravity="left|center_vertical"
app:collapsedTitleTextAppearance="@style/CollapsingText"
app:contentScrim="#f15800"
app:expandedTitleGravity="bottom|center_horizontal"
app:expandedTitleMarginBottom="50dp"
app:expandedTitleTextAppearance="@style/ExtendText"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:title="伸縮頭示例">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/timg"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"/>
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:id="@+id/toolbar"
app:collapseIcon="@mipmap/menu"
android:theme="@style/Theme.ToolBar.Sample"
app:contentInsetStartWithNavigation="20sp"
app:layout_collapseMode="pin"
android:layout_height="56dp"
app:navigationIcon="@mipmap/arrow_back">
...
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ToolBar圖標必須爲......很長的文本"
android:textSize="32sp"/>
</android.support.v4.widget.NestedScrollView>
...
</android.support.design.widget.CoordinatorLayout>
再放一遍效果吧:
CollapsingToolbarLayout會自動對title進行放大和縮小,需要特別注意的是,此效果要求title必須設置到CollapsingToolbarLayout裏,設置到Toolbar上是無效的。
通過設置:
app:layout_collapseMode="pin"
可以像ToolBar一樣,將子View固定在特定位置上,當佈局伸縮時,也不會被影響。
當設置成:
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"
時,子View就會像上部伸縮View裏的背景ImageView一樣,在伸縮時以視差的方式伸縮,視差的幅度通過app:layout_collapseParallaxMultiplier設置,1爲不動,0爲跟CollapsingToolbarLayout一樣的速度,一般取值(0,1)。
最後,國際慣例上代碼:https://github.com/lk666/ViewExp.git。