CoordinatorLayout的使用(四)——通过AppBarLayout源码分析联动机制

2023-05-16

一、整体交互逻辑

上一篇文章,我们从CoordinatorLayout源码出发,分析了一下Behavior几个重点方法的调用逻辑和流程。知道了整个交互的分发流程。但是具体是怎么让一个不是ScrollingView类型的View(如本篇要讲的AppBarLayout继承制LinearLayout的)怎么产生滑动效果的呢?这就是本篇所要讲解的内容了。

首先,我们先用一张图说明下整个交互逻辑,这里NestedScrollingChild实现类用NestedScrollView(这张图是网上找的)

  • CoordinatorLayout作为父布局,用于管理和分发交互事件给对应Behavior处理。

  • 系统默认会将AppBarLayout.BehaviorAppBarLayout进行绑定,负责具体处理由CoordinatorLayout分发过来的事件。

  • NestedScrollView充当滑动子View的角色(实现了NestedScrollingChild接口),在自身滑动过程中将对应事件传递个CoordinatorLayout(实现了NestedScrollingParent2接口),在分发给AppBarLayout.Behavior

  • ScrollingViewBehaviorNestedScrollView进行绑定(通过),它会依赖于AppBarLayout,从而在AppBarLayout滑动的时候,可以收到回调,做处理。

二、源码分析

1、类简介

上面介绍了整体的交互逻辑,下面就开始进入源码分析吧。

这里NestedScrollView的不是重点分析对象,不会做过多介绍,如果感兴趣的可以看这篇文章。这里我们重点看下AppBarLayout和它的两个内部类:AppBarLayout.BehaviorScrollingViewBehavior

先说AppBarLayout

还是看看Google的官方介绍吧,我觉得学习过程中真的要多看看Google官方文档,它里面的描述是最准确的。很多时候,我们使用过程中需要注意的事项在里面也会有提及。

AppBarLayout is a vertical LinearLayout which implements many of the features of material designs app bar concept, namely scrolling gestures.

Children should provide their desired scrolling behavior through setScrollFlags(int) and the associated layout xml attribute: app:layout_scrollFlags.

This view depends heavily on being used as a direct child within a CoordinatorLayout. If you use AppBarLayout within a different ViewGroup, most of it's functionality will not work.

AppBarLayout also requires a separate scrolling sibling in order to know when to scroll. The binding is done through the AppBarLayout.ScrollingViewBehavior behavior class, meaning that you should set your scrolling view's behavior to be an instance of AppBarLayout.ScrollingViewBehavior. A string resource containing the full class name is available.

大致翻译下就是:AppBarLayout是垂直方向的LinearLayout,同时遵循了material designs,可以支持很多滑动手势的交互。

通过在xml文件中设置app:layout_scrollFlags属性,或者通过setScrollFlags(int) 可以给子View设置不同的交互逻辑。

同时需要注意,该类只能作为CoordinatorLayout的直接子View,否则它的那些特性将会失效。你看这里Google就清除说明了使用时候的注意事项。所以多看官方文档总是没错的。

为了和滑动控件进行交互绑定,我们需要将AppBarLayout.ScrollingViewBehavior类的全路劲设置给需要进行交互的滑动View(如NestedScrollView)。

介绍完AppBarLayout,我们再看看AppBarLayout.Behavior

The default AppBarLayout.Behavior for AppBarLayout. Implements the necessary nested scroll handling with offsetting.

这个类,介绍很简单,就说是AppBarLayout的默认Behavior,实现了嵌套滑动的交互处理。

最后,我们再看下ScrollingViewBehavior

Behavior which should be used by Views which can scroll vertically and support nested scrolling to automatically scroll any AppBarLayout siblings.

这个类的介绍也很简单,继承自Behavior,需要设置给要自动响应AppBarLayout嵌套滑动事件的View。如NestedScrollView

2、交互流程分析

