自定义behavior

  • content
    {:toc}

    介绍

其实Behavior就是一个应用于View的观察者模式,一个View跟随者另一个View的变化而变化,或者说一个View监听另一个View。

在Behavior中,被观察View 也就是事件源被称为dependency,而观察View,则被称为child。

两个简单的控件进行一起滑动

效果

ButtonFragment中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
dependBtn.setOnTouchListener(new View.OnTouchListener() {
private int lastX, lastY;

@Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 在当前left、top、right、bottom的基础上加上偏移量
v.layout(v.getLeft() + offsetX,
v.getTop() + offsetY,
v.getRight() + offsetX,
v.getBottom() + offsetY);

break;
}
return true;
}
});

buttonBehavior 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
return dependency instanceof Button;
}

@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
//这里就决定了child位置改变,从而,在coordinatorLayout中,布局会重新布局的原因
child.setX(dependency.getX() + dependency.getWidth() + 200);
child.setY(dependency.getY());
Log.e(TAG, "onDependentViewChanged: " + dependency.getX() + ", " + dependency.getY());
return true;
}

UC浏览器主页demo

uc浏览器demo

主要是这4个重要的类

主要的类

布局说明:

  • UCTitleView

    UCTitleView如果要在content滑动结束刚刚全部出来

    应该是:

    float ratio = UCTitleView的滑动距离 / content的滑动距离

    由于初始状态是从上面往下面走,所以是 负的
    直接用 -1 * offset 控件会保持不动
    所以:

    UCTitleView移动距离 = -(1 + ratio) * offset
    1
    2
    3
    4
    5
    6
    7
    8
    9
    UCHeaderView headerView = (UCHeaderView) viewGroup;
    int scrollRange = headerView.getScrollRange();
    float ratio = getMeasuredHeight() / (float) scrollRange;
    //负号是往下面移动
    //如果直接是-offset,控件是原地不动的
    //经过计算, 如果重上往下走 如果是控件全部出来这一种 移动一个控件的高度就好了
    //即: -(1 + (控件高度/recyclerView滑动总距离)) * offset
    //offset的max是 recyclerView滑动总距离
    mATViewOffsetHelper.setTopAndBottomOffset((int) (-(ratio + 1) * offset));
  • UCTabLayout

    UCTabLayout如果要滑动刚刚和和UCTitleView全部划出来的地方
    距离应该是:

    ​ 滑动高度 = UCCenterView的高度 - content的滑动距离 - UCTitleView的高度
    从而得出了倍率:

    ​ float ratio = 滑动高度/content的滑动距离

    所以:

    ​ UCTabLayout动态移动距离 = ratio * offset

    1
    2
    3
    4
    5
    6
    UCHeaderView headerView = (UCHeaderView) viewGroup;
    int scrollRange = headerView.getScrollRange();
    int height = headerView.getCenterViewHeight();
    int currentScroll = height - scrollRange;
    float ratio = (currentScroll - headerView.getTitleHeight()) / (float) scrollRange;
    mATViewOffsetHelper.setTopAndBottomOffset((int) (ratio * offset));
  • UCScrollingBehavior
    UCScrollingBehavior主要用来放在 RecyclerView(也可以是其他实现了NestedScrollChild的接口View) 上面的一个behavior,用于把 UCHeaderView和RecyclerView摆放位置确定的类

    部分代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
     /**
    * 一开始确定
    * UCHeaderView 和 recyclerView的摆放
    */
    @Override
    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
    UCHeaderView headerView = findFirstDependency(parent.getDependencies(child));
    if (headerView != null) {
    CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) headerView.getLayoutParams();
    CoordinatorLayout.Behavior behavior = layoutParams.getBehavior();
    if (behavior instanceof UCHeaderView.Behavior) {
    // UCHeaderView.Behavior headerBehavior = (UCHeaderView.Behavior) behavior;
    ViewCompat.offsetTopAndBottom(child, dependency.getBottom() - child.getTop() - (headerView).getTitleHeight() - (headerView).getBarRange());
    }
    }
    return super.onDependentViewChanged(parent, child, dependency);
    }

    /**
    * 限制 content 滑动范围
    */
    @Override
    protected int getScrollRange(View v) {
    if (v instanceof UCHeaderView) {
    return ((UCHeaderView) v).getScrollRange() + ((UCHeaderView) v).getTitleHeight() + ((UCHeaderView) v).getBarRange();
    }
    return super.getScrollRange(v);
    }
  • UCHeaderView

    UCHeaderView决定头部里面所有控件的摆放和移动, 通过里面的behavior进行确定偏移(offset)

    UCTitleView 摆放在手机屏幕上面的外面,UCTitleView的bottom在上面屏幕的边界上

    UCCenterView 正常的摆放以自己的高度的高度进行了摆放

    UCTabLayout 摆在了UCCenterView后面,只不过UCTabLayout会在摆放的RecyclerView的下面,所以在UCScrollingBehavior中onDependentViewChanged和getScrollRange方法进行了体现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   /**
* 每个child view 的布局
* 确定没个childview的位置
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childTop = getPaddingTop();
int childLeft = getPaddingLeft();
//titleView的摆放
mTitleView.layout(childLeft, childTop - mTitleView.getMeasuredHeight(), childLeft + mTitleView.getMeasuredWidth(), childTop);
// childTop += mTitleView.getMeasuredHeight();

mCenterView.layout(childLeft, childTop, childLeft + mCenterView.getMeasuredWidth(), childTop + mCenterView.getMeasuredHeight());
childTop += mCenterView.getMeasuredHeight();

mTab.layout(childLeft, childTop, childLeft + mTab.getMeasuredWidth(), childTop + mTab.getMeasuredHeight());
// childTop += mTab.getMeasuredHeight();

for (View sv : mScrollableViews) {
sv.layout(childLeft, childTop, childLeft + sv.getMeasuredWidth(), childTop + sv.getMeasuredHeight());
childTop += sv.getMeasuredHeight();
}
}

参考

CoordinatorLayout 自定义Behavior并不难,由简到难手把手带你飞

支付宝首页交互三部曲 3 实现支付宝首页交互