目录介绍

  • 01.CoordinatorLayout滑动抖动问题描述
  • 02.滑动抖动问题分析
  • 03.自定义AppBarLayout.Behavior说明
  • 04.CoordinatorLayout滑动抖动解决方案
  • 05.案例测试是否根本问题

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

01.CoordinatorLayout滑动抖动问题描述

  • 先看下布局

    <android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/coordinator"
    android:layout_width="match_parent"
    android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"> <!--这个是滚动头部-->
    <include layout="@layout/include_find_header"/> <!--这个是吸顶布局-->
    <include layout="@layout/include_sticky_header"/> </android.support.design.widget.AppBarLayout> <!--app:layout_behavior 属性,该布局包含一个竖直方向的RecyclerView-->
    <include layout="@layout/include_recycler_view" /> </android.support.design.widget.CoordinatorLayout>
  • 出现的问题
    • 用手指轻轻滑动CoordinatorLayout部分, 上滑, 快速抬起手指, 形成一个fling操作。其实就是向上滑动一下!
    • 这时, 整个CoordinatorLayout部分会向上移动(fling),在停止移动之前,在下面的区域(也就是xml布局中的include_recycler_view)来一个反向的滑动(fling) , 这时整个页面就会开始或大或小的抖动, 非常明显。

02.滑动抖动问题分析

  • CoordinatorLayout向上fling滚动无法被外部中断

    • CoordinatorLayout和子View的联动时通过CoordinatorLayout.Behavior实现的,AppBarLayout使用的Behavior继承了HeaderBehavior<AppBarLayout>。
    • 问题就在这里。HeaderBehavior的onTouchEvent中使用Scroller实现了fling操作,但是没有通过NestedScrolling API对外开放,也就说一旦HeaderBehavior的fling动作形成,无法由外部主动中断。
  • RecyclerView向下fling滚动
    • 与AppBarLayout同层级的RecyclerView可以通过升级过的NestedScrolling API对AppBarLayout产生影响,比如RecyclerView向下fling时滑动到item 0之后,如果AppBarLayout可以滑动时会给AppBarLayout施加一个同样向下的fling动作,以此形成一个连贯的下滑fling。
    • 那么问题来了。当HeaderBehavior产生的向上的fling没有结束时,RecyclerView又送来向下的fling,抖动就产生了。
  • 分析一下HeaderBehavior
    • 当检测到down事件时, 取消了mScroller的运行(如果它正在scroll的话)。这里因为要访问父类(其实是父类的父类)的 mScroller变量。
    • 然后通过反射拿到mScroller变量,在onInterceptTouchEvent拦截事件中,当手指离开的时候,则停止overScroller动画效果。
    • 然后通过反射拿到flingRunnable变量,在onInterceptTouchEvent拦截事件中,当手指离开的时候,则需要remove所有的flingRunnable。

03.自定义AppBarLayout.Behavior说明

  • AppBarLayout简单说明

    • AppBarLayout是一个vertical的LinearLayout,实现了很多material的概念,主要是跟滑动相关的。AppBarLayout的子view需要提供layout_scrollFlags参数。AppBarLayout和CoordinatorLayout强相关,一般作为CoordinatorLayout的子类,配套使用。 按我的理解,AppBarLayout内部有2种view,一种可滑出(屏幕),另一种不可滑出,根据app:layout_scrollFlags区分。一般上边放可滑出的下边放不可滑出的。
  • AppBarLayout.Behavior部分方法说明
    • onInterceptTouchEvent():是否拦截触摸事件
    • onTouchEvent():处理触摸事件
    • layoutDependsOn():确定使用Behavior的View要依赖的View的类型
    • onDependentViewChanged():当被依赖的View状态改变时回调
    • onDependentViewRemoved():当被依赖的View移除时回调
    • onMeasureChild():测量使用Behavior的View尺寸
    • onLayoutChild():确定使用Behavior的View位置
    • onStartNestedScroll():嵌套滑动开始(ACTION_DOWN),确定Behavior是否要监听此次事件
    • onStopNestedScroll():嵌套滑动结束(ACTION_UP或ACTION_CANCEL)
    • onNestedScroll():嵌套滑动进行中,要监听的子 View的滑动事件已经被消费
    • onNestedPreScroll():嵌套滑动进行中,要监听的子 View将要滑动,滑动事件即将被消费(但最终被谁消费,可以通过代码控制)
    • onNestedFling():要监听的子 View在快速滑动中
    • onNestedPreFling():要监听的子View即将快速滑动