上面大致说了每个类在交互过程中担任的角色,也大致看了一下简介,下面就进入具体的分析。AppBarLayout和其他可嵌套滑动的View(为了叙述方面,我们都直接用NestedScrollView代替了)之间的交互,无非就两种情况。

  • AppBarLayout引起的滑动,然后NestedScrollView需要进行相关处理

  • 由NestedScrollView引起的滑动,AppBarLayout需要进行相关处理

所以,下面我们也从这两种方式进行分析,首先我们有AppBarLayout产生滑动的情况,这种情况下,我们的触摸范围是在AppBarLayout所在的范围, 通过上一篇CoordinatorLayout源码分析的学习,我们知道,最终会由CoordinatorLayout分发给它的子View的Behavior处理,这里会给到AppBarLayout.Behavior。所以这里我们就先看下AppBarLayout.Behavior中的onInterceptTouchEvent。但是我们发现,这个类里面没有这个方法。其实我们仔细看,会发现它是继承自HeaderBehaviorHeaderBehavior继承自ViewOffsetBehavior。所以,既然它自己没有这个方法,就找它父类了。在HeaerBehavior中有实现。

@Override
      public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
          if (mTouchSlop < 0) {
              mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
          }
  ​
          final int action = ev.getAction();
  ​
          // Shortcut since we're being dragged
          if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
              return true;
          }
  ​
          switch (ev.getActionMasked()) {
              case MotionEvent.ACTION_DOWN: {
                  mIsBeingDragged = false;
                  final int x = (int) ev.getX();
                  final int y = (int) ev.getY();
                  // 判断是否可以滑动 并且点击位置要在child(AppBarLayout)的范围内
                  if (canDragView(child) && parent.isPointInChildBounds(child, x, y)) {
                      // 记录下点击位置
                      mLastMotionY = y;
                      mActivePointerId = ev.getPointerId(0);
                      ensureVelocityTracker();
                  }
                  break;
              }
  ​
              case MotionEvent.ACTION_MOVE: {
                  final int activePointerId = mActivePointerId;
                  if (activePointerId == INVALID_POINTER) {
                      // If we don't have a valid id, the touch down wasn't on content.
                      break;
                  }
                  final int pointerIndex = ev.findPointerIndex(activePointerId);
                  if (pointerIndex == -1) {
                      break;
                  }
  ​
                  final int y = (int) ev.getY(pointerIndex);
                  final int yDiff = Math.abs(y - mLastMotionY);
                  // 判断是不是滑动操作
                  if (yDiff > mTouchSlop) {
                      // 设置拦截标志为true
                      mIsBeingDragged = true;
                      // 几下当前手指的位置
                      mLastMotionY = y;
                  }
                  break;
              }
  ​
              case MotionEvent.ACTION_CANCEL:
              case MotionEvent.ACTION_UP: {
                  mIsBeingDragged = false;
                  mActivePointerId = INVALID_POINTER;
                  // Fling相关处理(Fling相关知识,不是重点,就自己去补充了)
                  if (mVelocityTracker != null) {
                      mVelocityTracker.recycle();
                      mVelocityTracker = null;
                  }
                  break;
              }
          }
          // Fling相关处理
          if (mVelocityTracker != null) {
              mVelocityTracker.addMovement(ev);
          }
  ​
          return mIsBeingDragged;
      }

可以看到,里面代码还是比较简单的,主要就是判断是否是有效滑动,如果是,我们就拦截事件,然后交给自己的onTouchEvent()处理。具体过程请看方法里面的注释吧。

然后我们就进入onTouchEvent()里面看下吧,我们发现,这个方法也是在HeaderBehavior中处理的。

