关于分段ScrollView的探索

Posted ykb19891230

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于分段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给替换了,自己按需设置滚动时长。

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

                        贴——————代——————码   

以上是关于关于分段ScrollView的探索的主要内容,如果未能解决你的问题,请参考以下文章

每次按下按钮时如何使 scrollView 移动?

ScrollView Autolayout 使用标签但不使用自定义控件

360视频:分段球面投影SSP

360视频:分段球面投影SSP

Android 获取View的原图,并以高度分段裁剪存储

SwiftUI 分段控件可定制