最近在公司閒着沒事,下了幾款app看下有沒有好看的效果能不能動手實踐下,剛好就看到這個工具欄摺疊效果.
這一看不就是跟Material Design工具欄摺疊效果類似。我們捋一下效果是怎樣的,滑動的時候實現搜索欄漸變以及高度改變的工具欄摺疊效果。
知道大概效果,開始擼代碼。
首先我們先熟悉下Material Design摺疊欄的效果是怎樣的。
準備工作
相關控件瞭解
在創建activity的時候,android studio提供了一個叫ScrollingActivity的模版
點擊創建後之後出現一個有工具欄摺疊效果的activity。
ScrollActivity的佈局代碼如下:
<?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"
tools:context="com.m520it.test.ScrollingActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_scrolling" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
AppBarLayout是一種支持響應滾動手勢的app bar佈局(比如工具欄滾出或滾入屏幕),CollapsingToolbarLayout則是專門用來實現子佈局內不同元素響應滾動細節的佈局。
與AppBarLayout組合的滾動佈局(Recyclerview、NestedScrollView等)需要設置app:layout_behavior這個屬性
沒有設置的話,AppBarLayout將不會響應滾動佈局的滾動事件。
CollapsingToolbarLayout和ScrollView一起使用會有滑動bug,注意要使用NestedScrollView來替代ScrollView。
AppBarLayout的子佈局有5種滾動標識(就是上面代碼CollapsingToolbarLayout中配置的app:layout_scrollFlags屬性):
- scroll:將此佈局和滾動時間關聯。這個標識要設置在其他標識之前,沒有這個標識則佈局不會滾動且其他標識設置無效。
- enterAlways:任何向下滾動操作都會使此佈局可見。這個標識通常被稱爲“快速返回”模式。
- enterAlwaysCollapsed:假設你定義了一個最小高度(minHeight)同時enterAlways也定義了,那麼view將在到達這個最小高度的時候開始顯示,並且從這個時候開始慢慢展開,當滾動到頂部的時候展開完。
- exitUntilCollapsed:當你定義了一個minHeight,此佈局將在滾動到達這個最小高度的時候摺疊。
- snap:當一個滾動事件結束,如果視圖是部分可見的,那麼它將被滾動到收縮或展開。例如,如果視圖只有底部25%顯示,它將摺疊。相反,如果它的底部75%可見,那麼它將完全展開。
CollapsingToolbarLayout可以通過app:contentScrim設置摺疊時工具欄佈局的顏色,通過app:statusBarScrim設置摺疊時狀態欄的顏色。默認contentScrim是colorPrimary的色值,statusBarScrim是colorPrimaryDark的色值。
CollapsingToolbarLayout的子佈局有3種摺疊模式(Toolbar中設置的app:layout_collapseMode)
- off:這個是默認屬性,佈局將正常顯示,沒有摺疊的行爲。
- pin:CollapsingToolbarLayout摺疊後,此佈局將固定在頂部。
- parallax:CollapsingToolbarLayout摺疊時,此佈局也會有視差摺疊效果。
當CollapsingToolbarLayout的子佈局設置了parallax模式時,我們還可以通過app:layout_collapseParallaxMultiplier設置視差滾動因子,值爲:0~1。
FloatingActionButton這個控件通過app:layout_anchor這個設置錨定在了AppBarLayout下方。FloatingActionButton源碼中有一個Behavior方法,當AppBarLayout收縮時,FloatingActionButton就會跟着做出相應變化。
瞭解這麼多 大家可以新建個ScrollActivity模版去玩下這些屬性
接下來還有一個問題就是————工具欄的摺疊以及展開的狀態如何監聽?
從上面知道CollapsingToolbarLayout 是負責摺疊工具欄的佈局,AppBarLayout是負責素響應滾動細節的佈局。
那麼它們是如何實現聯動的?
查看AppBarLayout的源碼可以看到有這樣的一個接口OnOffsetChangedListerner
這個接口就是監聽當佈局AppBarLayout 出現滑動時響應的事件。
而CollapsingToolbarLayout 持有這個OnOffsetChangedListerner監聽對象
這樣我們可以看出當AppBarLayout出現滑動時,CollapsingToolbarLayou通過OnOffsetChangedListerner這個對象去響應AppBarLayout的滑動,做出對應工具欄的狀態
使用OnOffsetChangedListerner
代碼如下:
其中裏面offsetChanged 裏第二個參數verticalOffset含義指的是垂直滑動的距離
當滑動時我們打印看下verticalOffset的值
向上滑動得到的值是負的,初始值爲0 就是展開狀態。
這裏我們需要注意的是verticalOffset能滑動最遠距離爲
AppBarLayout的高度 減去 CollapsingToolbarLayout摺疊時的高度(這裏AppBarLayout可以通過layout_scrollFlags控制CollapsingToolbarLayout設置顯示或者隱藏狀態)
注意:在AppBarLayout 中設置android:fitsSystemWindows=”true”這個屬性回影響verticalOffset最終的值,會加上狀態欄的高度
至此,終於熟悉摺疊工具欄的效果時如何實現的,接下來就可以根據上面的原理去實現自定義的工具欄。
實現自定義摺疊工具欄
首先看下我根據上面的原理實現的效果:
效果分析
當我們滑動時
- 搜索框背景出現透明的漸變
- 高度逐漸變小至到跟toolbar工具欄高度一致
- 搜索框逐漸往上移動到最頂點。
思路分析
如何給搜索框背景出現透明漸變?
我們知道view有個設置透明值的方法 setAlpha(). 參數爲 0f到1.0f。
滑動時這個值如何確定,在上面我們提到過AppBarLayout 裏有OnOffsetChangedListerner這個滑動監聽。
通過這個方法獲取到 滑動距離verticalOffset 除以 能滑動的總距離 得出 漸變值。
如何給搜索框設置高度漸變
1.首先確定搜索框的高度漸變成工具欄toolbar 漸變百分比差值。
llHeightOffScale = 1.0f - (toolBarHeight / llHeight)
2.toolBarHeight 指的是工具欄toolBar的高度 llHeight是指搜索框佈局的高度。
每次滑動時得到的漸變差值爲:
滑動漸變差值/llHeightOffScale = 滑動距離(verticalOffset)/ 能滑動總距離
求出滑動時搜索框對應高度縮放指
float llHeightScale = 1.0f-(llHeightOffScale*((-verticalOffset)/offSetHeight));
3.得出高度
params.height = (int)(llHeight* llHeightScale);
如何設置搜索框佈局位移漸變
這裏我是通過滑動時設置搜索框佈局的margin來改變(也可以通過調用view的setTranslationY來變化Y軸距離)
1.通過LayoutParams獲取topMargin值
//得到滑動差值 就是佈局marginTop
llOffDistance = params.topMargin;
2.計算出每次滑動時marginTop的值,並設置
搜索框佈局滑動時的距離/總的滑動差值llOffDistance = 滑動距離/能滑動總距離
根據這個計算出滑動時的marginTop值
float distance = llOffDistance - (-verticalOffset)*llOffDistance / offSetHeight;
3.設置給搜索框佈局
params.setMargins(0,(int)distance,0,0);
//重新佈局子控件
fl.requestLayout();
下面給出佈局代碼以及Activity代碼:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:id="@+id/fl"
android:background="#fff"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.m520it.myapplication.TestScrollActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:statusBarScrim="@android:color/transparent"
app:contentScrim="@android:color/transparent"
app:layout_scrollFlags="scroll|snap|exitUntilCollapsed">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_collapseMode="none"
android:src="@mipmap/test"
android:scaleType="centerCrop"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/tool_bar_height"
app:layout_collapseMode="none"
app:contentInsetStart="0dp"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_test_scroll" />
</android.support.design.widget.CoordinatorLayout>
<!--搜索框佈局 -->
<RelativeLayout
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_anchor="@id/app_bar"
android:layout_marginTop="150dp"
app:layout_anchorGravity="bottom">
<TextView
android:id="@+id/bac"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorPrimary"/>
<EditText
android:id="@+id/fab"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="@dimen/fab_margin"
android:focusable="false"
android:background="@drawable/et_bac"
/>
</RelativeLayout>
</FrameLayout>
Activity代碼
public class TestScrollActivity extends AppCompatActivity {
private float totalHeight; //總高度
private float toolBarHeight; //toolBar高度
private float offSetHeight; //總高度 - toolBar高度 佈局位移值
private float llHeight; //搜索框高度
private float llHeightOffScale; //高度差比值
private float llOffDistance; //距離差
private float llOffDistanceScale; //距離差比值
private FrameLayout.LayoutParams params;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.toolbar_layout)
CollapsingToolbarLayout toolbarLayout;
@BindView(R.id.app_bar)
AppBarLayout appBar;
@BindView(R.id.fab)
EditText fab;
@BindView(R.id.ll)
RelativeLayout ll;
@BindView(R.id.bac)
TextView bac;
@BindView(R.id.fl)
FrameLayout fl;
@BindView(R.id.image)
ImageView image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling2);
ButterKnife.bind(this);
bac.setAlpha(0f);
totalHeight = getResources().getDimension(R.dimen.app_bar_height);
toolBarHeight = getResources().getDimension(R.dimen.tool_bar_height);
offSetHeight = totalHeight - toolBarHeight;
/**
* 移動效果值/最終效果值 = 移動距離/ 能移動總距離(確定)
*/
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
//第一次進入獲取高度,以及差值 高度差比值
if (llHeight == 0){
llHeight = ll.getMeasuredHeight();
params = (FrameLayout.LayoutParams) ll.getLayoutParams();
//算出高度偏移量比值 相對與llHeight
llHeightOffScale = 1.0f - (toolBarHeight / llHeight);
//得到滑動差值 就是佈局marginTop
llOffDistance = params.topMargin;
//得到滑動比值
llOffDistanceScale = llOffDistance / offSetHeight;
}
//滑動一次 得到漸變縮放值
float alphaScale = (-verticalOffset) / offSetHeight;
//獲取高度縮放值
float llHeightScale = 1.0f-(llHeightOffScale*((-verticalOffset)/offSetHeight));
//計算maigintop值
float distance = llOffDistance - (-verticalOffset)*llOffDistanceScale;
image.setAlpha(1.0f-alphaScale);
bac.setAlpha(alphaScale);
params.height = (int)(llHeight* llHeightScale);
params.setMargins(0,(int)distance,0,0);
fl.requestLayout();
}
});
}
}
感興趣的同學也可以根據AppBarLayout+CollapsingToolbarLayout再繼續寫出其它效果!
比如這樣的:
最後附上demo地址