美文网首页
Android CoordinatorLayout的Behavi

Android CoordinatorLayout的Behavi

作者: 水言 | 来源:发表于2017-07-21 10:18 被阅读157次

CoordinatorLayout
官方解释是:它是一个超级的FrameLayout,CoordinatorLayout的两个主要的用例:
作为一个顶级应用布局容器;
作为一个能够与一个或多个子视图交互的特定容器。
CoordinatorLayout.Behavior是个内部类,通过指定的Behavior可以完成内部的子控件的相互交互。Behavior是CoordinatorLayout用来和各个子View通信用的代理类。

CoordinatorLayout.Behavior类介绍

CoordinatorLayout.Behavior
官网对于 CoordinatorLayout.Behavior 的介绍已经将它的作用说明得很清楚了,就是用来协调 CoordinatorLayout 的Child Views之间的交互行为

1.使用自定义的Behavior

​第一种方式:我们自定义的可以通过在布局中使用app:layout_behavior的来指定,
例如,我们在NestedScrollView设定的Android提供的Behavior,

app:layout_behavior="@string/appbar_scrolling_view_behavior">,

其中的string是Behavior的绝对路径。

第二种方式:如果你要用到自定义View/ViewGroup中,可以通过@CoordinatorLayout.DefaultBehavior()注解指定Behavior;这样我们的View就会默认使用这个Behavior。比如在AppBarLayout的使用方式:

@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
    //省略具体实现代码
}

第三种方式:通过代码设定Behavior,前提是你的指定的View必须是CoordinatorLayout的子View,如下:

MyBehavior myBehavior = new MyBehavior();
//我们的View必须是CoordinatorLayout的子View,否则我们获取不到CoordinatorLayout.LayoutParams
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) 我们的View.getLayoutParams();
params.setBehavior(myBehavior);

2.系统源码FloatingActionButton.Behavior

@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
public class FloatingActionButton extends VisibilityAwareImageButton 

这是design中的FloatingActionButton控件,他有一个自己的Behavior,使用上面说的方式二设置behavior,它有两个特点:
1.被固定在AppBarLayout中的时候伴随他的隐藏消失。

//FloatingActionButton和AppBarLayout建立固定关系
   app:layout_anchor="@+id/a_design_appbarlayout"
特点1

2.依赖于SnakerBar的时候,当SnakerBar弹出消失的时候它要改变自己的位置。

特点二
源码如下:
 public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
        private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
        private ValueAnimatorCompat mFabTranslationYAnimator;
        private float mFabTranslationY;
        private Rect mTmpRect;

        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent,
                FloatingActionButton child, View dependency) {
            // 当API>11时候它依赖所有的Snackbar
            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
        }

        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
                View dependency) {
            if (dependency instanceof Snackbar.SnackbarLayout) {
               //当Snakerbar改变的时候更新位置
                updateFabTranslationForSnackbar(parent, child, dependency);
            } else if (dependency instanceof AppBarLayout) {
              //当和AppBarLayout建立固定关系的时候
                updateFabVisibility(parent, (AppBarLayout) dependency, child);
            }
            return false;
        }
        //随AppBarLayout更新FloatingActionButton 的状态
        private boolean updateFabVisibility(CoordinatorLayout parent,
                AppBarLayout appBarLayout, FloatingActionButton child) {
            final CoordinatorLayout.LayoutParams lp =
                    (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            if (lp.getAnchorId() != appBarLayout.getId()) {
                // The anchor ID doesn't match the dependency, so we won't automatically
                // show/hide the FAB
                return false;
            }
            if (child.getUserSetVisibility() != VISIBLE) {
                // The view isn't set to be visible so skip changing it's visibility
                return false;
            }
            if (mTmpRect == null) {
                mTmpRect = new Rect();
            }
            // First, let's get the visible rect of the dependency
            final Rect rect = mTmpRect;
            ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect);
            if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
                // If the anchor's bottom is below the seam, we'll animate our FAB out
                child.hide(null, false);
            } else {
                // Else, we'll animate our FAB back in
                child.show(null, false);
            }
            return true;
        }
       //随Snakerbar更新状态
        private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
                final FloatingActionButton fab, View snackbar) {
            final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
            if (mFabTranslationY == targetTransY) {
                // We're already at (or currently animating to) the target value, return...
                return;
            }
            final float currentTransY = ViewCompat.getTranslationY(fab);
            // Make sure that any current animation is cancelled
            if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) {
                mFabTranslationYAnimator.cancel();
            }
            if (fab.isShown()
                    && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
                // If the FAB will be travelling by more than 2/3 of it's height, let's animate
                // it instead
                if (mFabTranslationYAnimator == null) {
                    mFabTranslationYAnimator = ViewUtils.createAnimator();
                    mFabTranslationYAnimator.setInterpolator(
                            AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
                    mFabTranslationYAnimator.setUpdateListener(
                            new ValueAnimatorCompat.AnimatorUpdateListener() {
                                @Override
                                public void onAnimationUpdate(ValueAnimatorCompat animator) {
                                    ViewCompat.setTranslationY(fab,
                                            animator.getAnimatedFloatValue());
                                }
                            });
                }
                mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY);
                mFabTranslationYAnimator.start();
            } else {
                // Now update the translation Y
                ViewCompat.setTranslationY(fab, targetTransY);
            }
            mFabTranslationY = targetTransY;
        }

        private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
                FloatingActionButton fab) {
            float minOffset = 0;
            final List<View> dependencies = parent.getDependencies(fab);
            for (int i = 0, z = dependencies.size(); i < z; i++) {
                final View view = dependencies.get(i);
                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
                    minOffset = Math.min(minOffset,
                            ViewCompat.getTranslationY(view) - view.getHeight());
                }
            }
            return minOffset;
        }

        @Override
        public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
                int layoutDirection) {
            // First, lets make sure that the visibility of the FAB is consistent
            final List<View> dependencies = parent.getDependencies(child);
            for (int i = 0, count = dependencies.size(); i < count; i++) {
                final View dependency = dependencies.get(i);
                if (dependency instanceof AppBarLayout
                        && updateFabVisibility(parent, (AppBarLayout) dependency, child)) {
                    break;
                }
            }
            // Now let the CoordinatorLayout lay out the FAB
            parent.onLayoutChild(child, layoutDirection);
            // Now offset it if needed
            offsetIfNeeded(parent, child);
            return true;
        }
        private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) {
            final Rect padding = fab.mShadowPadding;

            if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) {
                final CoordinatorLayout.LayoutParams lp =
                        (CoordinatorLayout.LayoutParams) fab.getLayoutParams();

                int offsetTB = 0, offsetLR = 0;

                if (fab.getRight() >= parent.getWidth() - lp.rightMargin) {
                    // If we're on the left edge, shift it the right
                    offsetLR = padding.right;
                } else if (fab.getLeft() <= lp.leftMargin) {
                    // If we're on the left edge, shift it the left
                    offsetLR = -padding.left;
                }
                if (fab.getBottom() >= parent.getBottom() - lp.bottomMargin) {
                    // If we're on the bottom edge, shift it down
                    offsetTB = padding.bottom;
                } else if (fab.getTop() <= lp.topMargin) {
                    // If we're on the top edge, shift it up
                    offsetTB = -padding.top;
                }
                fab.offsetTopAndBottom(offsetTB);
                fab.offsetLeftAndRight(offsetLR);
            }
        }
    }

