仿造淘宝上拉加载详情控件
控件结构如下
逻辑处理
父控件Layout在静止的时候可能的滚动距离只有2种情况
显示topScrollView,滚动情况为未滚动的(0,0)位置
显示bottomScrollView时滚动为(0,mHeight)
子scrollView滚动情况
子view处于可滚动状态时不做处理。当topScrollView滚动到底部或者bottomScrollView滚动到顶部时,此时再向上(或者向下)拖动时,就应该滚动父Layout了。这其中父控件跟随手指滑动的方法有很多,本文采用的是view自带的scrollBy()方法,在dispatchTouchEvent()方法的Move函数中获取手指每次移动的距离,并且滚动父视图。
手指释放时父视图回滚的操作,这里配合前面的scroll来用Scroller来处理,它自带平滑滚动的效果,只要知道开始位置和结束位置(或者说滚动距离),就可以平滑的滚动到目标位置,而目标位置如上2所提到的2种情况。
代码部分
为了监听scrollView手指fling的时候的滑动状况,自定义了一个scrollView 值修改了onScrollChanged()方法加入了一个监听
public class FlingScrollView extends ScrollView {
private OnScrollListener mListener;
public void setOnScrollListener(OnScrollListener listener) {
this.mListener = listener;
}
public FlingScrollView(Context context) {
this(context, null);
}
public FlingScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlingScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mListener != null) {
mListener.onScrollAndFling(this, t);
}
}
public interface OnScrollListener {
void onScrollAndFling(ViewGroup v, int scrollY);
}
}
控件整体代码
/**
* 仿淘宝上拉查看详情模块
* Created by lqh on 2016/5/8.
*/
public class ScrollContainerView extends RelativeLayout {
private static final int SPEED = 500;
//mCurIndex = 0,当scrollY > mHeight*1/4f时,自动滚动到下一页
//mCurIndex = 1,当scrollY < mHeight*3/4f时,自动滚动到上一页
private static final float AUTO_SCROLL = 1 / 4f;
private int mWidth, mHeight;
private ViewGroup mBottomView;
private FlingScrollView mTopScrollView;
private FlingScrollView mBottomScrollView;
private int mAutoUpScrollY;
private int mTopScrollSize;//topView滚动到底部时的滚动距离
private boolean canPullUp, canPullDown;
//* 1.标记自动回滚的mScroller操作
//* 2.标记正在滚动
private boolean mIsScroller = false;
private int mCurIndex = 0;
//该值用于限制从子视图滚动过渡到父视图滚动,需要在子视图滚动完毕后父视图处于可滚动状态时,再次按下才能拖拽
private int mEvent = 0;
private int mScrollSize;
private int mLastY;
private Scroller mScroller;
private boolean mIsViewChange;
private boolean mShouldChangeView;
private VelocityTracker mTracker;
private ViewChangeListener mViewChangeListener;
public ScrollContainerView(Context context) {
this(context, null);
}
public ScrollContainerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrollContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mScroller = new Scroller(getContext());
mTracker = VelocityTracker.obtain();
}
public void setViewChangeListener(ViewChangeListener listener) {
this.mViewChangeListener = listener;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTopScrollView = (FlingScrollView) getChildAt(0);
mBottomView = (ViewGroup) getChildAt(1);
if (mBottomView instanceof ScrollView)
mBottomScrollView = (FlingScrollView) mBottomView;
else
mBottomScrollView = (FlingScrollView) mBottomView.getChildAt(1);
mTopScrollView.setOnScrollListener(mTopScrollListener);
mBottomScrollView.setOnScrollListener(mBottomScrollListener);
}
private FlingScrollView.OnScrollListener mTopScrollListener = new FlingScrollView.OnScrollListener() {
@Override
public void onScrollAndFling(ViewGroup v, int scrollY) {
if (mTopScrollSize == 0)
mTopScrollSize = mTopScrollView.getChildAt(0).getMeasuredHeight() - mHeight;
canPullUp = scrollY == mTopScrollSize && mCurIndex == 0;
}
};
private FlingScrollView.OnScrollListener mBottomScrollListener = new FlingScrollView.OnScrollListener() {
@Override
public void onScrollAndFling(ViewGroup v, int scrollY) {
canPullDown = scrollY == 0 && mCurIndex == 1;
}
};
/**
* 布局两个子View 高度必须设置match_parent,他们的高度和父控件是一致的
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mAutoUpScrollY = (int) (AUTO_SCROLL * h);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mTopScrollView.layout(0, 0, mWidth, mHeight);
mBottomView.layout(0, mHeight, mWidth, 2 * mHeight);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//如果当前视图正在滚动,则屏蔽所有对该视图的操作
if (mIsScroller)
return true;
int y = (int) ev.getY(0);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mTracker.clear();
mTracker.addMovement(ev);
mEvent = 0;
mLastY = y;
//获取当前父视图滚动的距离,显示第一页时为0,第二页时为mHeight
mScrollSize = mCurIndex * mHeight;
break;
case MotionEvent.ACTION_MOVE:
mTracker.addMovement(ev);
int distance = y - mLastY;
shouldScroll(distance);
mLastY = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTracker.addMovement(ev);
mTracker.computeCurrentVelocity(1000);
autoScroll();
break;
}
//父视图处于静止状态时,传递事件给子View;当父视图处于拖拽状态时,不传递事件给子View
if (getScrollY() == 0 || getScrollY() == mHeight) {
super.dispatchTouchEvent(ev);
}
return true;
}
/**
* 是否滚动父视图
*
* @param distance 手指move的距离
*/
private void shouldScroll(int distance) {
if (canPullUp && mEvent == 0 && mCurIndex == 0) {
//当滑动不是单一方向时,即先上拉后又下拉,则判定为不需要更改视图
mShouldChangeView = distance <= 0;
//为实现拖拽效果,此处父视图滚动的距离为手指滑动距离的1/2
mScrollSize -= distance / 2;
if (mScrollSize < 0)
mScrollSize = 0;
if (mScrollSize > mHeight / 2)
mScrollSize = mHeight / 2;
scrollTo(0, mScrollSize);
canPullDown = true;
return;
}
if (canPullDown && mEvent == 0 && mCurIndex == 1) {
mShouldChangeView = distance >= 0;
mScrollSize -= distance / 2;
if (mScrollSize > mHeight)
mScrollSize = mHeight;
if (mScrollSize < mHeight / 2)
mScrollSize = mHeight / 2;
scrollTo(0, mScrollSize);
canPullUp = true;
return;
}
//若此时为子视图在滚动,则event设置为-1,使得它即使满足canPullUp也无法进入到拖拽父视图操作,
// 只有当再次down事件才可以拖拽
mEvent = -1;
}
/**
* 手指离开后处理视图复位,
*/
private void autoScroll() {
//获取手指释放时父视图滚动的距离,依此来判断接下来要回滚的位置
final int scrollY = getScrollY();
final int ySpeed = (int) Math.abs(mTracker.getYVelocity());
if (scrollY == 0 || scrollY == mHeight)
return;
if (scrollY <= 10) {
scrollTo(0, 0);
return;
}
if (scrollY >= mHeight - 10) {
scrollTo(0, mHeight);
return;
}
if (!mShouldChangeView) {
scrollBack(scrollY);
return;
}
if (ySpeed < SPEED) {
// if (mCurIndex == 0 && scrollY > mAutoUpScrollY)和
// if (mCurIndex == 1 && scrollY < mHeight - mAutoUpScrollY)的合并条件语句
if ((2 * mCurIndex - 1) * scrollY <= mCurIndex * mHeight - mAutoUpScrollY)
scrollNext(scrollY);
else
scrollBack(scrollY);
} else {
scrollNext(scrollY);
}
}
private void scrollNext(int scrollY) {
mCurIndex = 1 - mCurIndex;
mIsViewChange = true;
mScroller.startScroll(0, scrollY, 0, mCurIndex * mHeight - scrollY, 500);
invalidate();
}
private void scrollBack(int scrollY) {
mIsViewChange = false;
mScroller.startScroll(0, scrollY, 0, mCurIndex * mHeight - scrollY, 500);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
if (!mIsScroller)
mIsScroller = true;
scrollTo(0, mScroller.getCurrY());
if (mViewChangeListener != null)
mViewChangeListener.scrollChanged(mScroller.getCurrY());
postInvalidate();
} else {
if (mIsScroller) {
if (mIsViewChange && mViewChangeListener != null) {
mViewChangeListener.showView(mCurIndex);
}
mIsViewChange = false;
mIsScroller = false;
}
}
}
/**
* 页面切换的监听
*/
public interface ViewChangeListener {
void showView(int curViewIndex);
void scrollChanged(int scrollSize);
}