手机屏幕越来越大,android页面布局也越来越复杂,仅仅使用一个listview或scrollview是远远不够的,所以很多情况下需要嵌套滑动

Android的嵌套滑动一直是新手朋友很蛋疼纠结的事,这里就几种解决方式作出自己的见解

1.ListView setHeader

即将页面其余布局放入ListViewHeader中,这是最简单有效的方式,也是Android5.0嵌套机制之前官方建议的实现方式

但是这种方式耦合性太强,一个很直观的例子,一篇文章下面通常需要嵌入评论列表,而评论系统通常是单独的Fragment,反向将布局插入子Fragment中,不直观,也是反逻辑的

并且一个布局中需要多个ListView时,这种方式便无能为力

2.ScrollView + ListView

ListView外层包裹ScrollView,然后将ListView的高度设置为与items的高度总和相同

第一种是重写ListView的onMeasure方法

另一种是通过Adapter遍历所有item,计算ListView的高度,如这种实现方式 http://www.cnblogs.com/zhwl/p/3333585.html

但是,如非必要,请不要这样写

因为这会使ListView的重用机制失效,adapter会一次性创建所有item,如果数据量过大,容易引起OOM

3.重写ScrollView

重写ScrollView的onInterceptTouchEvent方法,拦截子ListView的滑动事件,在需要其滑动时返回false

这种方式符合逻辑,推荐这种方式

//以下方式基于Android最新的嵌套机制

//如果对此机制不了解的,可以看看 http://blog.csdn.net/chen930724/article/details/50307193

4.CoordinatorLayout + RecyclerView

关于RecyclerView:

第一次见到RecyclerView时,就觉得这个东西可以替换所有的数据容器了,拔插式的设计方式,可以适应更复杂的设计方式

不了解的朋友可以去这里看看 http://www.cnblogs.com/shen-hua/p/5818172.html 非常详细

但是RecyclerView并不支持item点击和长按事件,需要在Adapter中自己监听

需要引入V7库,CoordinatorLayout 必须作为父类,并且可以使用V7库中的Toolbar、FloatingActionButton等非常好用的系统控件

更详细的资料见 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0717/3196.html

但是测试的时候发现,滑动父控件时并没有惯性滑动(Fling)效果,对于我这种强迫症非常不能忍

5.NestedScrollView + RecyclerView

NestedScrollView 是V4库中新添加的 实现了NestedScrollingParent和NestedScrollingChild的控件

网上是有这样的实现方式,但是,实验后很不理想,依旧不能流畅滑动

但是有人通过一行代码解决了,mRecyclerView.setNestedScrollingEnabled(false);

But,千万不要这样做

这和第二种实现方式的情况是类似的,RecycleView会自动扩展高度,Adapter中所有的Item都会创建

6.自己写一个FlingNestedScrollView + RecyclerView

当然,你也可以拉到最下面复制代码

解决的问题:可以自我嵌套,顺畅的滑动效果,SwipeRefreshLayout,ViewPager嵌套均不存在问题

Android新的嵌套滑动机制本质:

1.scroll

滑动发起者是NestedScrollingChild(下简称子控件),有滑动需求时询问NestedScrollingParent(下简称父控件)是否需要消费【(子)dispatchNestedPreScroll->(父)onNestedPreScroll】*

父控件消费后将剩余交还给子控件,子控件消费后再将剩余交给父控件【(子)dispatchNestedScroll->(父)onNestedScroll】

如果你觉得他们问来问去太麻烦,可以省去第二步

2.fling

子控件有惯性滑动需求时询问父控件,父控件选择截获或者不截获【(子)dispatchNestedPreFling->(父)onNestedPreFling】*

然后子控件选择是否消费,不消费再传回父控件【(子)dispatchNestedFling->(父)onNestedFling】

核心便是这8个函数,但是第二步通常不需要,一次交互即可,所以最最核心便只有4个函数,这么捋下来是不是觉得很简单呢

实现思路:

父控件在布局时遍历所有子控件,找到所有实现了NestedScrollingChild的子控件,方便操作

1.scroll

核心函数:nestedScrollBy(int dy)

子控件询问父控件时,父控件判断当前子控件是否能够滑动,能够滑动则不消费,否则父控件滑动自己

2.fling

所有的惯性滑动都由父控件来处理,由ScrollerCompat来计算滑动位置