@Override
      public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
          if (mTouchSlop < 0) {
              mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
          }
  ​
          switch (ev.getActionMasked()) {
              case MotionEvent.ACTION_DOWN: {
                  final int x = (int) ev.getX();
                  final int y = (int) ev.getY();
                  // 也是判断是否可以滑动 并且在范围内
                  if (parent.isPointInChildBounds(child, x, y) && canDragView(child)) {
                      // 是:记下触摸位置
                      mLastMotionY = y;
                      mActivePointerId = ev.getPointerId(0);
                      ensureVelocityTracker();
                  } else {
                      // 不是 直接放行
                      return false;
                  }
                  break;
              }
  ​
              case MotionEvent.ACTION_MOVE: {
                  final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                  if (activePointerIndex == -1) {
                      return false;
                  }
  ​
                  final int y = (int) ev.getY(activePointerIndex);
                  // 计算滑动距离
                  int dy = mLastMotionY - y;
  ​
                  // 如果是有效滑动并且之前没有进行过滑动
                  if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) {
                      // 设置滑动标志
                      mIsBeingDragged = true;
                      // 微调滑动距离
                      if (dy > 0) {
                          dy -= mTouchSlop;
                      } else {
                          dy += mTouchSlop;
                      }
                  }
  ​
                  // 如果手指是在滑动
                  if (mIsBeingDragged) {
                      mLastMotionY = y;
                      // We're being dragged so scroll the ABL
                      // 根据手指滑动的距离,移动AppBarLayout
                      scroll(parent, child, dy, getMaxDragOffset(child), 0);
                  }
                  break;
              }
  ​
              case MotionEvent.ACTION_UP:
                  if (mVelocityTracker != null) {
                      mVelocityTracker.addMovement(ev);
                      mVelocityTracker.computeCurrentVelocity(1000);
                      float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
                      // 抬起的时候处理的Fling相关逻辑
                      fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel);
                  }
                  // $FALLTHROUGH
              case MotionEvent.ACTION_CANCEL: {
                  // 取消 重置滑动标志
                  mIsBeingDragged = false;
                  mActivePointerId = INVALID_POINTER;
                  if (mVelocityTracker != null) {
                      mVelocityTracker.recycle();
                      mVelocityTracker = null;
                  }
                  break;
              }
          }
  ​
          if (mVelocityTracker != null) {
              mVelocityTracker.addMovement(ev);
          }
  ​
          return true;
      }

方法注释里里面已经说的比较清楚了,可以看到。有效滑动的时候,我们交给scroll()方法进行处理的,然后Fling操作是交给fling()方法处理的。我们先看scroll()方法

final int scroll(CoordinatorLayout coordinatorLayout, V header,
              int dy, int minOffset, int maxOffset) {
          return setHeaderTopBottomOffset(coordinatorLayout, header,
                  getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset);
      }

又给到了setHeaderTopBottomOffset()处理,那我们继续跟踪,这里要注意下。AppBarLayout.Behavior重写了该方法,所以这里需要进入AppBarLayout.Behavior里面看实现逻辑:

@Override
          //newOffeset:AppBarLayout已经移动的距离-手指滑动的距离(手指向上滑为正,向下滑为负)
          //minOffset:负的AppBarLayout的height,
          //maxOffset:0。
          int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
                  AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
              // 获取当前AppBarLayout的偏移量(已经移动过的距离)
              final int curOffset = getTopBottomOffsetForScrollingSibling();
              int consumed = 0;
  ​
              // 合法性检测:AppBarLayout滑动的距离必须在minOffset和maxOffset之间
              if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
                  // 计算出最终可以移动的距离
                  newOffset = MathUtils.clamp(newOffset, minOffset, maxOffset);
                  // 判断是否需要移动(newOffset就是本次滑动最终需要产生的偏移量)
                  // 如果当前的偏移量和最终需要的不相等,才进行移动
                  if (curOffset != newOffset) {
                      final int interpolatedOffset = appBarLayout.hasChildWithInterpolator()
                              // 如果我们自己设置了插值器 会进行条用
                              ? interpolateOffset(appBarLayout, newOffset)
                              : newOffset;
  ​
                      // 最终通过ViewCompat.offsetTopAndBottom()移动AppBarLayout
                      final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
  ​
                      // 更新消费的距离
                      consumed = curOffset - newOffset;
                      // 如果没有设置Interpolator 为0
                      mOffsetDelta = newOffset - interpolatedOffset;
  ​
                      if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
                      // 虽然这里没有移动操作 但是在我们自己设置的插值器中可能产生了移动 需要给依赖的View发送通知
                          coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
                      }
  ​
                      // 回调OnOffsetChangedListener监听
                      appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset());
  ​
                      // 根据我们设置的ScrollFlags调整状态
                      updateAppBarLayoutDrawableState(coordinatorLayout, appBarLayout, newOffset,
                              newOffset < curOffset ? -1 : 1, false);
                  }
              } else {
                  // Reset the offset delta
                  mOffsetDelta = 0;
              }
  ​
              return consumed;
          }

   

