CoordinatorLayout 自定義Behavior並不難,由簡到難手把手帶你擼三款!

先來看看最終的效果~~

這裏寫圖片描述

本文同步至博主的私人博客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哦~~

項目地址 https://github.com/githubwing/CustomBehavior

發佈了113 篇原創文章 · 獲贊 589 · 訪問量 53萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章