额外知识
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)
}
}
}
网友评论