在这里面,我们看到,正常情况下就会setTopAndBottomOffset()方法来移动我们的AppBarLayout了,同时做回调通知,最后根据设置的不同的scrolls_flag更新子View的状态(该隐藏的隐藏,该出来的出来)。

到这里,我们知道AppBarLayout是怎么通过手指滑动产生移动的了,但是我们实际使用中,AppBarLayout发生移动的同时,NestedScrollView也会跟着上下滚动,NestedScrollView又是怎么知道需要滚动的呢?还记得在介绍类的时候说了,必须给NestedScrollView设置ScrollingViewBehavior么。玄机就在这个类里面,我们看下该类:

  public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
  ​
          public ScrollingViewBehavior() {}
  ​
          public ScrollingViewBehavior(Context context, AttributeSet attrs) {
              super(context, attrs);
  ​
              final TypedArray a = context.obtainStyledAttributes(attrs,
                      R.styleable.ScrollingViewBehavior_Layout);
              setOverlayTop(a.getDimensionPixelSize(
                      R.styleable.ScrollingViewBehavior_Layout_behavior_overlapTop, 0));
              a.recycle();
          }
  ​
          @Override
          public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
              // 同AppBarLayout产生关联,那AppBarLayout发生移动的时候,就会收到回调了
              return dependency instanceof AppBarLayout;
          }
  ​
          @Override
          public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                  View dependency) {
              // 这里收到AppBarLayout移动的通知,然后就跟着移动了
              offsetChildAsNeeded(parent, child, dependency);
              return false;
          }
  ​
          ……
  ​
          private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
              // 获取到AppBarLayout的Behavior
              final CoordinatorLayout.Behavior behavior =
                      ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
              if (behavior instanceof Behavior) {
                  // Offset the child, pinning it to the bottom the header-dependency, maintaining
                  // any vertical gap and overlap
                  final Behavior ablBehavior = (Behavior) behavior;
                  // 计算出需要移动的距离,并跟随移动
                  ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
                          + ablBehavior.mOffsetDelta
                          + getVerticalLayoutGap()
                          - getOverlapPixelsForOffset(dependency));
              }
          }
          ……
      }

这里就不多解释了,看过上篇文章应该很容易就理解了,NestedScrollView和AppBarLayout通过这个类产生了依赖关系。AppBarLayout移动的时候会通知进入这里。就可以做同步移动操作了。

到这里,AppBarLayout的移动,并通知NestedScrollView同时移动的过程就分析完了。其实在onTouchEvent()中ACTION_UP的时候,还有一个fling()方法进行惯性滑动的操作。这里就不具体分析,机制也是一样,感兴趣的同学自己看下源码就明白了。只是这里说明一点,因为Fling的主体是AppBarLayout,所以不管我们在其上面滑动多块,最多也就只能滑动AppBarLayout的最大可滑动距离,而不会去滑动NestedScrollView。

说完AppBarLayout的滑动后,我们接着看我们之前说的第二种情况,由NestedScrollView滑动,引起AppBarLayout相应移动的逻辑了。我们知道,NestedScrollView滑动的时候,会通过CoordinatorLayout将相关的方法代理给Behavior来处理(这里也就是AppBarLayout.Behavior)。根据嵌套滑动机制,会首先调用onStartNestedScroll()方法。那我们进看下这个实现逻辑吧:

