先來看看最終的效果~~
本文同步至博主的私人博客wing的地方酒館
嗯。。一個是頭像上移的 另一個是模仿UC瀏覽器的。
(PД`q。)你不是說!有三款的嗎,怎麼只有兩款!!!!
不要急嘛。。。 說了從簡到難,第一款是介紹概念的啦。
關於CoordinatorLayout,以及系統預留ScrollBehavior使用網上以及有很多文章,這裏就不闡述了,如果你還不瞭解,你可以查看[譯]掌握CoordinatorLayout
基礎概念
其實Behavior就是一個應用於View的觀察者模式,一個View跟隨者另一個View的變化而變化,或者說一個View監聽另一個View。
在Behavior中,被觀察View 也就是事件源被稱爲denpendcy,而觀察View,則被稱爲child。
開始自定義 難度1 Button與TextView的愛恨情仇
首先在佈局文件中跟佈局設置爲CoordinatorLayout,裏面放一個Button和一個TextView。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<TextView
app:layout_behavior=".EasyBehavior"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="觀察者View child"
/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="被觀察View dependency"
/>
</android.support.design.widget.CoordinatorLayout>
這裏我們在Activity中做一些手腳,讓Button動起來(不要在意座標這些細節)
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_easy_behavior);
findViewById(R.id.btn).setOnTouchListener(new View.OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
v.setX(event.getRawX()-v.getWidth()/2);
v.setY(event.getRawY()-v.getHeight()/2);
break;
}
return false;
}
});
}
此時,Button已經可以跟隨手指移動了。
現在去自定義一個Behavior讓TextView跟隨Button一起動!
創建一個EasyBehavior類,繼承於Behavior
public class EasyBehavior extends CoordinatorLayout.Behavior<TextView> {//這裏的泛型是child的類型,也就是觀察者View
public EasyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
//告知監聽的dependency是Button
return dependency instanceof Button;
}
@Override
//當 dependency(Button)變化的時候,可以對child(TextView)進行操作
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
child.setX(dependency.getX()+200);
child.setY(dependency.getY()+200);
child.setText(dependency.getX()+","+dependency.getY());
return true;
}
}
注意兩個方法
layoutDependsOn() 代表尋找被觀察View
onDependentViewChanged() 被觀察View變化的時候回調用的方法
在onDependentViewChanged中,我們讓TextView跟隨Button的移動而移動。代碼比較簡單,一看就懂。
Tip
必須重寫帶雙參的構造器,因爲從xml反射需要調用。
接下來,在xml中,給TextView設置我們的Behavior。
<TextView
app:layout_behavior=".EasyBehavior"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="觀察者View child"
/>
運行效果如下:
這樣一個最簡單的behavior就做好了。
難度 2 仿UC摺疊Behavior
這個效果佈局嵌套比上一個例子些許複雜,如果看起來吃力,務必去補習CoordinatorLayout!!!!
先定義xml如下:
<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:ignore="RtlHardcoded"
>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:elevation="0dp"
>
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:scaleType="centerCrop"
android:src="@drawable/bg"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.9"
/>
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="bottom|center_horizontal"
android:background="@color/primary"
android:orientation="vertical"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.3"
>
</FrameLayout>
</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"
android:scrollbars="none"
app:behavior_overlapTop="30dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<include layout="@layout/layout_main"/>
</android.support.v4.widget.NestedScrollView>
<android.support.v7.widget.Toolbar
android:id="@+id/main.toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primaryDark"
app:layout_anchor="@id/frameLayout"
app:theme="@style/ThemeOverlay.AppCompat.Dark"
>
</android.support.v7.widget.Toolbar>
<TextView
android:id="@+id/tv_title"
android:textColor="#fff"
android:textSize="18sp"
android:gravity="center"
android:text="頭條"
app:layout_behavior=".DrawerBehavior"
android:background="@color/primaryDark"
android:layout_width="match_parent"
android:layout_height="50dp"
>
</TextView>
</android.support.design.widget.CoordinatorLayout>
有一點值得注意的是,app:layout_anchor=”@id/frameLayout”這個屬性,是附着的意思,這裏我用作給了toolbar,代表toolbar附着在了frameLayout之上。會跟隨frameLayout的scroll而變化Y的值。
思路分析
如何實現摺疊呢,下半部分不用管了,AppBarLayout已經幫我們做好了,我們只要標註相應的scrollflags即可,所以,如上的佈局,不做任何處理的話,作爲標題的TextView是一直顯示的,於是只要讓TextView跟隨Toolbar變化而變化就可以了。 接下來就創建一個Behavior類!
public class DrawerBehavior extends CoordinatorLayout.Behavior<TextView> {
private int mFrameMaxHeight = 100;
private int mStartY;
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
return dependency instanceof Toolbar;
}
public DrawerBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child,
View dependency) {
}
}
現在你應該可以很輕易的看懂這個Behavior的結構了。我們主要大展身手的地方其實是在onDependentViewChanged方法。
@Override public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child,
View dependency) {
//記錄開始的Y座標 也就是toolbar起始Y座標
if(mStartY == 0) {
mStartY = (int) dependency.getY();
}
//計算toolbar從開始移動到最後的百分比
float percent = dependency.getY()/mStartY;
//改變child的座標(從消失,到可見)
child.setY(child.getHeight()*(1-percent) - child.getHeight());
return true;
}
裏面監聽了Toolbar的Y座標變化,然後讓TextView的Y座標也跟着變化。達到如預覽圖效果。
難度3 頭像縮放效果
相信有了以上兩個例子,這個效果對你來說不難了,不就是讓imageView監聽Toolbar然後跟隨Toolbar的唯一變化而進行位移以及縮放麼。
所以具體的解析就不說了,直接上個Behavior代碼
/泛型爲child類型
public class CustomBehavior extends CoordinatorLayout.Behavior<CircleImageView> {
private Context mContext;
//頭像的最終大小
private float mCustomFinalHeight;
//最終頭像的Y
private float mFinalAvatarY;
private float mStartAvatarY;
private float mStartAvatarX;
private int mAvatarMaxHeight;
private BounceInterpolator interpolator = new BounceInterpolator();
public CustomBehavior(Context context, AttributeSet attrs) {
mContext = context;
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomBehavior);
//獲取縮小以後的大小
mCustomFinalHeight = a.getDimension(R.styleable.CustomBehavior_finalHeight, 0);
a.recycle();
}
}
// 如果dependency爲Toolbar
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) {
return dependency instanceof Toolbar;
}
//當dependency變化的時候調用
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {
//初始化屬性
//init(child, dependency);
mFinalAvatarY = dependency.getHeight()/2;
if(mStartAvatarY == 0){
mStartAvatarY = dependency.getY();
}
if(mStartAvatarX == 0){
mStartAvatarX = child.getX();
}
if(mAvatarMaxHeight == 0){
mAvatarMaxHeight = child.getHeight();
}
//child.setY(dependency.getY());
//讓ImageView跟隨toolbar垂直移動
child.setY(dependency.getY()+dependency.getHeight()/2-mCustomFinalHeight/2);
float percent = dependency.getY() / mStartAvatarY;
//float x = mStartAvatarX*(1+percent);
float x = mStartAvatarX * (1+ interpolator.getInterpolation(percent));
//Log.e("wing","started x "+ mStartAvatarX + " currentX "+ x);
//當toolbar 達到了位置,就不改變了。
if(dependency.getY() > dependency.getHeight()/2) {
child.setX(x);
}else {
child.setX(mStartAvatarX + ((mAvatarMaxHeight-mCustomFinalHeight))/2);
}
CoordinatorLayout.LayoutParams layoutParams =
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
layoutParams.height = (int) ((mAvatarMaxHeight-mCustomFinalHeight) * percent + mCustomFinalHeight);
layoutParams.width = (int) ((mAvatarMaxHeight-mCustomFinalHeight) * percent + mCustomFinalHeight);
child.setLayoutParams(layoutParams);
return true;
}
}
還是說說座標計算相關的吧。舉個例子。如何讓ImageView處於Toolbar中心呢,我的代碼如下
//讓ImageView跟隨toolbar垂直移動
child.setY(dependency.getY()+dependency.getHeight()/2-mCustomFinalHeight/2);
爲什麼是這樣? 上個圖就明白了
怎麼樣,不難吧,哈 喜歡的點個贊 給個star哦~~