以下为全部代码,竟可能加了一些注释,只实现了纵向滑动,有需要的也可以自己完善

因为没有作更充分的测试,所以可能存在没有考虑到的bug,希望共同完善吧

package com.simple.carpool.view;import android.content.Context;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ScrollerCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;/*** Created by xl on 2016/11/22.*/public class FlingNestedScrollView extends ViewGroup implements NestedScrollingParent, NestedScrollingChild, ScrollingView {private NestedScrollingParentHelper parentHelper;private NestedScrollingChildHelper childHelper;//手势判断private GestureDetectorCompat mGestureDetector;//惯性滑动计算private ScrollerCompat scroller;private View nestedScrollingView;private OnScrollChangeListener listener;private final int[] mScrollConsumed = new int[2];private final int[] mNestedOffsets = new int[2];private int windowOffsetY = 0;private List<View> nestedScrollingChildList = new ArrayList<>();private int currentFlingY = 0;public FlingNestedScrollView(Context context) {this(context, null);}public FlingNestedScrollView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public FlingNestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setOverScrollMode(View.OVER_SCROLL_NEVER);parentHelper = new NestedScrollingParentHelper(this);childHelper = new NestedScrollingChildHelper(this);setNestedScrollingEnabled(true);scroller = ScrollerCompat.create(context);mGestureDetector = new GestureDetectorCompat(getContext(), new MyGestureListener());}public void setOnScrollChangeListener(OnScrollChangeListener listener) {this.listener = listener;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);View view = getChildAt(0);if (view == null || !(view instanceof ViewGroup)) {throw new IllegalArgumentException("must have one child ViewGroup");}measureChild(view, widthMeasureSpec, heightMeasureSpec);ViewGroup viewGroup = (ViewGroup) view;for (int i = 0; i < viewGroup.getChildCount(); i++) {View child = viewGroup.getChildAt(i);measureChild(child, widthMeasureSpec, heightMeasureSpec);}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {View view = getChildAt(0);if (view == null || !(view instanceof ViewGroup)) {throw new IllegalArgumentException("must have one child ViewGroup");}ViewGroup viewGroup = (ViewGroup) view;int parentHeight = getMeasuredHeight();int top = 0;int width = r - l;for (int i = 0; i < viewGroup.getChildCount(); i++) {View child = viewGroup.getChildAt(i);LayoutParams layoutParams = child.getLayoutParams();if (layoutParams.height == LayoutParams.MATCH_PARENT) {layoutParams.height = parentHeight;} else {int childMeasuredHeight = child.getMeasuredHeight();layoutParams.height = childMeasuredHeight;}child.setLayoutParams(layoutParams);child.layout(0, top, width, top + layoutParams.height);top += layoutParams.height;}viewGroup.layout(0, 0, width, top);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();View view = getChildAt(0);if (view == null || !(view instanceof ViewGroup)) {throw new IllegalArgumentException("must have one child ViewGroup");}nestedScrollingChildList.clear();ViewGroup viewGroup = (ViewGroup) view;getAllNestedChildren(viewGroup);Log.i("carpool", "Children count:" + nestedScrollingChildList.size());Collections.sort(nestedScrollingChildList, new SortComparator());}private void onScrollChange(View view, int scroll) {if(listener != null) listener.onScrollChange(view, scroll);}//获取本身滑动高度private int getScrollHeight() {int height = getHeight() - getPaddingTop() - getPaddingBottom();int bottom = getChildAt(0).getHeight();return bottom - height;}//获取所有可以滑动的NestedScrollChildprivate void getAllNestedChildren(ViewGroup viewGroup) {for (int i = 0, len = viewGroup.getChildCount(); i < len; i++) {View child = viewGroup.getChildAt(i);if(ViewCompat.isNestedScrollingEnabled(child) && child instanceof ScrollingView) {nestedScrollingChildList.add(child);} else if(child instanceof ViewGroup) {getAllNestedChildren((ViewGroup) child);}}}//子view是否显示,不显示则不计算private boolean isShownInVertical(View view) {if(!view.isShown()) return false;int[] position = new int[2];view.getLocationOnScreen(position);int screenLeft = position[0];int screenRight = screenLeft + view.getWidth();if(screenRight < getWidth() / 3 || screenLeft > getWidth() / 3 * 2) {return false;}return true;}//滑动子view,如果子view正在滑动则返回,否则会引起循环调用,nestedChild会“歘”一下就滑到底部private void scrollViewYTo(View view, int y) {onScrollChange(view, y);if(view == nestedScrollingView) return;int currentScroll = getViewScrollY(view);view.scrollBy(0, y - currentScroll);}private void scrollViewYBy(View view, int dy) {onScrollChange(view, getViewScrollY(view) + dy);
//        if(view == nestedScrollingView) return;view.scrollBy(0, dy);}private void scrollYTo(int dy) {onScrollChange(this, dy);super.scrollTo(0, dy);}private void scrollYBy(int dy) {int scrollYTo = getScrollY() + dy;onScrollChange(this, scrollYTo);super.scrollBy(0, dy);}//获取view相对于root的坐标private int getYWithView(View view, View parent) {int[] position = new int[2];view.getLocationOnScreen(position);int viewTop = position[1];parent.getLocationOnScreen(position);int parentTop = position[1];return viewTop - parentTop;}//获取view相对于root的topprivate int getTopWithView(View view, View parent) {int parentY = getYWithView(view, parent);return getViewScrollY(parent) + parentY;}//获取子view当前滑动位置private int getViewScrollY(View view) {if(view instanceof ScrollingView) {return ((ScrollingView)view).computeVerticalScrollOffset();} else {return view.getScrollY();}}//------------------------------------scrollBy(因为在滑动过程中,scrollingview不知道自己的最大高度,所以不能使用全局坐标!)----------//全局滑动,包括子scrollingview//true:消费,false:未消费private boolean nestedScrollBy(int dy) {if(dy == 0) return true;int scrollY = getScrollY();int scrollMax = getScrollHeight();//有正在滑动的子viewView currentScrollChild = getCurrentScrollChild(dy);if(currentScrollChild != null) {//如果两者相等,返回false,让其自己消费,以免循环调用//按逻辑来说,还应该判断currentScrollChild是否是nestedScrollingView的子控件//但是子控件的scrollBy好像并不会再次触发onPreNestedScroll,暂未发现bug//所以就先这么写吧if(currentScrollChild == nestedScrollingView) {onScrollChange(currentScrollChild, getViewScrollY(currentScrollChild) + dy);return false;} else {scrollViewYBy(currentScrollChild, dy);return true;}}//将会滑动到子viewView nextScrollChild = getNextScrollChild(dy);if(nextScrollChild != null) {scrollYBy(getYWithView(nextScrollChild, this));return true;}//仅滑动自己if(dy < 0 && scrollY == 0) return false;if(dy > 0 && scrollY == scrollMax) return false;if(scrollY + dy <= 0) scrollYTo(0);else if(scrollY + dy >= scrollMax) scrollYTo(scrollMax);else scrollYBy(dy);return true;}//获取正在滑动的viewprivate View getCurrentScrollChild(int dy) {for(View child: nestedScrollingChildList) {int childY = getYWithView(child, this);if(childY == 0 && isShownInVertical(child)) {if(canScrollVertically(child, dy)) return child;}}return null;}//获取下一个将会自滑动的view//consumed: index0: parentscroll, index1: viewscrollprivate View getNextScrollChild(int dy) {View view = null;int viewY = 0;for(View child: nestedScrollingChildList) {int childY = getYWithView(child, this);if(dy > 0 && childY > 0) {view = child;viewY = childY;break;} else if(dy < 0 && childY < 0) {view  = child;viewY = childY;}}if(view == null) return null;if(!canScrollVertically(view, dy)) return null;if((dy < 0 && dy - viewY < 0) || (dy > 0 && dy - viewY > 0)) {return view;}return null;}//子View是否能够滑动private boolean canScrollVertically(View view, int scrollY) {if(scrollY > 0 && ViewCompat.canScrollVertically(view, 1)) {return true;} else if(scrollY < 0 && ViewCompat.canScrollVertically(view, -1)) {return true;}return false;}//-----------------------------------scrollBy(end)------------------------------------------//惯性滑动private void flingY(int velocityY) {if (getChildCount() > 0) {currentFlingY = 0;scroller.fling(0, 0, 0, velocityY, 0, 0, Integer.MIN_VALUE,Integer.MAX_VALUE);invalidate();}}@Overridepublic void computeScroll() {if (scroller.computeScrollOffset()) {int y = scroller.getCurrY();nestedScrollBy(y - currentFlingY);currentFlingY = y;invalidate();}}@Overridepublic void scrollBy(int x, int y) {nestedScrollBy(y);}//---------------------------NestedScrollParent----------------------------------
    @Overridepublic boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {Log.i("carpool", "start");if(!nestedScrollingChildList.contains(target)) {onFinishInflate();}nestedScrollingView = target;scroller.abortAnimation();return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;}@Overridepublic void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {Log.i("carpool", "accept");parentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);}@Overridepublic void onStopNestedScroll(View target) {Log.i("carpool", "stop");if(nestedScrollingView == target) nestedScrollingView = null;parentHelper.onStopNestedScroll(target);}@Overridepublic void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {}@Overridepublic void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {boolean isConsumed = nestedScrollBy(dy);if(isConsumed) {consumed[0] = 0;consumed[1] = dy;}}@Overridepublic boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {return false;}@Overridepublic boolean onNestedPreFling(View target, float velocityX, float velocityY) {Log.i("carpool", "fling");flingY((int) velocityY);return true;}@Overridepublic int getNestedScrollAxes() {return parentHelper.getNestedScrollAxes();}//---------------------------NestedScrollParent(End)-----------------------------//---------------------------NestedScrollChild----------------------------------
