当前位置: 首页 > news >正文

Android 自定义Behavior

一、Behavior的介绍

1、什么是Behavior

上篇文章我们讲到CoordinatorLayout 配合AppBarLayoutCollapsingToolbarLayout实现了Toolbar的隐藏和折叠,但他们之间能够进行交互,其实就是通过一个介质CoordinatorLayout.Behavior 实现的。BehaviorCoordinatorLayout用来和各个子View通信用的代理类,用来协调CoordinatorLayout的Child Views之间的交互行为,但使用的前提是Behavior只有是CoordinatorLayout的直接子View才有意义。

2、引用Behavior的两种方式

  • app:layout_behavior布局属性
    在布局中设置,值为自定义Behavior类的名字字符串(包含路径)
  • @CoordinatorLayout.DefaultBehavior类注解
    在需要使用Behavior的控件源码定义中添加该注解,然后通过反射机制获取

二、Behavior 的两种机制

1、Dependent 机制

1.1 Dependent 介绍

这种机制主要是用来描述 两个Child View 之间的绑定依赖关系,设置Behavior 属性的Child View 跟随依赖对象Dependency View 的大小位置改变而发生改变,因此它用于一个View 监听另一个View 的状态变化。我们需要通过两个方法,来实现绑定:

    /**
     * 确定要依赖的对象
     */
     @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return super.layoutDependsOn(parent, child, dependency);
    }

    /**
     * 当依赖的对象发生变化时会自动回调这个方法
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        return super.onDependentViewChanged(parent, child, dependency);
    }

1.2、举例说明 Dependent 机制


可以看到,我对红色方块 进行拖动的时候,绿色的方块也发生了位置变化。
先自定义一个DependentBehavior,代码如下:

public class DependentBehavior extends CoordinatorLayout.Behavior<View> {
    /**
     * 构造方法
     *
     * @param context
     * @param attrs
     */
    public DependentBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 确定要依赖的对象
     *
     * @param parent
     * @param child
     * @param dependency
     * @return
     */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency.getId() == R.id.btn_first;
    }

    /**
     * 当依赖的对象发生变化时会自动回调这个方法
     *
     * @param parent
     * @param child
     * @param dependency
     * @return
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        // 根据要依赖的x坐标,更改当前控件的X坐标
        child.setX(parent.getWidth() - dependency.getX() - dependency.getWidth());
        child.setY(dependency.getY());
        return super.onDependentViewChanged(parent, child, dependency);
    }
}

activity_dependent.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"
    android:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn_first"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="100dp"
        android:background="@android:color/holo_red_light" />

    <Button
        android:id="@+id/btn_two"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="right"
        android:layout_marginRight="20dp"
        android:layout_marginTop="100dp"
        android:background="@android:color/holo_green_light"
        app:layout_behavior=".DependentBehavior" />
</android.support.design.widget.CoordinatorLayout>

主界面代码:

public class DependentActivity extends AppCompatActivity implements View.OnTouchListener {

    private Button mbtnFirst;
    private int lastX, lastY;    //保存手指点下的点的坐标
    private int mWidth, mHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dependent);
        mbtnFirst = (Button) findViewById(R.id.btn_first);
        //设置屏幕触摸事件
        mbtnFirst.setOnTouchListener(this);
    }

    /**
     * 当activity 的焦点发生改变时(View 已经绘制完成,可以获得宽高)
     *
     * @param hasFocus
     */
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        mWidth = mbtnFirst.getWidth();
        mHeight = mbtnFirst.getHeight();
    }

    /**
     * 触屏事件处理
     *
     * @param view
     * @param event
     * @return
     */
    @Override
    public boolean onTouch(View view, MotionEvent event) {
        // 处理多点触摸的ACTION_POINTER_DOWN和ACTION_POINTER_UP事件
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                //将点下的点的坐标保存
                lastX = (int) event.getRawX();
                lastY = (int) event.getRawY();
                break;

            case MotionEvent.ACTION_MOVE:
                //计算出需要移动的距离
                int dx = (int) event.getRawX() - lastX;
                int dy = (int) event.getRawY() - lastY;
                //将移动距离加上,现在本身距离边框的位置
                int left = view.getLeft() + dx;
                int top = view.getTop() + dy;
                //获取到layoutParams然后改变属性,在设置回去
                CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) view
                        .getLayoutParams();
                layoutParams.height = mHeight;
                layoutParams.width = mWidth;
                layoutParams.leftMargin = left;
                layoutParams.topMargin = top;
                view.setLayoutParams(layoutParams);

                //记录最后一次移动的位置
                lastX = (int) event.getRawX();
                lastY = (int) event.getRawY();
                break;
        }
        return true;
    }
}

2、Nested机制

2.1 Nested 介绍

Nested机制要求CoordinatorLayout包含了一个实现了NestedScrollingChild接口的滚动视图控件,比如v7包中的RecyclerView,设置Behavior属性的Child View会随着这个控件的滚动而发生变化,涉及到的方法有:

onStartNestedScroll(View child, View target, int nestedScrollAxes)
onNestedPreScroll(View target, int dx, int dy, int[] consumed)
onNestedPreFling(View target, float velocityX, float velocityY)
onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
onNestedFling(View target, float velocityX, float velocityY, boolean consumed)
onStopNestedScroll(View target)

