zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    使用RPC的接口创建账户同时购买内存并为其抵押CPU和NET资源
    使用RPC的接口创建账户
    【移动安全基础篇】——21、Android脱壳思路
    插件
    NGUI 优化
    影子
    优化文章索引
    MVC
    《你不常用的c#之XX》
    CMake
  • 原文地址:https://www.cnblogs.com/muyuge/p/6333513.html
Copyright © 2011-2022 走看看