@Override
      //directTargetChild=target=NestedScrollView
          public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                  View directTargetChild, View target, int nestedScrollAxes, int type) {
              //如果滑动方向为VERTICAL,有足够空间进行滑动,并且有可滑动Child,started=true;
              final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
                      && child.hasScrollableChildren()
                      && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
  ​
              if (started && mOffsetAnimator != null) {
                  // Cancel any offset animation
                  mOffsetAnimator.cancel();
              }
  ​
              // A new nested scroll has started so clear out the previous ref
              mLastNestedScrollingChildRef = null;
  ​
              return started;
          }
这里主要方向和有足够滑动空间比较好理解,说下要有可以滑动的Child这个判断,看下具体实现:

  boolean hasScrollableChildren() {
          return getTotalScrollRange() != 0;
      }
      
  public final int getTotalScrollRange() {
          if (mTotalScrollRange != INVALID_SCROLL_RANGE) {
              return mTotalScrollRange;
          }
  ​
          int range = 0;
          for (int i = 0, z = getChildCount(); i < z; i++) {
              final View child = getChildAt(i);
              final LayoutParams lp = (LayoutParams) child.getLayoutParams();
              final int childHeight = child.getMeasuredHeight();
              final int flags = lp.mScrollFlags;
  ​
              if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
                  // We're set to scroll so add the child's height
                  range += childHeight + lp.topMargin + lp.bottomMargin;
  ​
                  if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                      // For a collapsing scroll, we to take the collapsed height into account.
                      // We also break straight away since later views can't scroll beneath
                      // us
                      range -= ViewCompat.getMinimumHeight(child);
                      break;
                  }
              } else {
                  // As soon as a view doesn't have the scroll flag, we end the range calculation.
                  // This is because views below can not scroll under a fixed view.
                  break;
              }
          }
          return mTotalScrollRange = Math.max(0, range - getTopInset());
      }

就是通过判断ChildView的scroll_flags属性,计算设置了SCROLL_FLAG_SCROLL属性的Child高度和,设置了该属性就代表是可以滚动的View,然后减去设置SCROLL_FLAG_EXIT_UNTIL_COLLAPSED的折叠高度,为什么要减去呢,因为这个标志,代表该View会变小知道达到最小高度,所以该控件的最小高度是不能计算在可滚动范围内的。

回到主流程上,所以正常情况下,onStartNestedScroll()是会返回true,也就是告诉系统我要消费滑动,后面就会继续进入onNestedPreScroll()方法。

@Override
          public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                  View target, int dx, int dy, int[] consumed, int type) {
              if (dy != 0) {
                  int min, max;
                  if (dy < 0) {
                      // 向下滚动 
                      min = -child.getTotalScrollRange(); // 该方法上面已经讲过了,获取到可滚动距离,然后取负数
                      max = min + child.getDownNestedPreScrollRange(); // 判断有没有向下滑动需要立即滑出的距离
                  } else {
                      // 向上滚动
                      min = -child.getUpNestedPreScrollRange(); // 内部就是getTotalScrollRange()
                      max = 0;
                  }
                  if (min != max) {
                      // 通过scroll方法进行滑动处理
                      consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
                  }
              }
          }

上面注释已经说的比较清楚了。就是根据滑动方向,然后分别做好边界值的处理。最后通过scroll()方法进行滑动处理。

getUpNestedPreScrollRange() 就是调用的getTotalScrollRange(),上面已经讲了,不再赘述。getDownNestedPreScrollRange()这个方法会获取到下滑的时候,需要立即移动的距离,然后加上min的值给到max。这样在后面通过scroll()方法处理移动的时候,才能够展示,如果这里返回时0,代表没有需要里展示的情况,这样min和max两个值相等,最终sroll()内部就不会产生移动,consumed[1]=0,最终就会给NestedScrollView自己去消费滑动距离。

