先來看看最終的效果~~
本文同步至博主的私人博客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);
為什么是這樣? 上個圖就明白了