posted @ by avenwu
手势处理在自定义控件中随处可见,诸如下拉,侧滑,加载更多等等,本文将利用手势相关的知识,来实现一个类似微信文章中过渡滑动交互。
下滑的处理在很早之前也写过几篇分别是简单的下拉,侧滑,原理基本类似,主要依赖的是offsetTopBotom,offsetLeftRight,Scroller;
在处理OverScroll的时候需要正确决定手势是否被拦截用于,滑动视图,或者分发至系统时间响应,根据内容区视图是否能滑动也有细节上的处理,这一块可以参照SwpieRefreshLayout的处理,即提供一个检测方法canChildScroolUp,针对内容view的类型单独处理;
下面是最终实现的效果:
首先考虑一下,这个效果的UI组成,当滑动到顶部时可以继续滑动,展示底部的视图;因此滑动主要解决的是视图在Y轴上的偏移:
if (count > 1) { mForegroundView = getChildAt(1); mBackgroundView = getChildAt(0); } else { mForegroundView = getChildAt(0); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); log("onMeasure: count=" + count); if (count > 1) { mForegroundView = getChildAt(1); mBackgroundView = getChildAt(0); } else { mForegroundView = getChildAt(0); } if (mBackgroundView != null) { measureChildWithMargins(mBackgroundView, widthMeasureSpec, 0, heightMeasureSpec, 0); } if (mForegroundView != null) { measureChildWithMargins(mForegroundView, widthMeasureSpec, 0, heightMeasureSpec, 0); mHeightLimitMax = (int) (mForegroundView.getMeasuredHeight() * mMaxOverPercent); if (mForegroundView instanceof ScrollIntercept) { ((ScrollIntercept) mForegroundView).attach(this); } } if (mForegroundView != null && mBackgroundView != null) { mForegroundView.bringToFront(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mBackgroundView != null) { MarginLayoutParams layoutParams = (MarginLayoutParams) mBackgroundView.getLayoutParams(); int left = layoutParams.leftMargin + getPaddingLeft(); int top = layoutParams.topMargin + getPaddingTop(); int right = left + mBackgroundView.getMeasuredWidth(); int bottom = top + mBackgroundView.getMeasuredHeight(); mBackgroundView.layout(left, top, right, bottom); log("onLayout, mBackgroudView = " + mBackgroundView.toString() + ", l=" + left + ",t=" + top + ", r=" + right + ",b=" + bottom); } if (mForegroundView != null) { MarginLayoutParams layoutParams = (MarginLayoutParams) mForegroundView.getLayoutParams(); int left = layoutParams.leftMargin + getPaddingLeft(); int top = layoutParams.topMargin + getPaddingTop(); int right = left + mForegroundView.getMeasuredWidth(); int bottom = top + mForegroundView.getMeasuredHeight(); mForegroundView.layout(left, top, right, bottom); log("onLayout, mForegroundView = " + mForegroundView.toString() + ", l=" + left + ",t=" + top + ", r=" + right + ",b=" + bottom); } }
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { log("onInterceptTouchEvent:" + ev.toString()); if (!isEnabled() || canChildScrollUp()) { //make child layout to determine the scroll state if (mForegroundView instanceof ScrollIntercept && (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP)) { log("try tryScrollToTop"); ((ScrollIntercept) mForegroundView).tryScrollToTop(this); } return false; } final int action = ev.getAction(); if (action != MotionEvent.ACTION_DOWN && isSliding) return true; switch (action) { case MotionEvent.ACTION_DOWN: isSliding = false; mX = ev.getX(); mY = ev.getY(); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: isSliding = false; break; case MotionEvent.ACTION_MOVE: float dy = ev.getY() - mY; log("onInterceptTouchEvent, canChildScrollUp=" + canChildScrollUp()); if (dy > mTouchSlop && !isSliding) { mY = ev.getY(); mX = ev.getX(); isSliding = true; log("dy=" + dy); } break; } log("onInterceptTouchEvent, isSliding=" + isSliding); return isSliding; }
@Override public boolean onTouchEvent(MotionEvent event) { log("onTouchEvent:" + event.toString()); if (!isEnabled() || canChildScrollUp()) { // Fail fast if we're not in a state where a swipe is possible return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mX = event.getX(); mY = event.getY(); isSliding = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); } case MotionEvent.ACTION_MOVE: float offsetX = event.getX() - mX; float offsetY = event.getY() - mY; log("move, offsetX=" + offsetX + ", offsetY=" + offsetY + ", mTouchSlop=" + mTouchSlop); if (isSliding) { if (offsetY > 0) { scrollY((int) offsetY); mX = event.getX(); mY = event.getY(); } else { return false; } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } int currentY = mForegroundView.getTop(); int gap = (int) (mForegroundView.getHeight() * mOverPercent); if (currentY + event.getY() - mY >= gap / 2) { int duration = (int) (Math.abs(0 - currentY + 0.5f) / mForegroundView.getHeight() * 1000); log("duration=" + duration); mScroller.startScroll(0, currentY, 0, 0 - currentY, duration); } else { int duration = (int) (Math.abs(-gap - currentY + 0.5f) / mForegroundView.getHeight() * 1000); log("duration=" + duration); mScroller.startScroll(0, currentY, 0, -gap - currentY, duration); } isSliding = false; invalidate(); return false; } return true; }
@Override public void tryScrollToTop(ScrollOver layout) { log("tryScrollToTop:" + mScrolledY); if (mScrolledY != 0) { mLayout.smooth2Top(); mScrolledY = 0; } } @Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { log("overScrollBy: deltaX=" + deltaX + ", deltaY=" + deltaY + ", scrollX=" + scrollX + ", scrollRangeX=" + scrollRangeX); final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); if (mLayout != null) { mScrolledY += deltaY; mLayout.scrollY(-deltaY); } return returnValue; }
在处理手势的时候进场会遇到效果不一致,这时候通过适量的log,可以更好地帮助开发者理解问题的出处; 完整的代码可以参考在github上的地址,欢迎各种star,for,issue:
https://github.com/avenwu/overscrolllayout
作者: Aven
出处: Make your OverScrollLayout
本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.