BottomSheetBehavior
来源:互联网 发布:开淘宝企业店需要什么 编辑:程序博客网 时间:2024/05/23 00:00
BottomSheetBehavior
标签(空格分隔): android
BottomSheetBehavior支持如下属性
<declare-styleable name="BottomSheetBehavior_Layout"><attr format="dimension" name="behavior_peekHeight"> <enum name="auto" value="-1"/></attr><attr format="boolean" name="behavior_hideable"/><attr format="boolean" name="behavior_skipCollapsed"/></declare-styleable>
含义
//折叠的高度app:behavior_peekHeight="10dp" setPeekHeight//是否可以隐藏app:behavior_hideable="true" setHideable//是否跳过折叠状态app:behavior_skipCollapsed="true" setSkipCollapsed
首先加载BottomSheetBehavior,根据属性配置,设置能否隐藏,折叠高度,是否跳过折叠状态
public static <V extends View> BottomSheetBehavior<V> from(V view) { ViewGroup.LayoutParams params = view.getLayoutParams(); if (!(params instanceof CoordinatorLayout.LayoutParams)) { throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); } CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params) .getBehavior(); if (!(behavior instanceof BottomSheetBehavior)) { throw new IllegalArgumentException( "The view is not associated with BottomSheetBehavior"); } return (BottomSheetBehavior<V>) behavior;}
public BottomSheetBehavior(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BottomSheetBehavior_Layout); TypedValue value = a.peekValue(R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight); if (value != null && value.data == PEEK_HEIGHT_AUTO) { setPeekHeight(value.data); } else { setPeekHeight(a.getDimensionPixelSize( R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight, PEEK_HEIGHT_AUTO)); } setHideable(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_hideable, false)); setSkipCollapsed(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed, false)); a.recycle(); ViewConfiguration configuration = ViewConfiguration.get(context); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); }
然后就是安排布局
@Overridepublic boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) { ViewCompat.setFitsSystemWindows(child, true); } int savedTop = child.getTop(); // First let the parent lay it out parent.onLayoutChild(child, layoutDirection); // Offset the bottom sheet mParentHeight = parent.getHeight(); int peekHeight; if (mPeekHeightAuto) { if (mPeekHeightMin == 0) { mPeekHeightMin = parent.getResources().getDimensionPixelSize( R.dimen.design_bottom_sheet_peek_height_min); } peekHeight = Math.max(mPeekHeightMin, mParentHeight - parent.getWidth() * 9 / 16); } else { peekHeight = mPeekHeight; } mMinOffset = Math.max(0, mParentHeight - child.getHeight()); mMaxOffset = Math.max(mParentHeight - peekHeight, mMinOffset); if (mState == STATE_EXPANDED) { //如果展开 ViewCompat.offsetTopAndBottom(child, mMinOffset); } else if (mHideable && mState == STATE_HIDDEN) { //如果隐藏 ViewCompat.offsetTopAndBottom(child, mParentHeight); } else if (mState == STATE_COLLAPSED) { //如果折叠,则以折叠高度为准(peekHeight) ViewCompat.offsetTopAndBottom(child, mMaxOffset); } else if (mState == STATE_DRAGGING || mState == STATE_SETTLING) { //如果是拖动或固定(拖动才会有位置变化) ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop()); } if (mViewDragHelper == null) { mViewDragHelper = ViewDragHelper.create(parent, mDragCallback); } mViewRef = new WeakReference<>(child); mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child)); return true;}
控制显示变化
public final void setState(final @State int state) { if (state == mState) { return; } if (mViewRef == null) { // The view is not laid out yet; modify mState and let onLayoutChild handle it later if (state == STATE_COLLAPSED || state == STATE_EXPANDED || (mHideable && state == STATE_HIDDEN)) { mState = state; } return; } final V child = mViewRef.get(); if (child == null) { return; } // Start the animation; wait until a pending layout if there is one. ViewParent parent = child.getParent(); //如果已经requested并且附加到window if (parent != null && parent.isLayoutRequested() && ViewCompat.isAttachedToWindow(child)) { child.post(new Runnable() { @Override public void run() { startSettlingAnimation(child, state); } }); } else { startSettlingAnimation(child, state); }}
具体的启动改变,setStateInternal中回调了BottomSheetCallback#onStateChanged
void startSettlingAnimation(View child, int state) { //先根据state设置好top位置 int top; if (state == STATE_COLLAPSED) { top = mMaxOffset; } else if (state == STATE_EXPANDED) { top = mMinOffset; } else if (mHideable && state == STATE_HIDDEN) { top = mParentHeight; } else { throw new IllegalArgumentException("Illegal state argument: " + state); } //先改为settling setStateInternal(STATE_SETTLING); //移动到指定位置后改为指定state if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) { ViewCompat.postOnAnimation(child, new SettleRunnable(child, state)); } }
设置状态很容易踩坑,使用的时候需要注意。
mMinOffset = Math.max(0, mParentHeight - child.getHeight());mMaxOffset = Math.max(mParentHeight - peekHeight, mMinOffset);
注意上面2句,在结合上面的具体移动。在没有peekHeight的时候STATE_COLLAPSED和STATE_EXPANDED这两个状态切换会出现mMinOffset==mMaxOffset的情况,也就是top没改变的情况,从而导致mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)
返回false导致最后state状态更新失败,回到STATE_SETTLING。总之在切换状态时注意2个状态之间top是否会变化,如果不能引起变化,则都会回到STATE_SETTLING
关于事件拦截与处理,滑动交给ViewDragHelper处理
//寻找behavior是否包含有NestedScrollingChildprivate View findScrollingChild(View view) { if (view instanceof NestedScrollingChild) { return view; } if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; for (int i = 0, count = group.getChildCount(); i < count; i++) { View scrollingChild = findScrollingChild(group.getChildAt(i)); if (scrollingChild != null) { return scrollingChild; } } } return null;}
@Overridepublic boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { //如果不可见不拦截 if (!child.isShown()) { mIgnoreEvents = true; return false; } int action = MotionEventCompat.getActionMasked(event); // Record the velocity if (action == MotionEvent.ACTION_DOWN) { reset(); } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); switch (action) { //恢复一些默认标记 case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mTouchingScrollingChild = false; mActivePointerId = MotionEvent.INVALID_POINTER_ID; // Reset the ignore flag if (mIgnoreEvents) { mIgnoreEvents = false; return false; } break; case MotionEvent.ACTION_DOWN: int initialX = (int) event.getX(); mInitialY = (int) event.getY(); View scroll = mNestedScrollingChildRef.get(); //如果有ns控件,并处于控件内 if (scroll != null && parent.isPointInChildBounds(scroll, initialX, mInitialY)) { mActivePointerId = event.getPointerId(event.getActionIndex()); mTouchingScrollingChild = true; } //判断是否忽略 mIgnoreEvents = mActivePointerId == MotionEvent.INVALID_POINTER_ID && !parent.isPointInChildBounds(child, initialX, mInitialY); break; } //不忽略,VDH要处理,拦截 if (!mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event)) { return true; } //VDH不处理情况 View scroll = mNestedScrollingChildRef.get(); return action == MotionEvent.ACTION_MOVE && scroll != null && !mIgnoreEvents && mState != STATE_DRAGGING && !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) && Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop();}
关于滑动的协同处理,前提是behavior内有ns控件。
//只协同处理竖直方向的滑动@Overridepublic boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) { mLastNestedScrollDy = 0; mNestedScrolled = false; return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;}
//这个方法主要处理的滑动控制,当遇到ns控件时,怎样协同处理ns的滚动,和自身的折叠展开等状态的控制@Overridepublic void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) { View scrollingChild = mNestedScrollingChildRef.get(); //如果不是ns控件不是发起者,不预处理 if (target != scrollingChild) { return; } int currentTop = child.getTop(); int newTop = currentTop - dy; if (dy > 0) { // Upward //如果上移超过了阈值,则以阈值为准 if (newTop < mMinOffset) { //计算消耗,移动,设置状态 consumed[1] = currentTop - mMinOffset; ViewCompat.offsetTopAndBottom(child, -consumed[1]); //此时属于展开状态 setStateInternal(STATE_EXPANDED); } else { consumed[1] = dy; ViewCompat.offsetTopAndBottom(child, -dy); //属于拖动状态 setStateInternal(STATE_DRAGGING); } } else if (dy < 0) { // Downward //需要检测是否可以垂直滚动,如NestedScrollView。如果可以滚动,则不预处理,先让其滚动 //直到不能滚动时,则预处理接管事件,进行拖动和折叠 if (!ViewCompat.canScrollVertically(target, -1)) { if (newTop <= mMaxOffset || mHideable) { consumed[1] = dy; ViewCompat.offsetTopAndBottom(child, -dy); setStateInternal(STATE_DRAGGING); } else { consumed[1] = currentTop - mMaxOffset; ViewCompat.offsetTopAndBottom(child, -consumed[1]); setStateInternal(STATE_COLLAPSED); } } } //回调BottomSheetCallback#onSlide dispatchOnSlide(child.getTop()); mLastNestedScrollDy = dy; mNestedScrolled = true;}
0 0
- BottomSheetBehavior
- 利用bottomSheetBehavior
- BottomSheetBehavior源码部分浅析
- android-bottomSheetBehavior详解
- BottomSheetBehavior的坑
- Android Material Design:BottomSheetBehavior
- BottomSheetDialog获得BottomSheetBehavior的方法
- 底部弹窗:BottomSheetBehavior使用
- bottomsheetbehavior和recycleview的冲突
- BottomSheetBehavior底部弹出的用法
- Android Support 23.2 BottomSheetBehavior的使用
- BottomSheetBehavior底部弹出窗口的用法
- Android Design Support Library BottomSheetBehavior使用
- BottomSheetBehavior、BottomSheetDialog和BottomSheetDialogFragment的用法
- Android的Design库---BottomSheetBehavior和BottomSheetDialog
- Material Design系列,Behavior之BottomSheetBehavior与BottomSheetDialog
- Material Design系列,Behavior之BottomSheetBehavior与BottomSheetDialog
- 如何使用Material Design中的BottomSheetBehavior、BottomSheetDialog和BottomSheetDialogFragment
- java 设计模式 —— 浅析单例模式
- 关于return的两个小例子
- CentOS7本地yum源安装
- HOG特征算法
- Linux下keepalived安装与配置
- BottomSheetBehavior
- [题解]bzoj1875(SDOI2009)HH去散步
- Java实现动态表查找--二叉排序树
- linux tail 命令详解
- java 设计模式之过滤器模式(Filter)
- FP增长算法(FP Growth Algorithm)
- Centos在VM中不能全屏问题
- 网易新闻客户端链接
- 程序员职业能窥探其他职业,反之...