2.实现思路
继承LinearLayout,设置方向为垂直
控件中有两个ScrollView,至于为什么要使用ScrollView,主要是因为内容超过一页时省去自己处理滑动
关键是事件分发处理。监听两个ScrollView的滑动事件,当第一页滑动到底部时,再向上拖动时,拦截事件,判断距离,超过设定值时,滑动到第二页,否则回弹;同理,当第二页滑动到顶部时,再向下拖动时,拦截事件,判断距离,超过设定值时,滑动到第一页,否则回弹(还有很多细节需要结合代码讲解)
关于回弹和滑动换页使用的是Scroller,对于Scroller的使用,本文不做过多解释
3.实现
3.1重写ScrollView
根据实现思路,我们需要监听ScrollView是否滑动到顶部和底部,但是ScrollView的setOnScrollChangeListener()方法在api23才添加。主要是重写ScrollViewonScrollChanged(int l, int t, int oldl, int oldt)方法。
l:当前水平方向滚动值,和getScrollX()相等
t:当前竖直方向滚动值,和getScrollY()相等
oldl:上一次水平滚动值
oldt:上一次竖直滚动值
监听接口:
public interface OnScrollEndListener { void scrollToBottom(View view); void scrollToTop(View view); void scrollToMiddle(View view); }onScrollChanged方法
@Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if(t == 0){if (mOnScrollBottomListener != null) {mOnScrollBottomListener.scrollToTop(this);} } else if(t + getMeasuredHeight() >= getChildAt(0).getMeasuredHeight()){if (mOnScrollBottomListener != null) {mOnScrollBottomListener.scrollToBottom(this);} } else {if (mOnScrollBottomListener != null) {mOnScrollBottomListener.scrollToMiddle(this);} } }3.2重写onMeasure方法、page的获取与设置
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /*** 显示调用第二个自孩子的测量方法,不然尺寸有可能为0*/ View child2 = getChildAt(1); if (child2 != null) {child2.measure(widthMeasureSpec, heightMeasureSpec); } }在onFinishInflate中初始化两个页面
@Override protected void onFinishInflate() { super.onFinishInflate(); if(getChildCount() == 2){View child1 = getChildAt(0);if (child1 instanceof ScrollEndScrollView){scrollView1 = (ScrollEndScrollView) child1;}View child2 = getChildAt(1);if(child2 instanceof ScrollEndScrollView){scrollView2 = (ScrollEndScrollView) child2;} } initEvent(); }为两个页面设置滑动监听
private ScrollEndScrollView.OnScrollEndListener scrollEndListener = new ScrollEndScrollView.OnScrollEndListener() { @Override public void scrollToBottom(View view) {if(view == scrollView1){isToBotttom = true;} } @Override public void scrollToTop(View view) {if(view == scrollView2){isToTop = true;} } @Override public void scrollToMiddle(View view) {if(view == scrollView1){isToBotttom = false;}if(view == scrollView2){isToTop = false;} } };3.3Scroller使用的几步
@Override public void computeScroll() { super.computeScroll(); //先判断mScroller滚动是否完成 if (mScroller.computeScrollOffset()) {//这里调用View的scrollTo()完成实际的滚动scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//必须调用该方法,否则不一定能看到滚动效果postInvalidate(); } }辅助方法
//调用此方法设置滚动的相对偏移 public void smoothScrollBy(int dx, int dy) { //设置mScroller的滚动偏移量 mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, Math.max(300, Math.abs(dy))); invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果 }//调用此方法滚动到目标位置 public void smoothScrollTo(int fx, int fy) { int dx = fx - mScroller.getFinalX(); int dy = fy - mScroller.getFinalY(); smoothScrollBy(dx, dy); }3.4事件分发
@Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); int yPosition = (int) ev.getY(); switch (action) {case MotionEvent.ACTION_DOWN:mScroller.abortAnimation();mLastY = yPosition;mMoveY = 0;break;case MotionEvent.ACTION_MOVE:mMoveY = (mLastY - yPosition);mLastY = yPosition;if(isToBotttom){ if(mMoveY > 0){ //向上 smoothScrollBy(0, mMoveY); return true; } else { //向下 if(mScroller.getFinalY() != 0){//这是出于第一页和第二页显示连接处if(getScrollY() + mMoveY > 0){smoothScrollBy(0, mMoveY);return true;} else{smoothScrollTo(0, 0);return super.dispatchTouchEvent(ev);} } }}else if(isToTop){ if(mMoveY < 0){ //向下 smoothScrollBy(0, mMoveY); return true; } else { //向上 if(mScroller.getFinalY() < scrollView1.getHeight()){//这是出于第一页和第二页显示连接处smoothScrollBy(0, mMoveY);return true; } else {smoothScrollTo(0, scrollView1.getHeight());return super.dispatchTouchEvent(ev); } }}//处理快速滑动时两页覆盖问题if(pageIndex == 0){ smoothScrollTo(0, 0);} else if(pageIndex == 1){ smoothScrollTo(0, scrollView1.getHeight());}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if(isToBotttom){ if(Math.abs(getScrollY()) > TO_NEXT_PAGE_HEIGHT){ //移动到第二页 pageIndex = 1; smoothScrollTo(0, scrollView1.getHeight()); isToBotttom = false; isToTop = true; } else { //回弹 smoothScrollBy(0, -mScroller.getFinalY()); }} else if(isToTop){ if(scrollView1.getHeight() - getScrollY() > TO_NEXT_PAGE_HEIGHT){ //移动到第一页 pageIndex = 0; smoothScrollTo(0, 0); isToBotttom = true; isToTop = false; } else { //回弹 smoothScrollTo(0, scrollView1.getHeight()); }}break;default:break; } return super.dispatchTouchEvent(ev); }4.总结