NestedScrollView平滑滑动嵌套 Fling
手機屏幕越來越大,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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces-743D - Ch
- 下一篇: 【从零开始学BPM,Day2】默认表单开