@Overridepublic void setNestedScrollingEnabled(boolean enabled) {childHelper.setNestedScrollingEnabled(enabled);}@Overridepublic boolean isNestedScrollingEnabled() {return childHelper.isNestedScrollingEnabled();}@Overridepublic boolean startNestedScroll(int axes) {return childHelper.startNestedScroll(axes);}@Overridepublic void stopNestedScroll() {childHelper.stopNestedScroll();}@Overridepublic boolean hasNestedScrollingParent() {return childHelper.hasNestedScrollingParent();}@Overridepublic boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);}@Overridepublic boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);}@Overridepublic boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {return childHelper.dispatchNestedFling(velocityX, velocityY, consumed);}@Overridepublic boolean dispatchNestedPreFling(float velocityX, float velocityY) {return childHelper.dispatchNestedPreFling(velocityX, velocityY);}//---------------------------NestedScrollChild(End)-----------------------------//---------------------------ScrollingView---------------------------
    @Overridepublic int computeHorizontalScrollRange() {return 0;}public int computeHorizontalScrollOffset() {return 0;}public int computeHorizontalScrollExtent() {return 0;}public int computeVerticalScrollRange() {return getChildAt(0).getHeight();}public int computeVerticalScrollOffset() {return getScrollY();}public int computeVerticalScrollExtent() {return getHeight() - getPaddingTop() - getPaddingBottom();}//--------------------------ScrollingView(End)------------------------------
