前言
CoordinatorLayout
功能非常強大,而他的神奇之處就在于Behavior
對象,CoordinatorLayout
自己并不控制View
,所有的控制權都在Behavior
。前面我們講了系統(tǒng)自帶的Behavior
使用,現(xiàn)在我們嘗試著自定義Behavior
,實現(xiàn)自己想要的效果。
前段時間在項目中剛好用到了自定義Behavior
,接下來就看看如何一步步實現(xiàn)的,首先我們看看要實現(xiàn)的效果。
要自己定義CoordinatorLayout Behavior
,需要實現(xiàn)layoutDependsOn()
和onDependentViewChanged()
兩個方法。比如AppBarLayout.Behavior
就定義了這兩個關鍵方法。這個behavior
用于當滾動發(fā)生的時候讓AppBarLayout
發(fā)生改變。
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
// check the behavior triggered
android.support.design.widget.CoordinatorLayout.Behavior behavior = ((android.support.design.widget.CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
if (behavior instanceof AppBarLayout.Behavior) {
// do stuff here
}
}
實現(xiàn)
首先我們分析一下上面的界面,這就是一個簡單的用戶信息界面,界面中用到了可折疊的Toolbar
,隨著界面上下滑動,用戶的頭像可以跟隨著一起移動,并伴隨著變大變小。根據(jù)前面了解的知識,要實現(xiàn)上面的效果就需要用到的有CoordinatorLayout
、AppBarLayout
、CollapsingToolbarLayout
等。
布局代碼
<?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="me.shihao.coordinatorlayoutusage.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="240dp"
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:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_user_center_appbar_iv"
app:layout_collapseMode="parallax"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:navigationIcon="?attr/homeAsUpIndicator"
app:popupTheme="@style/AppTheme.PopupOverlay">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<TextView
android:id="@+id/tv_title"
style="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="個人信息"/>
</RelativeLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<ImageButton
android:id="@+id/ibtn_ico"
android:layout_width="70dp"
android:layout_height="70dp"
android:background="@drawable/usercenter_avator_bg"
android:padding="2dp"
android:scaleType="fitCenter"
android:src="@drawable/avator_default"
app:layout_behavior="me.shihao.coordinatorlayoutusage.UserInfoImageButtonBehavior"/>
<TextView
android:id="@+id/tv_title_nick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:paddingTop="32dp"
android:text="NickName"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_anchor="@id/ibtn_ico"
app:layout_anchorGravity="bottom|center_horizontal"/>
<include layout="@layout/content_scrolling"/>
</android.support.design.widget.CoordinatorLayout>
自定義的behavior代碼
public class UserInfoImageButtonBehavior extends CoordinatorLayout.Behavior<ImageButton> {
private String TAG = getClass().getSimpleName();
private int maxScrollDistance;
private float maxChildWidth;
private float minChildWidth;
private int toolbarHeight;
private int statusBarHeight;
private int appbarStartPoint;
private int marginRight;
public UserInfoImageButtonBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
//計算出頭像的最小寬度
minChildWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32, context.getResources()
.getDisplayMetrics());
//計算出toolbar的高度
toolbarHeight = context.getResources().getDimensionPixelSize(android.support.design.R.dimen
.abc_action_bar_default_height_material);
//計算出狀態(tài)欄的高度
statusBarHeight = XStatusBarHelper.getStatusBarHeight(context);
//計算出頭像居右的距離
marginRight = context.getResources().getDimensionPixelSize(R.dimen.activity_horizontal_margin);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, ImageButton child, View dependency) {
// Log.d(TAG, "layoutDependsOn");
//確定依賴關系,這里我們用作頭像的ImageButton相依賴的是AppBarLayout,也就是ImageButton跟著AppBarLayout的變化而變化。
return dependency instanceof AppBarLayout;
}
private int startX;
private int startY;
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, ImageButton child, View dependency) {
//這里的dependency就是布局中的AppBarLayout,child即顯示的頭像
if (maxScrollDistance == 0) {
//也就是第一次進來時,計算出AppBarLayout的最大垂直變化距離
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
maxScrollDistance = dependency.getBottom() - toolbarHeight - statusBarHeight - statusBarHeight;
else
maxScrollDistance = dependency.getBottom() - toolbarHeight;
}
//計算出appbar的開始的y坐標
if (appbarStartPoint == 0)
appbarStartPoint = dependency.getBottom();
//計算出頭像的寬度
if (maxChildWidth == 0)
maxChildWidth = Math.min(child.getWidth(), child.getHeight());
//計算出頭像的起始x坐標
if (startX == 0)
startX = (int) (dependency.getWidth() / 2 - maxChildWidth / 2);
//計算出頭像的起始y坐標
if (startY == 0)
startY = (int) (dependency.getBottom() - maxScrollDistance / 2 - maxChildWidth / 2 - toolbarHeight / 2);
//計算出appbar已經(jīng)變化距離的百分比,起始位置y減去當前位置y,然后除以最大距離
float expandedPercentageFactor = (appbarStartPoint - dependency.getBottom()) * 1.0f /
(maxScrollDistance * 1.0f);
//根據(jù)上面計算出的百分比,計算出頭像應該移動的y距離,通過百分比乘以最大距離
float moveY = expandedPercentageFactor * (maxScrollDistance - (appbarStartPoint - startY - toolbarHeight / 2
- minChildWidth / 2));
//根據(jù)上面計算出的百分比,計算出頭像應該移動的y距離
float moveX = expandedPercentageFactor * (startX + maxChildWidth - marginRight - minChildWidth);
//更新頭像的位置
child.setX(startX + moveX);
child.setY(startY - moveY);
//計算出當前頭像的寬度
float nowWidth = maxChildWidth - ((maxChildWidth - minChildWidth) * expandedPercentageFactor);
//更新頭像的寬高
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
params.height = params.width = (int) nowWidth;
child.setLayoutParams(params);
return true;
}
}
在代碼中有詳細的注釋,在這里我們用作頭像的是ImageButton
,與其相依賴的是AppBarLayout
,在layoutDependsOn
我們定義了兩者的依賴關系。在xml中使用時,我們通過app:layout_behavior
來設置。
app:layout_behavior="me.shihao.coordinatorlayoutusage.UserInfoImageButtonBehavior"
這里注意一下,我們引用時使用的是全路徑me.shihao.coordinatorlayoutusage.UserInfoImageButtonBehavior
。
Activity代碼
public class MainActivity extends AppCompatActivity {
private TextView tvTitle;
private TextView tvTitleNick;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
XStatusBarHelper.immersiveStatusBar(this);
XStatusBarHelper.setHeightAndPadding(this, toolbar);
tvTitle = (TextView) findViewById(R.id.tv_title);
tvTitleNick = (TextView) findViewById(R.id.tv_title_nick);
AppBarLayout appBar = (AppBarLayout) findViewById(R.id.app_bar);
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
float total = appBarLayout.getTotalScrollRange() * 1.0f;
//計算出滑動百分比
float p = Math.abs(verticalOffset) / total;
if (p > 0.5) {
tvTitle.setAlpha(1.0f / 0.5f * (p - 0.5f));
tvTitleNick.setAlpha(0);
} else {
tvTitle.setAlpha(0);
tvTitleNick.setAlpha(1.0f - 1.0f / 0.5f * p);
}
}
});
}
}
這里我們通過設置AppBarLayout
的addOnOffsetChangedListener
,來監(jiān)聽AppBarLayout
的變化,然后通過變化來改變Title
與NickName
的顯示隱藏。
基本代碼就是這樣,接下里我們看看實際的效果。
實際測試中卻發(fā)現(xiàn)了還有另外一個問題,通過對比4.1與6.0上的效果,發(fā)現(xiàn)在6.0上當推到最上面時,頭像被隱藏了,而我們實際需要的是不隱藏。為了解決這個問題,我在toolbar中增加了一個ImageButton
,用做最后顯示頭像,通過監(jiān)聽變化設置其顯示隱藏。
改變后的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="me.shihao.coordinatorlayoutusage.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="240dp"
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:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_user_center_appbar_iv"
app:layout_collapseMode="parallax"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:navigationIcon="?attr/homeAsUpIndicator"
app:popupTheme="@style/AppTheme.PopupOverlay">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<TextView
android:id="@+id/tv_title"
style="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="個人信息"/>
<ImageButton
android:id="@+id/ibtn_title_ico"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:background="@drawable/usercenter_avator_bg"
android:padding="2dp"
android:scaleType="fitCenter"
android:src="@drawable/avator_default"/>
</RelativeLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<ImageButton
android:id="@+id/ibtn_ico"
android:layout_width="70dp"
android:layout_height="70dp"
android:background="@drawable/usercenter_avator_bg"
android:padding="2dp"
android:scaleType="fitCenter"
android:src="@drawable/avator_default"
app:layout_behavior="me.shihao.coordinatorlayoutusage.UserInfoImageButtonBehavior"/>
<TextView
android:id="@+id/tv_title_nick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:paddingTop="32dp"
android:text="NickName"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_anchor="@id/ibtn_ico"
app:layout_anchorGravity="bottom|center_horizontal"/>
<include layout="@layout/content_scrolling"/>
</android.support.design.widget.CoordinatorLayout>
改變后的Activity代碼
public class MainActivity extends AppCompatActivity {
private TextView tvTitle;
private TextView tvTitleNick;
private ImageButton ibtnTitleIco;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
XStatusBarHelper.immersiveStatusBar(this);
XStatusBarHelper.setHeightAndPadding(this, toolbar);
tvTitle = (TextView) findViewById(R.id.tv_title);
tvTitleNick = (TextView) findViewById(R.id.tv_title_nick);
ibtnTitleIco = (ImageButton) findViewById(R.id.ibtn_title_ico);
AppBarLayout appBar = (AppBarLayout) findViewById(R.id.app_bar);
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
float total = appBarLayout.getTotalScrollRange() * 1.0f;
//計算出滑動百分比
float p = Math.abs(verticalOffset) / total;
if (p > 0.5) {
tvTitle.setAlpha(1.0f / 0.5f * (p - 0.5f));
tvTitleNick.setAlpha(0);
} else {
tvTitle.setAlpha(0);
tvTitleNick.setAlpha(1.0f - 1.0f / 0.5f * p);
}
ibtnTitleIco.setVisibility(p == 1 ? View.VISIBLE : View.INVISIBLE);
}
});
}
}
最終的效果如下:
勉強算是解決了吧,個人能力有限,如果你有更好的方法,歡迎再下面留言。
項目中為了實現(xiàn)沉浸式狀態(tài)欄使用了XStatusBarHelper,詳細使用請前往查看。
如何實現(xiàn)沉浸式狀態(tài)欄:http://www.lxweimin.com/p/00fed1371bb0
https://github.com/fodroid/XStatusBarHelper