04.CoordinatorLayout滑动抖动解决

  • 通过反射拿到flingRunnable变量,注意这里我判断了一下27和28版本的问题,27及以下是mFlingRunnable,28及以上是flingRunnable,一定要注意这个问题。

    /**
    * 反射获取私有的flingRunnable 属性,考虑support 28以后变量名修改的问题
    * @return Field
    * @throws NoSuchFieldException
    */
    private Field getFlingRunnableField() throws NoSuchFieldException {
    Class<?> superclass = this.getClass().getSuperclass();
    try {
    // support design 27及一下版本
    Class<?> headerBehaviorType = null;
    if (superclass != null) {
    String name = superclass.getName();
    LogUtil.d("AppBarLayout.Behavior父类",name);
    headerBehaviorType = superclass.getSuperclass();
    }
    if (headerBehaviorType != null) {
    String name = headerBehaviorType.getName();
    LogUtil.d("AppBarLayout.Behavior父类的父类1",name);
    return headerBehaviorType.getDeclaredField("mFlingRunnable");
    }else {
    return null;
    }
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    // 可能是28及以上版本
    Class<?> headerBehaviorType = superclass.getSuperclass().getSuperclass();
    if (headerBehaviorType != null) {
    String name = headerBehaviorType.getName();
    LogUtil.d("AppBarLayout.Behavior父类的父类2",name);
    return headerBehaviorType.getDeclaredField("flingRunnable");
    } else {
    return null;
    }
    }
    }
  • 通过反射拿到scroller变量,和上面类似。
    /**
    * 反射获取私有的scroller 属性,考虑support 28以后变量名修改的问题
    * @return Field
    * @throws NoSuchFieldException
    */
    private Field getScrollerField() throws NoSuchFieldException {
    Class<?> superclass = this.getClass().getSuperclass();
    try {
    // support design 27及一下版本
    Class<?> headerBehaviorType = null;
    if (superclass != null) {
    headerBehaviorType = superclass.getSuperclass();
    }
    if (headerBehaviorType != null) {
    return headerBehaviorType.getDeclaredField("mScroller");
    }else {
    return null;
    }
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    // 可能是28及以上版本
    Class<?> headerBehaviorType = superclass.getSuperclass().getSuperclass();
    if (headerBehaviorType != null) {
    return headerBehaviorType.getDeclaredField("scroller");
    }else {
    return null;
    }
    }
    }
  • 然后在onInterceptTouchEvent拦截事件里处理逻辑。当手指触摸屏幕的时候停止fling事件
    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
    LogUtil.d(TAG, "onInterceptTouchEvent:" + child.getTotalScrollRange());
    shouldBlockNestedScroll = isFlinging;
    switch (ev.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
    //手指触摸屏幕的时候停止fling事件
    stopAppbarLayoutFling(child);
    break;
    default:
    break;
    }
    return super.onInterceptTouchEvent(parent, child, ev);
    } /**
    * 停止appbarLayout的fling事件
    * @param appBarLayout
    */
    private void stopAppbarLayoutFling(AppBarLayout appBarLayout) {
    //通过反射拿到HeaderBehavior中的flingRunnable变量
    try {
    Field flingRunnableField = getFlingRunnableField();
    Field scrollerField = getScrollerField();
    if (flingRunnableField != null) {
    flingRunnableField.setAccessible(true);
    }
    if (scrollerField != null) {
    scrollerField.setAccessible(true);
    }
    Runnable flingRunnable = null;
    if (flingRunnableField != null) {
    flingRunnable = (Runnable) flingRunnableField.get(this);
    }
    OverScroller overScroller = null;
    if (scrollerField != null) {
    overScroller = (OverScroller) scrollerField.get(this);
    }
    //下面是关键点
    if (flingRunnable != null) {
    LogUtil.d(TAG, "存在flingRunnable");
    appBarLayout.removeCallbacks(flingRunnable);
    flingRunnableField.set(this, null);
    }
    if (overScroller != null && !overScroller.isFinished()) {
    overScroller.abortAnimation();
    }
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    }
  • 完整版本的代码如下所示
    /**
    * <pre>
    * @author yangchong
    * blog : https://github.com/yangchong211
    * time : 2019/03/13
    * desc : 自定义Behavior
    * revise: 解决appbarLayout若干问题
    * 1)快速滑动appbarLayout会出现回弹
    * 2)快速滑动appbarLayout到折叠状态下,立马下滑,会出现抖动的问题
    * 3)滑动appbarLayout,无法通过手指按下让其停止滑动
    * </pre>
    */
    public class AppBarLayoutBehavior extends AppBarLayout.Behavior { private static final String TAG = "AppbarLayoutBehavior";
    private static final int TYPE_FLING = 1;
    private boolean isFlinging;
    private boolean shouldBlockNestedScroll; public AppBarLayoutBehavior(Context context, AttributeSet attrs) {
    super(context, attrs);
    } @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
    LogUtil.d(TAG, "onInterceptTouchEvent:" + child.getTotalScrollRange());
    shouldBlockNestedScroll = isFlinging;
    switch (ev.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
    //手指触摸屏幕的时候停止fling事件
    stopAppbarLayoutFling(child);
    break;
    default:
    break;
    }
    return super.onInterceptTouchEvent(parent, child, ev);
    } /**
    * 反射获取私有的flingRunnable 属性,考虑support 28以后变量名修改的问题
    * @return Field
    * @throws NoSuchFieldException
    */
    private Field getFlingRunnableField() throws NoSuchFieldException {
    Class<?> superclass = this.getClass().getSuperclass();
    try {
    // support design 27及一下版本
    Class<?> headerBehaviorType = null;
    if (superclass != null) {
    headerBehaviorType = superclass.getSuperclass();
    }
    if (headerBehaviorType != null) {
    return headerBehaviorType.getDeclaredField("mFlingRunnable");
    }else {
    return null;
    }
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    // 可能是28及以上版本
    Class<?> headerBehaviorType = superclass.getSuperclass().getSuperclass();
    if (headerBehaviorType != null) {
    return headerBehaviorType.getDeclaredField("flingRunnable");
    } else {
    return null;
    }
    }
    } /**
    * 反射获取私有的scroller 属性,考虑support 28以后变量名修改的问题
    * @return Field
    * @throws NoSuchFieldException
    */
    private Field getScrollerField() throws NoSuchFieldException {
    Class<?> superclass = this.getClass().getSuperclass();
    try {
    // support design 27及一下版本
    Class<?> headerBehaviorType = null;
    if (superclass != null) {
    headerBehaviorType = superclass.getSuperclass();
    }
    if (headerBehaviorType != null) {
    return headerBehaviorType.getDeclaredField("mScroller");
    }else {
    return null;
    }
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    // 可能是28及以上版本
    Class<?> headerBehaviorType = superclass.getSuperclass().getSuperclass();
    if (headerBehaviorType != null) {
    return headerBehaviorType.getDeclaredField("scroller");
    }else {
    return null;
    }
    }
    } /**
    * 停止appbarLayout的fling事件
    * @param appBarLayout
    */
    private void stopAppbarLayoutFling(AppBarLayout appBarLayout) {
    //通过反射拿到HeaderBehavior中的flingRunnable变量
    try {
    Field flingRunnableField = getFlingRunnableField();
    Field scrollerField = getScrollerField();
    if (flingRunnableField != null) {
    flingRunnableField.setAccessible(true);
    }
    if (scrollerField != null) {
    scrollerField.setAccessible(true);
    }
    Runnable flingRunnable = null;
    if (flingRunnableField != null) {
    flingRunnable = (Runnable) flingRunnableField.get(this);
    }
    OverScroller overScroller = (OverScroller) scrollerField.get(this);
    if (flingRunnable != null) {
    LogUtil.d(TAG, "存在flingRunnable");
    appBarLayout.removeCallbacks(flingRunnable);
    flingRunnableField.set(this, null);
    }
    if (overScroller != null && !overScroller.isFinished()) {
    overScroller.abortAnimation();
    }
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    } @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
    View directTargetChild, View target,
    int nestedScrollAxes, int type) {
    LogUtil.d(TAG, "onStartNestedScroll");
    stopAppbarLayoutFling(child);
    return super.onStartNestedScroll(parent, child, directTargetChild, target,
    nestedScrollAxes, type);
    } @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout,
    AppBarLayout child, View target,
    int dx, int dy, int[] consumed, int type) {
    LogUtil.d(TAG, "onNestedPreScroll:" + child.getTotalScrollRange()
    + " ,dx:" + dx + " ,dy:" + dy + " ,type:" + type);
    //type返回1时,表示当前target处于非touch的滑动,
    //该bug的引起是因为appbar在滑动时,CoordinatorLayout内的实现NestedScrollingChild2接口的滑动
    //子类还未结束其自身的fling
    //所以这里监听子类的非touch时的滑动,然后block掉滑动事件传递给AppBarLayout
    if (type == TYPE_FLING) {
    isFlinging = true;
    }
    if (!shouldBlockNestedScroll) {
    super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
    }
    } @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
    View target, int dxConsumed, int dyConsumed, int
    dxUnconsumed, int dyUnconsumed, int type) {
    LogUtil.d(TAG, "onNestedScroll: target:" + target.getClass() + " ,"
    + child.getTotalScrollRange() + " ,dxConsumed:"
    + dxConsumed + " ,dyConsumed:" + dyConsumed + " " + ",type:" + type);
    if (!shouldBlockNestedScroll) {
    super.onNestedScroll(coordinatorLayout, child, target, dxConsumed,
    dyConsumed, dxUnconsumed, dyUnconsumed, type);
    }
    } @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl,
    View target, int type) {
    LogUtil.d(TAG, "onStopNestedScroll");
    super.onStopNestedScroll(coordinatorLayout, abl, target, type);
    isFlinging = false;
    shouldBlockNestedScroll = false;
    } private static class LogUtil{
    static void d(String tag, String string){
    Log.d(tag,string);
    }
    } }

