自定义Behavior —— 仿知乎,FloatActionButton隐藏与展示(1)

Behavior简介


Behavior是CoordinatorLayout里面的一个内部类,通过它我们可以与 CoordinatorLayout的一个或者多个子View进行交互,包括 drag,swipes, flings等手势动作。

今天 我们主要着重介绍里面的几个方法

| 方法 | 解释 |

| — | — |

| boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) | 确定child View 是否有一个特定的兄弟View作为布局的依赖(即dependency) |

| boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) | 当child View 的 dependent view 发生变化的时候,这个方法会调用 |

| boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) | 当CoordinatorLayout 的直接或者非直接子View开始准备嵌套滑动的时候会调用 |

| void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) | 当嵌套滑动的 时候,target尝试滑动或者正在滑动的 时候会调用 |

关于更多方法,请参考官网文档说明

怎样自定义Behavior


前面已经说到,今天主要介绍四个方法,这里我们把它分为两组。

第一组

// 决定child 依赖于把一个 dependency

boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)

// 当 dependency View 改变的时候 child 要做出怎样的响应

boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency)

第二组

// 当CoordinatorLayout的直接或者非直接子View开始嵌套滑动的时候,会调用这个方法

boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)

// 当嵌套滑动的时候,target 尝试滑动或者正在滑动会调用这个方法

onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)

首先我们先看第一组是怎样实现的?

/**

  • 知乎效果底部behavior 依赖于 AppBarLayout

  • @author xujun on 2016/11/30.

  • @email gdutxiaoxu@163.com

*/

public class FooterBehaviorDependAppBar extends CoordinatorLayout.Behavior {

public static final String TAG = “xujun”;

public FooterBehaviorDependAppBar(Context context, AttributeSet attrs) {

super(context, attrs);

}

//当 dependency instanceof AppBarLayout 返回TRUE,将会调用onDependentViewChanged()方法

@Override

public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {

return dependency instanceof AppBarLayout;

}

@Override

public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {

//根据dependency top值的变化改变 child 的 translationY

float translationY = Math.abs(dependency.getTop());

child.setTranslationY(translationY);

Log.i(TAG, "onDependentViewChanged: " + translationY);

return true;

}

}

思路分析

这里我们要分清两个概念,child 和 dependency ,child 是我们要改变的坐标的view,而 dependency 是child 的 附属 ,即child 会随着 dependency 坐标的改变而改变。

比如上面的例子:当我们把 app:layout_behavior=“com.xujun.contralayout.behavior.FooterBehaviorDependAppBar” 设置给 RadioGroup 的时候,这时候 child 就是 RadioGroup ,而 dependency 就是 APPBarLayout ,因为我们在 layoutDependsOn 方法里面 ,返回 dependency instanceof AppBarLayout ,即当 dependency 是 AppBarLayout 或者 AppBarLayout的子类的时候返回TRUE。

//当 dependency instanceof AppBarLayout 返回TRUE,将会调用onDependentViewChanged()方法

@Override

public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {

return dependency instanceof AppBarLayout;

}

而之所以 RadioGroup 在向上滑动的时候会隐藏,向下滑动的时候会显示,是因为我们在 onDependentViewChanged 方法的时候 动态地根据 dependency 的 top 值改变 RadioGroup 的 translationY 值,核心 代码如下

@Override

public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {

//根据dependency top值的变化改变 child 的 translationY

float translationY = Math.abs(dependency.getTop());

child.setTranslationY(translationY);

Log.i(TAG, "onDependentViewChanged: " + translationY);

return true;

}

到此第一种思路分析为止

第二种思路

主要是根据 onStartNestedScroll() 和 onNestedPreScroll()方法 来实现的,

  • 当我们开始滑动的时候,我们判断是否是垂直滑动,如果是返回TRUE,否则返回 FALSE,返回TRUE,会接着调用onNestedPreScroll()等一系列方法。

//1.判断滑动的方向 我们需要垂直滑动

@Override

public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,

View directTargetChild, View target, int nestedScrollAxes) {

return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;

}

  • 在 onNestedPreScroll() 方法里面,我们根据我们的逻辑来决定是否显示 target , 在这里我们是向上上滑动的时候,如果我们滑动的距离超过 target 的高度 并且 当前是可见的状态下,我们执行动画,隐藏 target,当我们向下滑动的时候,并且 View 是不可见的情况下,我们执行动画 ,显示target

//2.根据滑动的距离显示和隐藏footer view

@Override

public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child,

View target, int dx, int dy, int[] consumed) {

if (dy > 0 && sinceDirectionChange < 0 || dy < 0 && sinceDirectionChange > 0) {

child.animate().cancel();

sinceDirectionChange = 0;

}

sinceDirectionChange += dy;

int visibility = child.getVisibility();

if (sinceDirectionChange > child.getHeight() && visibility == View.VISIBLE) {

hide(child);

} else {

if (sinceDirectionChange < 0 && (visibility == View.GONE || visibility == View

.INVISIBLE)) {

show(child);

}

}

}