int getDownNestedPreScrollRange() {
          if (mDownPreScrollRange != INVALID_SCROLL_RANGE) {
              // If we already have a valid value, return it
              return mDownPreScrollRange;
          }
  ​
          int range = 0;
          for (int i = getChildCount() - 1; i >= 0; i--) {
              final View child = getChildAt(i);
              final LayoutParams lp = (LayoutParams) child.getLayoutParams();
              final int childHeight = child.getMeasuredHeight();
              final int flags = lp.mScrollFlags;
  ​
              if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) {
                  // First take the margin into account
                  range += lp.topMargin + lp.bottomMargin;
                  // The view has the quick return flag combination...
                  if ((flags & LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) != 0) {
                      // If they're set to enter collapsed, use the minimum height
                      range += ViewCompat.getMinimumHeight(child);
                  } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                      // Only enter by the amount of the collapsed height
                      range += childHeight - ViewCompat.getMinimumHeight(child);
                  } else {
                      // Else use the full height (minus the top inset)
                      range += childHeight - getTopInset();
                  }
              } else if (range > 0) {
                  // If we've hit an non-quick return scrollable view, and we've already hit a
                  // quick return view, return now
                  break;
              }
          }
          return mDownPreScrollRange = Math.max(0, range);
      }

就是判断是否有设置enterAlways或者enterAlwaysCollapsed的flag。如果只有scroll|enterAlways,下滑的时候,需要先将控件显示出来,需要加上整个View的高度。如果是scroll|enterAlways|enterAlwaysCollapsed,需要先滑出控件的最小高度,需要加上控件的最小高度。

接着继续嵌套滑动机制的下一步骤onNestedScroll()onStopNestedScroll()方法。这两个方法比较简单,就一起介绍了

@Override
          public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                  View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                  int type) {
              if (dyUnconsumed < 0) {
                  // If the scrolling view is scrolling down but not consuming, it's probably be at
                  // the top of it's content
                  scroll(coordinatorLayout, child, dyUnconsumed,
                          -child.getDownNestedScrollRange(), 0);
              }
          }
  ​
          @Override
          public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl,
                  View target, int type) {
              if (type == ViewCompat.TYPE_TOUCH) {
                  // If we haven't been flung then let's see if the current view has been set to snap
                  snapToChildIfNeeded(coordinatorLayout, abl);
              }
  ​
              // Keep a reference to the previous nested scrolling child
              mLastNestedScrollingChildRef = new WeakReference<>(target);
          }

onNestedScroll()就是判断是否还有未消费的滑动距离,如果有,就直接交给scroll()方法进行处理。一般就是NestedScrollView已经滑到边界的时候,才会有剩余未消费的距离,其他情况一般dyUnconsumed=0