@Overridepublic boolean onTouchEvent(MotionEvent event) {event.offsetLocation(0, windowOffsetY);mGestureDetector.onTouchEvent(event);if(event.getAction() == MotionEvent.ACTION_UP) {windowOffsetY = 0;stopNestedScroll();}return true;}private class MyGestureListener implements GestureDetector.OnGestureListener {@Overridepublic boolean onDown(MotionEvent e) {scroller.abortAnimation();startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);windowOffsetY = 0;return true;}@Overridepublic void onShowPress(MotionEvent e) {}@Overridepublic boolean onSingleTapUp(MotionEvent e) {return false;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {int dx = (int) distanceX;int dy = (int) distanceY;dispatchNestedPreScroll(dx, dy, mScrollConsumed, mNestedOffsets);windowOffsetY += mNestedOffsets[1];dy -= mScrollConsumed[1];if(dy == 0) return true;else nestedScrollBy(dy);return true;}@Overridepublic void onLongPress(MotionEvent e) {}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {if(!dispatchNestedPreFling(-velocityX, -velocityY)) {dispatchNestedFling(-velocityX, -velocityY, true);flingY((int) -velocityY);}return true;}}public class SortComparator implements Comparator<View> {@Overridepublic int compare(View lhs, View rhs) {return getTopWithView(lhs, FlingNestedScrollView.this) - getTopWithView(rhs, FlingNestedScrollView.this);}}public interface OnScrollChangeListener {void onScrollChange(View view, int scroll);}
}

转载于:https://www.cnblogs.com/xlqwe/p/6183492.html