全部代码如下

/**

  • 知乎效果底部 behavior

  • @author xujun on 2016/11/30.

  • @email gdutxiaoxu@163.com

*/

public class FooterBehavior extends CoordinatorLayout.Behavior {

private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();

private int sinceDirectionChange;

public FooterBehavior(Context context, AttributeSet attrs) {

super(context, attrs);

}

//1.判断滑动的方向 我们需要垂直滑动

@Override

public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,

View directTargetChild, View target, int nestedScrollAxes) {

return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;

}

//2.根据滑动的距离显示和隐藏footer view

@Override

public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child,

View target, int dx, int dy, int[] consumed) {

if (dy > 0 && sinceDirectionChange < 0 || dy < 0 && sinceDirectionChange > 0) {

child.animate().cancel();

sinceDirectionChange = 0;

}

sinceDirectionChange += dy;

int visibility = child.getVisibility();

if (sinceDirectionChange > child.getHeight() && visibility == View.VISIBLE) {

hide(child);

} else {

if (sinceDirectionChange < 0 && (visibility == View.GONE || visibility == View

.INVISIBLE)) {

show(child);

}

}

}

private void hide(final View view) {

ViewPropertyAnimator animator = view.animate().translationY(view.getHeight()).

setInterpolator(INTERPOLATOR).setDuration(200);

animator.setListener(new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animator) {

}

@Override

public void onAnimationEnd(Animator animator) {

view.setVisibility(View.GONE);

}

@Override

public void onAnimationCancel(Animator animator) {

show(view);

}

@Override

public void onAnimationRepeat(Animator animator) {

}

});

animator.start();

}

private void show(final View view) {

ViewPropertyAnimator animator = view.animate().translationY(0).

setInterpolator(INTERPOLATOR).

setDuration(200);

animator.setListener(new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animator) {

}

@Override

public void onAnimationEnd(Animator animator) {

view.setVisibility(View.VISIBLE);

}

@Override

public void onAnimationCancel(Animator animator) {

hide(view);

}

@Override

public void onAnimationRepeat(Animator animator) {

}

});

animator.start();

}

}


两种实现方法的对比和总结


  • 我们知道第一种方法我们主要是重写layoutDependsOn 和 onDependentViewChanged 这两个方法,这个方法在 layoutDependsOn 判断 dependency 是否是 APpBarLayout 的实现类,所以 会导致 child 依赖于 AppBarLayout,灵活性不是太强

  • 而第二种方法,我们主要是重写 onStartNestedScroll 和 onNestedPreScroll 这两个方法,判断是否是垂直滑动,是的话就进行处理,灵活性大大增强,推荐使用这一种方法

  • 需要注意的是不管是第一种方法,还是第二种方法,我们都需要重写带两个构造方法的函数,因为底层机制会采用反射的形式获得该对象

public FooterBehavior(Context context, AttributeSet attrs) {

super(context, attrs);

}


自定义 Behavior 实现 FloatingActionButton 的显示与隐藏


效果图如下

缩放隐藏的

向上向下隐藏的

布局代码

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.CoordinatorLayout

android:id=“@+id/activity_floating_action_button”

xmlns:android=“http://schemas.android.com/apk/res/android”

xmlns:tools=“http://schemas.android.com/tools”

android:layout_width=“match_parent”

android:layout_height=“match_parent”

xmlns:app=“http://schemas.android.com/apk/res-auto”

tools:context=“com.xujun.contralayout.UI.FloatingActionButtonActivity”>

<android.support.design.widget.AppBarLayout

android:id=“@+id/index_app_bar”

theme=“@style/AppTheme.AppBarOverlay”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”>

<RelativeLayout

android:layout_width=“match_parent”

android:layout_height=“?attr/actionBarSize”

android:background=“@color/colorPrimary”

app:layout_scrollFlags=“scroll|enterAlways”>

<ImageView

android:id=“@+id/search”

android:layout_width=“24dp”

android:layout_height=“24dp”

android:layout_centerVertical=“true”

android:layout_marginLeft=“10dp”

android:src=“@drawable/search”/>

<TextView

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:layout_centerVertical=“true”

android:layout_marginLeft=“10dp”

android:layout_toRightOf=“@id/search”

android:text=“搜索话题、问题或人”

android:textSize=“16sp”/>

</android.support.design.widget.AppBarLayout>

<android.support.v7.widget.RecyclerView

android:id=“@+id/recyclerView”

android:layout_width=“match_parent”

android:layout_height=“match_parent”

app:layout_behavior=“@string/appbar_scrolling_view_behavior”>

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

<android.support.design.widget.FloatingActionButton

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“bottom|right|end”

android:layout_marginBottom=“40dp”

android:layout_marginRight=“25dp”

android:background=“@android:color/holo_green_light”

