CoordinatorLayout.Behavior

2018-07-27  本文已影响23人  有点健忘

额外知识

app:layout_anchor="@+id/btn_move"
app:layout_anchorGravity="top|right"

在CoordinatorLayout里的view可以添加如上的属性,根据第一个锚点anchor以及锚点重心来决定自己的位置
anchorGravity默认是在左上角的, 这里指的是当前view的中心位置在另外一个锚点view的什么地方。
如下图,就是textview的中心点在button的 top和右边,


image.png

Behavior的简单使用

首先需要在CoordinatorLayout使用,其次给其中的一个控件添加
app:layout_behavior="com.charliesong.demo0327.behavior.SimpleBehavior" 的参数,
参数就是我们写的behavior的完整类名
如下我们简单写了一个。

class SimpleBehavior:CoordinatorLayout.Behavior<TextView>{
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    override fun onDependentViewChanged(parent: CoordinatorLayout?, child: TextView, dependency: View): Boolean {
        child.translationX=dependency.translationX
        return true
    }
    override fun layoutDependsOn(parent: CoordinatorLayout?, child: TextView, dependency: View): Boolean {
        if(dependency.id== R.id.tv1){
            return true
        }
        return super.layoutDependsOn(parent, child, dependency)
    }
}

下边来说明下。
首先泛型一般写个View就行,就是限制这个behavior类可以给哪些控件用,另外也是方便不用进行类型转换,你泛型写个textview,那么下边所有child的参数都是textview了。
其实主要重写上边的2个方法基本就可以实现简单的功能了。

layoutDependsOn方法

第二个参数child,就是上边app:layout_behavior赋值的那个view,这个view必须是CoordinatorLayout的直接child。
最后一个参数,返回的就是CoordinatorLayout下的所有直接child view,【当然排除了第二个参数的child了】
如果我们的child布局和后边的dependency有关,那么就返回true,否则返回false。
以下是英文注释

   /**
         * Determine whether the supplied child view has another specific sibling view as a
         * layout dependency.
         *
         * <p>This method will be called at least once in response to a layout request. If it
         * returns true for a given child and dependency view pair, the parent CoordinatorLayout
         * will:</p>
         * <ol>
         *     <li>Always lay out this child after the dependent child is laid out, regardless
         *     of child order.</li>
         *     <li>Call {@link #onDependentViewChanged} when the dependency view's layout or
         *     position changes.</li>
         * </ol>
         *
         * @param parent the parent view of the given child
         * @param child the child view to test
         * @param dependency the proposed dependency of child
         * @return true if child's layout depends on the proposed dependency's layout,
         *         false otherwise
         *
         * @see #onDependentViewChanged(CoordinatorLayout, View, View)
         */
 public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
            return false;
        }

onDependentViewChanged

当denpendency的view发生变化的时候 会走这个方法,在这里我们来修改我们的child的布局。
如果我们在这里根据denpendency改变了child的大小,位置之类的,那么就返回true.
英文注释

        /**
         * Respond to a change in a child's dependent view
         *
         * <p>This method is called whenever a dependent view changes in size or position outside
         * of the standard layout flow. A Behavior may use this method to appropriately update
         * the child view in response.</p>
         *
         * <p>A view's dependency is determined by
         * {@link #layoutDependsOn(CoordinatorLayout, View, View)} or
         * if {@code child} has set another view as it's anchor.</p>
         *
         * <p>Note that if a Behavior changes the layout of a child via this method, it should
         * also be able to reconstruct the correct position in
         * {@link #onLayoutChild(CoordinatorLayout, View, int) onLayoutChild}.
         * <code>onDependentViewChanged</code> will not be called during normal layout since
         * the layout of each child view will always happen in dependency order.</p>
         *
         * <p>If the Behavior changes the child view's size or position, it should return true.
         * The default implementation returns false.</p>
         *
         * @param parent the parent view of the given child
         * @param child the child view to manipulate
         * @param dependency the dependent view that changed
         * @return true if the Behavior changed the child view's size or position, false otherwise
         */

其他时候可能还需要重写下边的方法来对控件进行布局,因为默认的都是从左上角开始的

    override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean {
     
        layoutChild(parent, child, layoutDirection)
        return true  //这里得返回true,否则上边你layout完发现没效果
    }

现在回想下当初看到过的系统的各种效果。
1~ FloatingActionButton
这玩意在底部的时候,如果我们弹一个snackbar出来,可以发现floatingactionbutton被顶上去了,可我们也没设置layoutbehavior啊。我们是没设置,可系统默认是有的,如下图

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

然后看下它都和哪些view有关联,如下图
AppBarLayout以及设置了BottomSheetBehavior的view。


image.png

然后我们看下snackbar的代码,如下图,可以看到也被添加了一个behavior


image.png

不过这个behavior好像不是我们期望的啊。貌似和floatactionbutton没啥关系

final class Behavior extends SwipeDismissBehavior<SnackbarBaseLayout>

public class SwipeDismissBehavior<V extends View> extends CoordinatorLayout.Behavior<V> 

appbarlayout的展开收缩简单方法