onStopNestedScroll()就是对吸附效果进行处理,也就是snap标志位,判断最终是要全部显示设置了snap的View还是全部隐藏。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CoordinatorLayout的使用(四)——通过AppBarLayout源码分析联动机制 的相关文章

  • Qt上位机:与STM32串口通信,数据收发,按钮控制LED

    Qt学习了几周 xff0c 做一个串口助手巩固一下最近学习的内容 遇到的问题1 xff1a write函数只能发送一次数据 xff0c 想要继续发送必须重新关闭打开串口 xff0c 每次只能发送一次数据 解决办法 xff1a 在网上找不到类
  • 考研数据结构2 | 使用 C++ 实现顺序栈 | 栈的基本应用之计算后缀表达式

    文章目录 1 顺序栈 简介2 顺序栈 代码实现3 栈的应用之计算后缀表达式3 1 表达式介绍3 2 计算后缀表达式的实现3 3 完整代码3 4 LeetCode 提交代码 1 顺序栈 简介 在上一次的学习中 xff0c 使用指针实现了链栈
  • C++使用libcurl做HttpClient

    C 43 43 使用libcurl做HttpClient 分类 xff1a 基础技术分享 2012 06 14 19 25 1469人阅读 评论 3 收藏 举报 当使用C 43 43 做HTTP客户端时 xff0c 目前通用的做法就是使用l
  • CAN的报文格式

    CAN的报文格式 在总线中传送的报文 xff0c 每帧由7部分组成 CAN协议支持两种报文格式 xff0c 其唯一的不同是标识符 xff08 ID xff09 长度不同 xff0c 标准格式为11位 xff0c 扩展格式为29位 在标准格式
  • uabntu系统安装软件:E: 无法定位软件包问题

    血泪教训 耽误了贼长时间 一开始参考了如下链接 xff0c 然而我的问题一个没解决 xff0c 哭了 Ubuntu sudo apt get install 出现 E 无法定位软件包问题 解决方法汇总 遥想在想peach的博客 CSDN博客
  • 右手定则

    http www 7wenta com zhuanti 9648 html 问他 首页问答中心竞技场 学习快报 达人榜商城 问他手机版 注册 登录 问他网 gt 学习专题 gt 高中物理 gt 右手定则 物理题库练习题我要提问
  • 【机器人规划】Bug解析

    文章目录 参考文献简介Bug1算法Bug2算法Tangent Bug算法关于O i的选择激光雷达半径对算法的影响 总结 参考文献 自动驾驶决策规划算法 Bug Algorithms Bug算法 Robotic Motion Planning
  • ubantu虚拟机无法联网

    在VMware中安装Ubuntu虚拟机 xff0c 总会发生无法上网的情况 xff0c 主要情况有以下几点 xff1a 宿主机可以上网 xff1b 虚拟机却无法访问网页虚拟机ping不通任何网站 xff0c 用浏览器显示error 一般情况
  • 半监督语义分割论文学习记录

    Semi Supervised Semantic Segmentation with Cross Consistency Training 1 1 motivation 一致性训练的目的是在应用于输入的小扰动上增强模型预测的不变性 因此 x
  • 使用Qt二次开发周立功CAN(一)

    使用Qt二次开发周立功CAN xff08 一 xff09 使用Qt二次开发周立功的CAN通信 xff0c 第一步需要完成动态链接库的加载 xff0c 成功加载之后才能调用其提供的接口函数 加载库需要注意的问题有两个 xff1a 一是Qt版本