3.自定义Behavior

1.依赖机制
这种机制描述的是两个Child Views之间的绑定依赖关系,主要依赖:

layoutDependsOn()
onLayoutDependencyChanged()

这两个方法,第一个很显然是告诉CoordinatorLayout,一个View是否依赖于另一个View。
第二个是CoordinatorLayout发现存在依赖的时候,把被依赖方回调给依赖方,因为这时候,layout已经完成,我们可以获取被依赖方的所有布局信息,根据布局信息,使用offset来决定依赖方的一些位置;同时在这个时候,你也可以调用requestLayout进行重新布局。
上面说到的FloatingActionButton.Behavior就属于这一种。

    /**
     * 告诉CoordinatorLayout我要依赖那个view,这里我明确要依赖first
     */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency.getId() == R.id.first;
    }
    /**
     *当我们监听的view有任何变化都会回调这个方法。我们在这里面改变监听者的位置
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        child.setTranslationY(dependency.getTranslationY()+dependency.getHeight());
        return true;
    }

例子:

public class MyBottomBarBehavior extends CoordinatorLayout.Behavior<View> {
    public MyBottomBarBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        //这个方法是说明这个子控件是依赖AppBarLayout的
        return dependency instanceof AppBarLayout;
    }
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        float translationY = Math.abs(dependency.getTop());//获取更随布局的顶部位置
        child.setTranslationY(translationY);
        return true;
    }
}

2.监听滑动

监听滑动

CoordinatorLayout是实现了NestedScrollingParent接口的,也就是说,要用到这个特性的话,默认不实现NestedScrollingChild接口 且不调用dispatchNestedScroll相关接口的View就没有这些效果了。
主要是这3个方法,将依赖对象的滑动事件都将通知进来:

@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
    return true;//这里返回true,才会接受到后续滑动事件。
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//进行滑动事件处理
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
//当进行快速滑动
    return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}

项目下载地址:链接:http://pan.baidu.com/s/1pLt2YvD 密码:r095

完整的例子:https://github.com/zhaochenpu/CoordinatorLayoutDemo

参考:
https://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.html
http://www.tuicool.com/articles/IZnIBbf
http://www.tuicool.com/articles/Uj6Nvqj
https://github.com/zhaochenpu/CoordinatorLayoutDemo

相关文章

网友评论

      本文标题:Android CoordinatorLayout的Behavi

      本文链接:https://www.haomeiwen.com/subject/gycwzttx.html