NestedScrollView平滑滑动嵌套 Fling相关推荐

  1. android GridView 在TV上,上下翻页的时候平滑滑动的实现

    应该做过android tv开发的同学都知道,在TV上使用GridView的时候,如果焦点上下移动的时候,如果移动到在屏幕上可见的第一行或者最后一行的时候,如果再继续上下移动, 的话,是比较生硬呆滞的 ...

  2. Android之ViewPager中包含ViewFlipper时实现双滑动嵌套解决父控件干扰问题

    想要实现一个在ViewPager中嵌一个ViewFlipper实现类似豌豆荚中的广告栏功能,试了好久,发现父控件总是干扰子控件的操作,表现为手势滑动ViewFlipper中的图片时,ViewPager ...

  3. Android 实现嵌套滑动

    前言 Android实现简易版滑动 上次文章中实现了简易的ScrollerView滑动,但实际使用中许多场景都会涉及到嵌套滑动,在今天的博文中我们基于上次的ScrollLayout来进一步实现嵌套滑动 ...

  4. 干货:五分钟带你看懂NestedScrolling嵌套滑动机制

    Android NestedScrolling嵌套滑动机制 Android在发布5.0之后加入了嵌套滑动机制NestedScrolling,为嵌套滑动提供了更方便的处理方案.在此对嵌套滑动机制进行详细 ...

  5. NestedScrollView、RecycleView、ViewPager 嵌套常见问题

    在开发中我们经常会用到 NestedScrollView 和 RecycleView,一般情况下这两种布局是不需要进行嵌套的,很多情况下 RecycleView 就可以自行解决,但是毕竟是一般情况,因 ...

  6. android控件的touch事件_聊聊Android嵌套滑动

    聊聊Android嵌套滑动 最近工作中遇到了需求是使用 Bottom-Sheet 交互的弹窗,使用了 design 包里面的 CoordinatorLayout 和 BottomSheetBehavi ...

  7. 使用Android SwipeRefreshLayout了解Android的嵌套滑动机制

    SwipeRefreshLayout 是在Android Support Library, revision 19.1.0加入到support v4库中的一个下拉刷新控件,关于android的下拉刷新 ...

  8. 一种嵌套滑动冲突的解决方案

    非嵌套滑动 | 嵌套滑动 相比起非嵌套滑动的自定义分发事件的方案,嵌套滑动冲突有比较成熟的 Google 解决方案:NestedScrolling . 三层嵌套的滑动冲突 UI 层级如下: 最外层(底 ...

  9. Android开发之解决NestedScrollView滑动监听兼容低版本的方法

    NestedScrollView的滑动监听目前仅限api23及以上,为了兼容低版本如下自定义方法 可以自定义NestedScrollView即可如下: package cn.net.gfan.worl ...

最新文章

  1. 收藏 不显示删除回复显示所有回复显示星级回复显示得分回复 有损脑健康的七种坏习惯...
  2. RabbitMQ的Work能者多劳模式
  3. apt-get的更新源
  4. mysql升级准备工作
  5. win7 lnk 图标丢失——图片缓存问题
  6. 对话即平台:利用人工智能以及云平台打造你的智能机器人
  7. Snagit--高难度、多功能截图,有了它截图不求人!
  8. fish设置环境变量
  9. 【知易行难】RS485组网连接示意图
  10. python xlwt库的详细函数介绍,xlwt
  11. GraphQL学习过程应该是这样的 1
  12. arm linux内核启动过程,ARM64的启动过程之(一):内核第一个脚印
  13. java 图像特效之黑白 浮雕和底片
  14. 虚拟现实产业发展白皮书(2019年)发布
  15. 合泰单片机触摸例程_合泰单片机iic例程
  16. 大厂面试机器学习算法(0):特征工程 | 数据预处理
  17. 小程序统一服务消息接口
  18. 最全支付系统设计包含:账户,对账,风控......
  19. 和菜鸟一起学linux总线驱动之初识USB设备描述符
  20. 3D打印机(Prusa I2)DIY经验分享(Part I)

热门文章

  1. BZOJ 1061费用流
  2. Struts2 入门修行第一天 | 小节二
  3. vue.js学习笔记(1)
  4. Android 开发中的View事件监听机制
  5. 结对子作业 四则运算 V2.0
  6. C#编译器选项(目标平台)
  7. Unix/Linux环境C编程入门教程(39) shell命令之系统管理
  8. Dreamer 3.0 支持json、xml、文件上传
  9. 异常机制及throw与throws的区别 (z)
  10. 使用 UpdatePanel 【转by Dorian Deng】