android:src=“@drawable/add”

app:layout_behavior=“@string/behavior_my_fab_scale”/>

</android.support.design.widget.CoordinatorLayout>

如果想使用不同的效果,只需要给 FloatingActionButton 制定不同的 bevaior 即可

app:layout_behavior=“com.xujun.contralayout.behavior.MyFabBehavior”

自定义behavior 代码

/**

  • FloatingActionButton behavior 向上向下隐藏的

  • @author xujun on 2016/12/1.

  • @email gdutxiaoxu@163.com

*/

public class MyFabBehavior extends CoordinatorLayout.Behavior {

private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();

private float viewY;//控件距离coordinatorLayout底部距离

private boolean isAnimate;//动画是否在进行

public MyFabBehavior(Context context, AttributeSet attrs) {

super(context, attrs);

}

//在嵌套滑动开始前回调

@Override

public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {

if(child.getVisibility() == View.VISIBLE&&viewY==0){

//获取控件距离父布局(coordinatorLayout)底部距离

viewY=coordinatorLayout.getHeight()-child.getY();

}

return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;//判断是否竖直滚动

}

//在嵌套滑动进行时,对象消费滚动距离前回调

@Override

public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {

//dy大于0是向上滚动 小于0是向下滚动

if (dy >=0&&!isAnimate&&child.getVisibility()==View.VISIBLE) {

hide(child);

} else if (dy <0&&!isAnimate&&child.getVisibility()==View.GONE) {

show(child);

}

}

//隐藏时的动画

private void hide(final View view) {

ViewPropertyAnimator animator = view.animate().translationY(viewY).setInterpolator(INTERPOLATOR).setDuration(200);

animator.setListener(new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animator) {

isAnimate=true;

}

@Override

public void onAnimationEnd(Animator animator) {

view.setVisibility(View.GONE);

isAnimate=false;

}

@Override

public void onAnimationCancel(Animator animator) {

show(view);

}

@Override

public void onAnimationRepeat(Animator animator) {

}

});

animator.start();

}

//显示时的动画

private void show(final View view) {

ViewPropertyAnimator animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(200);

animator.setListener(new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animator) {

view.setVisibility(View.VISIBLE);

isAnimate=true;

}

@Override

public void onAnimationEnd(Animator animator) {

isAnimate=false;

}

@Override

public void onAnimationCancel(Animator animator) {

hide(view);

}

@Override

public void onAnimationRepeat(Animator animator) {

}

});

animator.start();

}

}

/**

  • 下拉时显示FAB,上拉隐藏,留出更多位置给用户。

  • Created on 2016/12/1.

  • @author xujun

*/

public class ScaleDownShowBehavior extends FloatingActionButton.Behavior {

/**

  • 退出动画是否正在执行。

*/

private boolean isAnimatingOut = false;

private OnStateChangedListener mOnStateChangedListener;

public ScaleDownShowBehavior(Context context, AttributeSet attrs) {

super();

}

@Override

public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {

return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;

}

@Override

public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

if ((dyConsumed > 0 || dyUnconsumed > 0) && !isAnimatingOut && child.getVisibility() == View.VISIBLE) {//往下滑

AnimatorUtil.scaleHide(child, viewPropertyAnimatorListener);

if (mOnStateChangedListener != null) {

mOnStateChangedListener.onChanged(false);

}

} else if ((dyConsumed < 0 || dyUnconsumed < 0) && child.getVisibility() != View.VISIBLE) {

AnimatorUtil.scaleShow(child, null);

if (mOnStateChangedListener != null) {

mOnStateChangedListener.onChanged(true);

}

}

}

public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener) {

this.mOnStateChangedListener = mOnStateChangedListener;

}

// 外部监听显示和隐藏。

public interface OnStateChangedListener {

void onChanged(boolean isShow);

}

public static ScaleDownShowBehavior from(V view) {

ViewGroup.LayoutParams params = view.getLayoutParams();

if (!(params instanceof CoordinatorLayout.LayoutParams)) {

总结

这次面试问的还是还是有难度的,要求当场写代码并且运行,也是很考察面试者写代码
因为Android知识体系比较庞大和复杂的,涉及到计算机知识领域的方方面面。在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
ateChangedListener != null) {

mOnStateChangedListener.onChanged(true);

}

}

}

public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener) {

this.mOnStateChangedListener = mOnStateChangedListener;

}

// 外部监听显示和隐藏。

public interface OnStateChangedListener {

void onChanged(boolean isShow);

}

public static ScaleDownShowBehavior from(V view) {

ViewGroup.LayoutParams params = view.getLayoutParams();

if (!(params instanceof CoordinatorLayout.LayoutParams)) {

总结

这次面试问的还是还是有难度的,要求当场写代码并且运行,也是很考察面试者写代码
因为Android知识体系比较庞大和复杂的,涉及到计算机知识领域的方方面面。在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-AnGznSk7-1715363550933)]
里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值