下面我将简单描述下四个方法:

    /**
     *  会遍历每一个 子View,询问它们是否对滚动列表的滚动事件感兴趣,若 Behavior.onStartNestedScroll 方法返回 true,
     *  则表示感兴趣,那么滚动列表后续的滚动事件都会分发到该 子View的Behavior
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int axes, int type) {
        return true;
    }

    /**
     *  处理 子View 的滚动事件
     */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed, int type) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
    }

    /**
     * 检测滚动距离的最终消费情况,可以继续处理 滚动事件
     */
    @Override
    public void onNestedScroll( CoordinatorLayout coordinatorLayout,  View child,  View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
    }

    /**
     * 检测到滚动事件的结束
     */
    @Override
    public void onStopNestedScroll( CoordinatorLayout coordinatorLayout,  View child,  View target, int type) {
        super.onStopNestedScroll(coordinatorLayout, child, target, type);
    }

2.2、举例说明 Nested 机制


可以看到,当下拉列表时,红色布局就会消失,向上滑动时,红色布局就会出现。
先创建一个NestedBehavior,代码如下:

public class NestedBehavior extends CoordinatorLayout.Behavior<View> {

    int offsetTotal = 0;

    public NestedBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     *  会遍历每一个 子View,询问它们是否对滚动列表的滚动事件感兴趣,若 Behavior.onStartNestedScroll 方法返回 true,
     *  则表示感兴趣,那么滚动列表后续的滚动事件都会分发到该 子View的Behavior
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int axes, int type) {
        return true;
    }

    /**
     *  处理 子View 的滚动事件
     */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed, int type) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
        offset(child, dy);
    }

    public void offset(View child, int dy) {
        // 上次保存的位置
        int old = offsetTotal;
        // 当前的位置
        int curr = offsetTotal - dy;
        // 保证子控件的位置一直在 0-控件高度之间
        curr = Math.max(curr, -child.getHeight());
        curr = Math.min(curr, 0);
        offsetTotal = curr;
        if (old == offsetTotal) {
            return;
        }
        // 原来的位置 - 当前的位置 = 要移动的位置
        int delta = old - offsetTotal;
        child.offsetTopAndBottom(delta);
    }

}

activity_nested.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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </android.support.v7.widget.RecyclerView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_gravity="bottom"
        android:background="@android:color/holo_red_light"
        android:gravity="center"
        app:layout_behavior=".NestedBehavior">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="自定义Behavior"
            android:textColor="@android:color/white"
            android:textSize="20sp" />
    </LinearLayout>
</android.support.design.widget.CoordinatorLayout>

结尾:以上就是对自定义Behavior 的简单的介绍

最后附上源码的地址:https://github.com/smile-sxl/CoordinatorLayout

转载于:https://www.cnblogs.com/javasxl/p/9397775.html

相关文章:

  • maven 配置环境变量
  • 使用本地缓存快还是使用redis缓存好?
  • POJ 1298 - The Hardest Problem Ever(模拟)
  • [Window编程][VC6.0++][error LNK2001: unresolved external symbol __imp__PlaySoundA@12]
  • 【安富莱专题教程第4期】SEGGER的J-Scope波形上位机软件,HSS模式简单易用,无需额外资源,也不需要写目标板代码...
  • 手动搭建OpenStack(Ocata版)
  • Vue - 动态组件 异步组件
  • 【查找网站后台的方法】
  • 构建之法第四章两人合作
  • C++虚函数表解析***
  • 缓存一致
  • 七牛云上传图片
  • s22day6笔记
  • BZOJ5093 [Lydsy1711月赛]图的价值 【第二类斯特林数 + NTT】
  • 【大数据Spark_SparkSQL系列_1】Spark SQL基础(五星重要)
  • 【剑指offer】让抽象问题具体化
  • 2017 前端面试准备 - 收藏集 - 掘金
  •  D - 粉碎叛乱F - 其他起义
  • Octave 入门
  • Promise面试题2实现异步串行执行
  • QQ浏览器x5内核的兼容性问题
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • 基于web的全景—— Pannellum小试
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 设计模式 开闭原则
  • 深度解析利用ES6进行Promise封装总结
  • 思否第一天
  • 一个完整Java Web项目背后的密码
  • 移动端 h5开发相关内容总结(三)
  • Linux权限管理(week1_day5)--技术流ken
  • Mac 上flink的安装与启动
  • ​Linux·i2c驱动架构​
  • #define、const、typedef的差别
  • #define与typedef区别
  • $NOIp2018$劝退记
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (补)B+树一些思想
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (小白学Java)Java简介和基本配置
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (一)WLAN定义和基本架构转
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转)详解PHP处理密码的几种方式
  • .htaccess配置重写url引擎
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置
  • .NET 反射 Reflect
  • ??在JSP中,java和JavaScript如何交互?
  • @Autowired 与@Resource的区别
  • @autowired注解作用_Spring Boot进阶教程——注解大全(建议收藏!)
  • [ C++ ] STL_stack(栈)queue(队列)使用及其重要接口模拟实现
  • [30期] 我的学习方法
  • [AIGC] SQL中的数据添加和操作:数据类型介绍