关于分段ScrollView的探索

本来是要做一个类似淘宝和京东的产品详情页的那个两段ScrollView的效果,但是最后并没有完全实现,等后面有时间了再继续折腾一下看能不能完全模仿吧。

还是老样子,先看看效果图

这里写图片描述

如果只是达到我这个效果,方法就比较简单了,简单到什么地步呢?整个类只有250多行代码。。。
虽然简单,但是我么还是要知道怎么实现的对吧,所以我还是简单的讲一下我当时对这个问题的思考吧,如果要实现淘宝京东那样的效果,大家还是去github搜搜吧,应该早就有很多种方式实现了。

其实我当时的想法很简单,就是在ScrollView里找到一个分界的视图,用它来决定ScrollView滚动的效果,实现假的“分页”。那么首先要怎么样找到这个分界的视图呢?老办法,就像我前面一篇文章《打造更好的透明(沉浸?)状态栏》 里一样,用findViewByTag找到分界视图,得到它的高度、在布局中的位置等信息,这个就需要大家在布局的时候自己去控制了。

光找到这个分界视图还不行,还要找一个“第二页”视图,为什么需要这个呢?因为当我们滑动到 “第二页”后,往上拉到分页视图的时候,继续拉一段距离,还要往上翻页到“第一页”,同样的,我们找到它也是为了得到它的高度、在布局中的位置等信息。

拿到这些信息后,就可以根据当前ScrollView的scrollY来判断该显示哪一页了。

往上拉的时候,如果scrollY大于了分页视图在布局中的Y值(这个Y值是偏移过的,代码里面可以看出来,偏移的目的是为了往上拉的时候,分界的地方固定在屏幕底部,往下拉的时候把分界的地方固定在屏幕顶部,大家去看看京东和淘宝的效果就知道了),这个时候如果是手指在移动,那就正常随手指移动,如果是由于惯性,那就要把ScrollView回滚到分页视图在布局中的Y值,即不让滚到下一页;当回滚完成后,用户在手动往上拉,当手指抬起的时候,上拉的距离大于一个阈值,我们就认为可以滑动到下一页了。

往下拉的时候,这个时候就要比较scrollY和“第二页”在布局中的Y值了,因为这个时候分页的焦点固定在屏幕顶部。当scrollY小于了“第二页”在布局中的Y值,同样的,如果是用户在干预,那就随手指滑动,如果是惯性,那就要把ScrollView回滚到第二页”视图在布局中的Y值,即不让滚到上一页;当回滚完成后,用户在手动往下拉,当手指抬起的时候,下拉的距离大于一个阈值,我们就认为可以滑动到上一页了。

原理应该算是讲述明白了,看起来还有点多,其实并不是,只是不好描述。到这里呢,应该就是贴代码了,对吧?那我们就贴代码吧 O(∩_∩)O

PagingScrollView

package com.ykbjson.demo.customview.scrollview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ScrollView;

import com.drivingassisstantHouse.library.tools.SLog;
import com.ykbjson.demo.R;


/**
 * 包名:com.ykbjson.demo.customview.scrollview
 * 描述:两段ScrollView
 * 创建者:yankebin
 * 日期:2016/12/27
 */

public class PagingScrollView extends ScrollView {
    public interface OnNextPageLoadCallback {
        void onNextPageLoad(boolean isFirstLoad);
    }

    private final String TAG_PAGE_DIVIDER = getResources().getString(R.string.tag_pageDivider);
    private final String TAG_SECOND_PAGE = getResources().getString(R.string.tag_pageSecondView);
    private static final int SCROLL_DURATION = 500;
    private static final int PAGE_NO_FIRST = 1;
    private static final int PAGE_NO_SECOND = PAGE_NO_FIRST + 1;

    private float downY;
    private View dividerView;
    private View secondView;
    private CustomScroller scroller;
    private int dividerY;
    private int secondTop;
    private int dividerHeight;
    private int pageNo = PAGE_NO_FIRST;
    private boolean isInMove;
    private int downScrollY;
    private OnNextPageLoadCallback callback;
    private boolean isFirstLoadNextPage = true;


    public PagingScrollView(Context context) {
        this(context, null);
    }