随机推荐

  • 字节序基础知识

    在各种计算机体系结构中 xff0c 对于字节 字等的存储机制有所不同 xff0c 因而引发了计算机通信领 域中一个很重要的问题 xff0c 即通信双方交流的信息单元 xff08 比特 字节 字 双字等等 xff09 应该以什么样的顺序进行传
  • vlc命令行: 转码 流化 推流

    写在命令行之前的话 xff1a VLC不仅仅可以通过界面进行播放 xff0c 转码 xff0c 流化 xff0c 也可以通过命令行进行播放 xff0c 转码和流化 还可以利用里面的SDK进行二次开发 vlc命令行使用方法 xff1a 1 x
  • C++ 简单实现HTTP GET/POST 请求

    HTTP 超文本传输协议 是一种客户端与服务端的传输协议 xff0c 最早用于浏览器和服务器之间的通信 xff0c 后来因为其使用灵活 方便等特点 xff0c 广泛用于客户端与服务端的通信 文章将简单介绍HTTP协议 xff0c 同时以C
  • STM32单片机HAL库下串口接收不定长数据

    xff33 xff34 xff2d xff13 xff12 单片机 xff28 xff21 xff2c 库下串口接收不定长数据 xff28 xff21 xff2c 库下的串口接收不定长数据代码配置代码实现代码演示总结 xff28 xff21
  • C++将一个数据格式化为固定长度的字符串

    经常会遇到将数据解析为文本文件的现象 xff0c 通常因为数据长度的不同导致 xff0c 可视化效果不好 写一个输入数据获取固定长度字符串的函数 xff0c 来得到一个固定长度的数据 xff0c 让格式化看起来好看一些 include lt
  • Socket原理与编程基础

    一 Socket简介 Socket是进程通讯的一种方式 xff0c 即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换 几个定义 xff1a xff08 1 xff09 IP地址 xff1a 即依照TCP IP协议分
  • mac 安装brew

    起因是这样的 xff0c 我想在mac上安装htop 然后我了解到可以用brew安装htop 然后再执行命令 brew install htop 所以我就开始吭哧吭哧安装brew 过程xue wei 曲折了一些 先是看到一个文章 xff0c
  • 【项目学习】C++实现高并发服务器——代码学习(三)用户注册登录功能

    项目来源 xff1a WebServer 上一篇 xff1a 存储解析HTTP请求报文 xff0c 创建响应报文 本文介绍以下功能的代码实现 利用RAII机制实现了数据库连接池 xff0c 减少数据库连接建立与关闭的开销 xff0c 同时实
  • 用CSS3实现动画进度条

    CSS3的新特性为我们实现漂亮的进度条扫清了障碍 xff0c 我们可以完全不需要任何图片和简单的Javascript代码就可以构建 一 第一个例子 效果图 xff1a Demo地址 xff1a http namepk sinaapp com
  • tcpdump命令使用详解

    tcpdump命令使用详解 疯狂的小企鹅的博客 CSDN博客 tcpdump命令详解全网最详细的 tcpdump 使用指南 王一白 博客园Tcpdump抓包工具实战教程 xff0c 让你知道一个抓包走天下 xff01 哔哩哔哩 bilibi
  • Chrome浏览器Postman插件安装包及安装教程

    最近电脑装了新环境 xff0c 以前本地的postman安装包竟然找不到了 xff0c 网上费尽心力找了很多资源 xff0c 终于找到纯净的安装包 xff0c 绕开套路 xff0c 现将Postman安装包及安装教程分享给各位 xff0c
  • LayoutInflater的错误用法(Avoid passing null as the view root )

    今天在练习使用Fragment的时候 xff0c 注意到在使用LayoutInflater的时候有黄色报警 xff08 Avoid passing null as the view root needed to resolve layout
  • Android M(6.0)运行时权限申请及遇到的坑

    一 概述 在对动态权限申请进行详细说明时 xff0c 还是先大致介绍下6 0后 xff0c google对权限的一个归类和划分 在Android M之前 xff0c 再开发应用的时候 xff0c 程序员只需要在AndroidManifest
  • Android DataBinding介绍(一)——简介、数据及方法事件绑定

    简介 Data binding 是Google在2015年7月发布的Android Studio v1 3 0 版本上引入的 xff0c 在2016年4月Android Studio v2 0 0 上正式支持 引入之初 xff0c 不支持双
  • CoordinatorLayout的使用(一)——简单使用

    简介 CoordinatorLayout是Android support design推出的新布局 xff0c 主要用于作为视图根布局以及协调子控件的行为 xff08 根据用户的触摸行为产生一定的动画效果 xff09 主要是通过设置子Vie
  • BottomSheetDialog的使用及注意事项

    一 BottomSheetDialog简介 用途 xff1a 底部弹起的view或dialog 实现 xff1a 其关键也是CoordinatorLayout与Behavior 要求 xff1a 采用View的形式展示的话 xff0c 用于
  • mac设置mysql的环境变量

    1 终端输入 xff1a PATH 61 34 PATH 34 usr local mysql bin 这种每次重新进入终端都得写一次 xff0c 比较麻烦 xff1b 2 改变 zshrc文件 终端输入 xff1a vim zshrc 按
  • 关于解决自定义FloatingActionButton滑动行为(Behavior)只隐藏不出现的问题

    最近在使用FloatingActionButton的时候 xff0c 自定义了其Behavior xff0c 然后发现在SDK在25及以上的时候 xff0c 出现了只能隐藏不能重新出现的问题 xff08 24及以下没有出现此问题 xff09
  • NestedScrolling机制解析(二)——NestedScrollView源码

    上一篇文章我们介绍了NestedScrollingParent和NestedScrollingChild接口 xff0c 了解了两个接口里的方法和相互之间的调用关系 这篇我们以NestedScrollView类为例 xff0c 看先嵌套滚动
  • CoordinatorLayout的使用(四)——通过AppBarLayout源码分析联动机制

    一 整体交互逻辑 上一篇文章 xff0c 我们从CoordinatorLayout源码出发 xff0c 分析了一下Behavior几个重点方法的调用逻辑和流程 知道了整个交互的分发流程 但是具体是怎么让一个不是ScrollingView类型