appbar.setExpanded(false,true)

BottomSheetBehavior

N久以前这玩意刚出来的时候学习了下,把以前弄的复制过来,老帖子删掉,整理下
1~原始的使用
第一种就是直接写在布局文件里,需要如图红圈所示的条件。父类需要是coorinatorLayout。自己需要设置app那3个属性。


image.png

behavior_peekHeight高度是默认的显示的高度。不设置的话,默认这个控件是不可见的。
而第二个属性hideable是和第一个关联的,用来设置多出来的部分是否可以隐藏,也就是滑动到屏幕底部消失。默认是不可以的


image.png
简单的使用方法如下:
BottomSheetBehavior behavior = BottomSheetBehavior.from(findViewById(R.id.scroll));

if(behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {

behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);

tv_top.setText("top");

}else{

behavior.setState(BottomSheetBehavior.STATE_EXPANDED);

tv_top.setText("bottom");

}

behavior.setBottomSheetCallback(newBottomSheetBehavior.BottomSheetCallback() {

@Override

public void onStateChanged(@NonNullView bottomSheet, intnewState) {

}

@Override

public void onSlide(@NonNullView bottomSheet, floatslideOffset) {

}

});

2~第二种,利用系统封装的Dialog
我们要做的就是把自己要底部弹出的view放进去即可

         BottomSheetDialog dialog =new BottomSheetDialog(this);

        dialog.setContentView(view);

        dialog.show();

//
public class BottomSheetDialog extends AppCompatDialog 

3~封装成fragmentdialog而已
就当普通的fragment使用,不过他自带底部弹出效果,而且可以往上展开而已

public class MyBottomSheetDialogFragment extends BottomSheetDialogFragment
public class BottomSheetDialogFragment extends AppCompatDialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new BottomSheetDialog(getContext(), getTheme());
    }

}

简单看下BottomSheetDialog的实现原理

    public void setContentView(View view) {
        super.setContentView(wrapInBottomSheet(0, view, null));
    }

  //对我们传进来的view进行了处理,添加进了系统自己的view里,并返回的是系统的view,系统view的布局下边给
    private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
        final FrameLayout container = (FrameLayout) View.inflate(getContext(),
                R.layout.design_bottom_sheet_dialog, null);
        final CoordinatorLayout coordinator =
                (CoordinatorLayout) container.findViewById(R.id.coordinator);
        if (layoutResId != 0 && view == null) {
            view = getLayoutInflater().inflate(layoutResId, coordinator, false);
        }
        FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
        mBehavior = BottomSheetBehavior.from(bottomSheet);
        mBehavior.setBottomSheetCallback(mBottomSheetCallback);
        mBehavior.setHideable(mCancelable);
        if (params == null) {
            bottomSheet.addView(view);
        } else {
            bottomSheet.addView(view, params);
        }
        // We treat the CoordinatorLayout as outside the dialog though it is technically inside
        coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) {
                    cancel();
                }
            }
        });

看下系统布局的代码【v27.1.1布局,每次更新可能代码布局都会被修改,所以这里注释下,也许你的和我的就不一样】

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    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:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coordinator"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <View
            android:id="@+id/touch_outside"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:importantForAccessibility="no"
            android:soundEffectsEnabled="false"
            tools:ignore="UnusedAttribute"/>

        <FrameLayout
            android:id="@+id/design_bottom_sheet"
            style="?attr/bottomSheetStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|top"
            app:layout_behavior="@string/bottom_sheet_behavior"/>

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

</FrameLayout>

实际效果

Fragment调用show方法以后,可以看到默认是显示一部分,也就是那个peek_height,这个东西如果不设置的话是有个默认值的

 if (mPeekHeightMin == 0) {
                mPeekHeightMin = parent.getResources().getDimensionPixelSize(
                        R.dimen.design_bottom_sheet_peek_height_min);//64dp好像
            }
            peekHeight = Math.max(mPeekHeightMin, mParentHeight - parent.getWidth() * 9 / 16);
      //parent的高度减去parent的宽度的9/16  和系统默认的那个值比较,取较大的那个

实际需求

老版本我记得是直接展开的,现在的版本咋默认是非展开的状态。
获取到behavior以后,在onstart里重写状态,必须在这里写。因为BottomSheetDialog在onstart里让他收缩了

    protected void onStart() {
        super.onStart();
        if (mBehavior != null) {
            mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }

看了上边的代码就知道我们要改这个状态,只能重写onstart的。

class BottomDialogFragment : BottomSheetDialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
        val v = LayoutInflater.from(activity).inflate(R.layout.activity_weather2, null)
        dialog.setContentView(v)
        behavior = BottomSheetBehavior.from(v.parent as View)
      //不想写这些代码也可以反射获取behavior。
        return dialog
    }

    lateinit var behavior: BottomSheetBehavior<View>
    override fun onStart() {
        super.onStart()
        if (behavior != null) {
            behavior.setState(BottomSheetBehavior.STATE_EXPANDED)
        }
    }
}
上一篇 下一篇

猜你喜欢

热点阅读