美文网首页Android 进阶之旅
Android 进阶学习(二十九) BottomSheetD

Android 进阶学习(二十九) BottomSheetD

作者: Tsm_2020 | 来源:发表于2021-07-26 15:25 被阅读0次

    在第二十五篇博客中,通过重写BottomSheetDialog 给他添加了一个布局,同时也给顶部添加了一个TopMargin ,但是在使用过程中产品对于现阶段的BottomSheetDialog 的表现形式并不是非常满意,这里我先把已经添加底部固定区域的的BottomSheetDialog 的图片上一下,


    GIF 2021-6-9 17-00-38.gif

    本期,我们继续对这个dialog做一下修改,
    先来说一下我们的问题都有哪些,

    1. 由于给BottomSheetDialog 添加了一个顶部的margin,导致在BottomSheetDialog 完全展开时,点击顶部透明区域时,dialog并不会收缩
      2.产品在看了原生BottomSheetDialog 的展示形式时,感觉类似抖音之类的展示形式比较好,也就是整个BottomSheetDialog 除了展开就是隐藏,不想要中间的那个折叠状态,我们上下最终的效果图
    GIF 2021-7-26 19-06-54.gif

    由于修改这个代码比较多,所以还是分享一个github 的地址吧,这样方便大家使用
    https://github.com/tsm1991/TsmBottomSheetDialog

    1.点击外部不能消失的问题

    先来修改第一个问题,想要解决外部不能点击的问题很简单,我们只需要让最外层的View 可以点击就好了,

    <?xml version="1.0" encoding="utf-8"?>
    <!--
    ~ Copyright (C) 2015 The Android Open Source Project
    ~
    ~ Licensed under the Apache License, Version 2.0 (the "License");
    ~ you may not use this file except in compliance with the License.
    ~ You may obtain a copy of the License at
    ~
    ~      http://www.apache.org/licenses/LICENSE-2.0
    ~
    ~ Unless required by applicable law or agreed to in writing, software
    ~ distributed under the License is distributed on an "AS IS" BASIS,
    ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    ~ See the License for the specific language governing permissions and
    ~ limitations under the License.
    -->
    <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:clickable="true">/////  这里增加可以点击,同时在dialog中添加点击事件即可
      <androidx.coordinatorlayout.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="com.tsm.tsmmodelapp.behavior.TsmBottomSheetBehavior"/>
      </androidx.coordinatorlayout.widget.CoordinatorLayout>
      <FrameLayout
          android:id="@+id/bottom_design_bottom_sheet"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_gravity="bottom"
          android:clickable="true" ///这里的作用是屏蔽底部View 的点击事件,不让他传递到下一层
          android:background="#ffffff"
          >
      </FrameLayout>
    </FrameLayout>
    

    2.第二个问题就比较严重了,由于 BottomSheetDialog 很多信息我们在外界都获取不到,想要实现上述所说的问题就必须要重写BottomSheetBehavior , 并修改他的代码,我们这里就先看看我们需要改什么吧,

    public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
        
       ... 省略部分代码
    
       if (state == STATE_EXPANDED) {
        ViewCompat.offsetTopAndBottom(child, getExpandedOffset());
      } else if (state == STATE_HALF_EXPANDED) {
        ViewCompat.offsetTopAndBottom(child, halfExpandedOffset);
      } else if (hideable && state == STATE_HIDDEN) {
        ViewCompat.offsetTopAndBottom(child, parentHeight);
      } else if (state == STATE_COLLAPSED) {
        ViewCompat.offsetTopAndBottom(child, collapsedOffset);
      } else if (state == STATE_DRAGGING || state == STATE_SETTLING) {
        ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
      }
      ...省略部分代码
      }
    

    onLayoutChild 这个方法是在布局完成后调用,可以看到在布局完成后他是根据不同的状态来确定展示形式,而我们的需求比较粗暴,无非就是全部展开或者消失,这里就直接变成

    public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
        
       ... 省略部分代码
    
    ViewCompat.offsetTopAndBottom(child, getExpandedOffset());
      ...省略部分代码
      }
    

    全部都是完全展开的形式,

    这里处理完就剩下一个拖动事件了,由于他是使用了NestScroll 等一系列的事件分发的方式,我们只需要关注最后一帧的滑动事件就好了,所以这里需要看一下onStopNestedScroll 这个方法

    public void onStopNestedScroll(
        @NonNull CoordinatorLayout coordinatorLayout,
        @NonNull V child,
        @NonNull View target,
        int type) {
      if (child.getTop() == getExpandedOffset()) {///如果是展开,那么状态就是3
        setStateInternal(STATE_EXPANDED);
        return;
      }
      if (nestedScrollingChildRef == null
          || target != nestedScrollingChildRef.get()
          || !nestedScrolled) 
        return;
      }
      int top;
      int targetState;
      if (lastNestedScrollDy > 0) {
        if (fitToContents) {
          top = fitToContentsOffset;
          targetState = STATE_EXPANDED;
        } else {
          int currentTop = child.getTop();
          if (currentTop > halfExpandedOffset) {
            top = halfExpandedOffset;
            targetState = STATE_HALF_EXPANDED;
          } else {
            top = expandedOffset;
            targetState = STATE_EXPANDED;
          }
        }
      } else if (hideable && shouldHide(child, getYVelocity())) {
        top = parentHeight;
        targetState = STATE_HIDDEN;
      } else if (lastNestedScrollDy == 0) {
        int currentTop = child.getTop();
        if (fitToContents) {
          if (Math.abs(currentTop - fitToContentsOffset) < Math.abs(currentTop - collapsedOffset)) {
            top = fitToContentsOffset;
            targetState = STATE_EXPANDED;
          } else {
            top = collapsedOffset;
            targetState = STATE_COLLAPSED;
          }
        } else {
          if (currentTop < halfExpandedOffset) {
            if (currentTop < Math.abs(currentTop - collapsedOffset)) {
              top = expandedOffset;
              targetState = STATE_EXPANDED;
            } else {
              top = halfExpandedOffset;
              targetState = STATE_HALF_EXPANDED;
            }
          } else {
            if (Math.abs(currentTop - halfExpandedOffset) < Math.abs(currentTop - collapsedOffset)) {
              top = halfExpandedOffset;
              targetState = STATE_HALF_EXPANDED;
            } else {
              top = collapsedOffset;
              targetState = STATE_COLLAPSED;
            }
          }
        }
      } else {
        if (fitToContents) {
          top = collapsedOffset;
          targetState = STATE_COLLAPSED;
        } else {
          // Settle to nearest height.
          int currentTop = child.getTop();
          if (Math.abs(currentTop - halfExpandedOffset) < Math.abs(currentTop - collapsedOffset)) {
            top = halfExpandedOffset;
            targetState = STATE_HALF_EXPANDED;
          } else {
            top = collapsedOffset;
            targetState = STATE_COLLAPSED;
          }
        }
      }
      startSettlingAnimation(child, targetState, top, false);
      nestedScrolled = false;
    }
    

    其实这里很多属性我也不是很了解,但是我能看一下大概,具体修改后的代码如下,

      public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int type) {
          if (child.getTop() == this.getExpandedOffset()) {
              this.setStateInternal(3);
          } else if (this.nestedScrollingChildRef != null && target == this.nestedScrollingChildRef.get() && this.nestedScrolled) {
              int top;
              byte targetState;
              if (this.lastNestedScrollDy > 0) {
                  top = this.getExpandedOffset();
                  targetState = 3;
              } else if (this.hideable && this.shouldHide(child, this.getYVelocity())) {
                  top = this.parentHeight;
                  targetState = 5;
              } else {
                  int currentTop;
                  if (this.lastNestedScrollDy == 0) {//最后一帧没有滑动
                      currentTop = child.getTop();
                      if (currentTop < this.halfExpandedOffset){ //小于折叠高度  那么就收缩
                          targetState = STATE_HIDDEN;
                          top = this.parentHeight;
                      }else{//其他展开
                          top = this.getExpandedOffset();
                          targetState = 3;
                      }
                  }else {
                      currentTop = child.getTop();
                      ///大于折叠高度,并小于最大高度展开
                      if (Math.abs(currentTop - this.halfExpandedOffset) < Math.abs(currentTop - this.collapsedOffset)) {
                          top = this.getExpandedOffset();
                          targetState = 3;
                      } else {
                          top = this.parentHeight;///收起
                          targetState = 5;
                      }
                  }
              }
              this.startSettlingAnimation(child, targetState, top, false);
              this.nestedScrolled = false;
          }
      }
    
    

    去除了很多中间的状态的代码,反而看起来更简单了, 这里是嵌套滑动的整改,如果没有嵌套滑动,则调用的是onViewReleased 这个方法,所以对这个方法我们也要修改

    
    
          public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
              int top;
              byte targetState;
              int currentTop;
              if (yvel < 0.0F) {
                  if (TsmBottomSheetBehavior.this.fitToContents) {
                      top = TsmBottomSheetBehavior.this.fitToContentsOffset;
                      targetState = STATE_EXPANDED;
                  } else {
                      currentTop = releasedChild.getTop();
                      if (currentTop > TsmBottomSheetBehavior.this.halfExpandedOffset) {
                          top = TsmBottomSheetBehavior.this.halfExpandedOffset;
                          targetState = STATE_EXPANDED;
                      } else {
                          top = TsmBottomSheetBehavior.this.expandedOffset;
                          targetState = STATE_EXPANDED;
                      }
                  }
              } else if (TsmBottomSheetBehavior.this.hideable && TsmBottomSheetBehavior.this.shouldHide(releasedChild, yvel) && (releasedChild.getTop() > TsmBottomSheetBehavior.this.collapsedOffset || Math.abs(xvel) < Math.abs(yvel))) {
                  top = TsmBottomSheetBehavior.this.parentHeight;
                  targetState = STATE_HIDDEN;
              } else if (yvel != 0.0F && Math.abs(xvel) <= Math.abs(yvel)) {
                  if (TsmBottomSheetBehavior.this.fitToContents) {
                      top = TsmBottomSheetBehavior.this.parentHeight;
                      targetState = STATE_HIDDEN;
                  } else {
                      currentTop = releasedChild.getTop();
                      if (Math.abs(currentTop - TsmBottomSheetBehavior.this.halfExpandedOffset) < Math.abs(currentTop - TsmBottomSheetBehavior.this.collapsedOffset)) {
                          top = TsmBottomSheetBehavior.this.expandedOffset;
                          targetState = STATE_EXPANDED;
                      } else {
                          top = TsmBottomSheetBehavior.this.parentHeight;
                          targetState = STATE_HIDDEN;
                      }
                  }
              } else {
                  currentTop = releasedChild.getTop();
                  if (TsmBottomSheetBehavior.this.fitToContents) {
                      if (Math.abs(currentTop - TsmBottomSheetBehavior.this.fitToContentsOffset) < Math.abs(currentTop - TsmBottomSheetBehavior.this.collapsedOffset)) {
                          top = TsmBottomSheetBehavior.this.fitToContentsOffset;
                          targetState = STATE_EXPANDED;
                      } else {
                          top = TsmBottomSheetBehavior.this.parentHeight;
                          targetState = STATE_HIDDEN;
                      }
                  } else if (currentTop < TsmBottomSheetBehavior.this.halfExpandedOffset) {
                      if (currentTop < Math.abs(currentTop - TsmBottomSheetBehavior.this.collapsedOffset)) {
                          top = TsmBottomSheetBehavior.this.expandedOffset;
                          targetState = STATE_EXPANDED;
                      } else {
                          top = TsmBottomSheetBehavior.this.parentHeight;
                          targetState = STATE_HIDDEN;
                      }
                  } else if (Math.abs(currentTop - TsmBottomSheetBehavior.this.halfExpandedOffset) < Math.abs(currentTop - TsmBottomSheetBehavior.this.collapsedOffset)) {
                      top = TsmBottomSheetBehavior.this.parentHeight;
                      targetState = STATE_HIDDEN;
                  } else {
                      top = TsmBottomSheetBehavior.this.parentHeight;
                      targetState = STATE_HIDDEN;
                  }
              }
              TsmBottomSheetBehavior.this.startSettlingAnimation(releasedChild, targetState, top, true);
          }
    
    }
    

    最后结合第二十五篇的BottomSheetDialog,就可以使用了

    相关文章

      网友评论

        本文标题:Android 进阶学习(二十九) BottomSheetD

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