关于分段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的探索的主要内容,如果未能解决你的问题,请参考以下文章