05.案例测试是否根本问题

  • 代码如下所示

    <android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    app:layout_behavior="org.yczbj.ycrefreshview.sticky.AppBarLayoutBehavior"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
  • 发现最终解决问题

06.参考案例

其他介绍

01.关于博客汇总链接

02.关于我的博客

项目地址:https://github.com/yangchong211/YCRefreshView

CoordinatorLayout滑动抖动问题的更多相关文章

  1. 关于CoordinatorLayout与Behavior的一点分析

    Behavior是Android新出的Design库里新增的布局概念.Behavior只有是CoordinatorLayout的直接子View才有意义.可以为任何View添加一个Behavior.Be ...

  2. APICloud框架——总结一下最近开发APP遇到的一些问题 (三)

    ajax报错 Uncaught DOMException: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 需要在服务器环境下 ...

  3. CoordinatorLayout 嵌套 AppBarLayout RecyclerView ,通过代码控制,使得CoordinatorLayout 自动滑动到tab置顶的位置

    有两个方式可以实现 一:调用AppBarLayout,设置间距 val behavior = (appbar_layout.getLayoutParams() as CoordinatorLayout ...

  4. IOS中position:fixed吸底时的滑动出现抖动的解决方案

    H5方法: //吸顶头部 .header{ width:100%; height:50px; position:fixed; top:0px; } //main滑动区域 .main{ width:10 ...

  5. onTouchEvent中,跟随手指滑动的view出现抖动

    在这次实践中,它抖动得不正常,太不正常,太抖. 其实是我代码上出现了问题,记录一下. 我是怎么设置滑动的呢? 通过改变view的margin. 然而我在onTouchEvent中怎么控制它滑动的大小呢 ...

  6. 使ie6的漂浮栏滑动右侧滚动条的时候不抖动

    body {_background-attachment: fixed;}html {_background-image: url(about:blank);}

  7. 【知识必备】一文让你搞懂design设计的CoordinatorLayout和AppbarLayout联动,让Design设计更简单~

    一.写在前面 其实博主在之前已经对design包的各个控件都做了博文说明,无奈个人觉得理解不够深入,所以有了这篇更加深入的介绍,希望各位看官拍砖~ 二.从是什么开始 1.首先我们得知道Coordina ...

  8. 实现滑动可固定header以及页面刷新

    用到的布局:SwiperRefreshLayout,AppBarLayout,ToolBar,CollapsingToolbarLayout,CoordinatorLayout 布局 <?xml ...

  9. 协调者布局:CoordinatorLayout

    layout_scrollFlag属性: scroll:需要哪个View滚动就需要设置该属性: exitUntilCollapsed:向上推动屏幕的时候滑动的部分折叠起来,只有下滑到最低端的时候折叠部 ...

  10. 使用 CoordinatorLayout 实现复杂联动效果

    GitHub 地址已更新: unixzii / android-FancyBehaviorDemo CoordinatorLayout 是 Google 在 Design Support 包中提供的一 ...

随机推荐

  1. OGG_Linux_x64_BigData启动ggsci时报错:error while loading shared libraries: libjvm.so: cannot open shared object file: No such file or directory

    问题描述 [root@hadoop03 ggs]$ ./ggsci ./ggsci: error while loading shared libraries: libjvm.so: cannot o ...

  2. JS LeetCode 1423. 可获得的最大点数简单题解

    壹 ❀ 引 最近也是浮躁的很,一篇redux的文章写了三千多字才算写了一半...写的泪目了.还是刷刷算法静下心,顺带记录下算法做题过程吧.今天的题来自LeetCode每日打卡,题目出自LeetCode ...

  3. NC51032 八数码

    题目链接 题目 题目描述 The 15-puzzle has been around for over 100 years; even if you don't know it by that nam ...

  4. STM32F407VET6烧录出现flash download failed target dll has been cancelled

    今天在通过stlink烧录一个长时间未用的STM32F407VET6 Black Board的时候, 出现错误 Internal command error Flash download failed ...

  5. 以二进制文件安装K8S之创建CA根证书

    为etcd和Kubernetes服务启用基于CA认证的安全机制,需要CA证书进行配置. 如果组织能够提供统一的CA认证中心,则直接使用组织颁发的CA证书即可.如果没有统一的CA认证中心,则可以通过颁发 ...

  6. MySQL单表能存储多少条数据?

    MySQL是中小型网站普遍使用的数据库之一,然而,很多人并不清楚MySQL到底能支持多大的数据量,甚至对它产生误解.MySQL单表的上限,主要与操作系统支持的最大文件大小有关.事实上MySQL能承受的 ...

  7. 【LeetCode二叉树#15】二叉搜索树中的众数(递归中序遍历)

    二叉搜索树中的众数 力扣题目链接(opens new window) 给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素). 假定 BST 有如下定义: 结点左子树 ...

  8. 1-Django框架简介以及基本操作

    安装 注意:安装的磁盘目录,以及后续通过Django创建目录的时候,不要出现中文,否则会出现预料之外的错误 建议:禁止套娃,即不要在A项目中创建B项目 # 如果不指定版本号,默认最新版 pip ins ...

  9. DataGear 制作自适应任意屏幕尺寸的数据可视化看板

    DataGear 即支持以编写HTML.JavaScript.CSS源码的源码模式制作看板,也支持直观可见.友好快捷的可视模式制作看板. 本文将通过看板可视编辑模式提供的网格布局和样式设置功能,介绍如 ...

  10. 【Azure 应用服务】App Service 进入后台管理(Kudu)页面,因为文件过多而显示不全的问题

    问题描述 当App Service 应用发布到Azure上后,需要查看某一个日志文件时候,如果一个文件夹中的文件内容过多,则会出现错误消息提醒: Full error Message: There a ...