    public PagingScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PagingScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        scroller = new CustomScroller(context);
        scroller.init(SCROLL_DURATION, this);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        dividerView = findViewWithTag(TAG_PAGE_DIVIDER);
        secondView = findViewWithTag(TAG_SECOND_PAGE);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (!checkHasPaging()) {
            return;
        }
        dividerHeight = dividerView.getMeasuredHeight();
        int dividerTop = getChildTop(dividerView, dividerView.getTop());
        dividerY = dividerTop+dividerHeight - getHeight() ;
        secondTop = getChildTop(secondView, secondView.getTop());
         if (secondTop <= getHeight() || dividerTop <= getHeight()) {
            throw new IllegalArgumentException("firstPage's height must be more than the 
            parent's height");
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            downScrollY = getScrollY();
            downY = ev.getRawY();
        } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            isInMove = true;
        } else if (ev.getAction() == MotionEvent.ACTION_UP
                || ev.getAction() == MotionEvent.ACTION_CANCEL) {
            isInMove = false;
        }

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (checkHasPaging()) {
            if (ev.getAction() == MotionEvent.ACTION_UP
                    || ev.getAction() == MotionEvent.ACTION_CANCEL) {
                if (scroller.isFinished()&&checkNeedHandle() && handleScroll((int) (ev.getRawY() - downY))) {
                    return true;
                }
            }
        }
        //解决滚动时用户又干预引起的异常
        return !scroller.isFinished() ||super.onTouchEvent(ev);
    }

    public void setOnNextPageLoadCallback(OnNextPageLoadCallback callback) {
        this.callback = callback;
    }

    /**
     * 检测是否需要分页
     *
     * @return
     */
    private boolean checkHasPaging() {
        return null != dividerView && null != secondView;
    }

    /**
     * 检测是否需要处理
     *
     * @return
     */
    private boolean checkNeedHandle() {
        int currentScrollY = getScrollY();
        SLog.e("currentScrollY : " + currentScrollY + " downScrollY : " + downScrollY + " pageNo : " + pageNo);
        //内容滚向尾部
        if (currentScrollY > downScrollY) {
            if (pageNo == 1) {
                if (currentScrollY > dividerY) {
                    return true;
                }
            }
        }
        //内容滚向头部
        else if (currentScrollY < downScrollY) {
            if (pageNo == 2) {
                if (currentScrollY < secondTop) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 根据y方向坐标平滑滑动
     *
     * @param y
     */

    private void smoothScrollTo(int y) {
        smoothScrollBy(y - getScrollY());
    }

    /**
     * 根据y方向距离平滑滑动
     *
     * @param dstY
     */
    private void smoothScrollBy(int dstY) {
        scroller.abortAnimation();
        scroller.startScroll(0, getScrollY(), 0, dstY);
    }

    /**
     * 处理滚动
     *
     * @param distance
     */
    private boolean handleScroll(int distance) {
        switch (pageNo) {
            case PAGE_NO_FIRST:
                if (distance < 0) {
                    if (distance <= -dividerHeight) {
                        smoothScrollTo(secondTop);
                        pageNo = PAGE_NO_SECOND;
                        if (null != callback) {
                            callback.onNextPageLoad(isFirstLoadNextPage);
                        }
                        if (isFirstLoadNextPage) {
                            isFirstLoadNextPage = false;
                        }
                    } else {
                        smoothScrollTo(dividerY);
                    }
                    return true;
                }
                break;
            case PAGE_NO_SECOND:
                if (distance > 0) {
                    if (distance >= dividerHeight) {
                        smoothScrollTo(dividerY);
                        pageNo = PAGE_NO_FIRST;
                    } else {
                        smoothScrollTo(secondTop);
                    }
                    return true;
                }
                break;
        }

        return false;
    }

    /**
     * 找到view距离顶部的距离
     *
     * @param view 当前视图
     * @param top  当前视图的顶部距离
     * @return
     */
    private int getChildTop(View view, int top) {
        ViewParent parent = view.getParent();
        if (null == parent || this == parent || !(parent instanceof View)) {
            return top;
        }
        ViewGroup parentView = (ViewGroup) parent;
        top += parentView.getTop();
        return getChildTop(parentView, top);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (!checkHasPaging()) {
            return;
        }

        if (t > oldt) {
            if (pageNo == 2) {
                return;
            }
            if (t > dividerY) {
                if (!isInMove) {
                    smoothScrollTo(dividerY);
                }
            }
        } else if (t < oldt) {
            if (pageNo == 1) {
                return;
            }
            if (t < secondTop) {
                if (!isInMove) {
                    smoothScrollTo(secondTop);
                }
            }
        }
    }
}

有木有骗你们?真的很简单,只有250多行代码。但是别急,我还要贴一份代码,原因在后面说

CustomScroller

package com.ykbjson.demo.customview.scrollview;

import android.content.Context;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.OverScroller;
import android.widget.ScrollView;

import java.lang.reflect.Field;

/**
 * 包名:com.ykbjson.demo.customview.scrollview
 * 描述:自定义Scroller
 * 创建者:yankebin
 * 日期:2016/12/27
 */

public class CustomScroller extends OverScroller {
    private int mScrollDuration;

    public CustomScroller(Context context) {
        this(context, new AccelerateDecelerateInterpolator());
    }

    public CustomScroller(Context context, Interpolator interpolator) {
        super(context, interpolator);
    }

    public CustomScroller(Context context, Interpolator interpolator, float bounceCoefficientX, float bounceCoefficientY) {
        super(context, interpolator, bounceCoefficientX, bounceCoefficientY);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy) {
        super.startScroll(startX, startY, dx, dy, mScrollDuration);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        super.startScroll(startX, startY, dx, dy, mScrollDuration);
    }

    public void setScrollDuration(int scrollDuration) {
        this.mScrollDuration = scrollDuration;
    }

    public void init(int scrollDuration, ScrollView scrollView) {
        setScrollDuration(scrollDuration);
        try {
            Field mScroller = ScrollView.class.getDeclaredField("mScroller");
            mScroller.setAccessible(true);
            mScroller.set(scrollView, this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

为什么要贴这个呢?因为当我在回滚ScrollView和翻页的时候,我尝试了ScrollView自带smoothScrollTo方法,无论怎么尝试,回滚的时候都是一下就就过去了,我去看了下他的Scroller的源码,不论多远的距离,滚动的时间默认都是250毫秒,这是个常量,所以没办法(我也只是粗看,结论可能不是对的哦),我只能用反射的方式把ScrollView自带的Scroller给替换了,自己按需设置滚动时长。

好了,该说的也说了,代码也贴完了,等后面有时间,弄出来完全相仿京东淘宝的效果后再给大家

                        贴——————代——————码   
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值