用途
我们经常会看到scrollview带下拉上拉功能,提供页面刷新。现在我们总结以下。
效果与原理
我们知道,scrollView可以提供滚动功能,如果超出屏幕就可以滚动显示,当滚动到顶部或者底部的时候切换拖动。
当滚动到底部时,又有两种情况,一种是手没有离开,在拖动,这样的话就是一般的拖动方法。另外一种是滑动之后松手,屏幕开始滚动,滚动到边界,这种情况不是正常的上拉下拉,但是我们可以给一个弹性,给人一种惯性弹出屏幕的感觉。安卓的scrollview自带效果是滚动到底端时,会闪出一道神奇的蓝光,然并卵,所以我们自己加了这个惯性弹出效果。
- 拖动效果
- 滚动效果
实现流程
在以下两种情况下出现弹性效果
- 1.自然滚动超越屏幕
复写函数:onOverScrolled
STATUS_SCROLLOVER
if 手离开屏幕 && Y轴有滚动
——–只做滚出然后弹回的动画效果autoScrollAnimation
- 2.拖动超越屏幕(或者外层容器)
复写函数:onTouchEvent
if 滚动距离+scrollview外边框高度==内部控件的实际高度(即滚动到最底下时拖动) || y轴没有滚动(长度不够滚动时拖动)
——–上拉 STATUS_DRAG
—————拉动+松手回调+弹回动画reductAnimation
——–下拉 STATUS_PULL
—————拉动+松手回调+弹回动画reductAnimation
- 动画完毕恢复状态STATUS_NORMAL
关键问题
- 1.关于控件移动的方法
本例程控件移动使用控件的setTranslationY方法
- 2.判断由滚动切换到拖动需要的数据
获取滚动距离 getScrollY()
scrollview外边框高度 getHeight() 此处需要区别
scrollview的高度是屏幕内可见的范围
内部控件的实际高度 内部控件.getMeasuredHeight();
scrollview中真正长的其实是scrollview内部的控件
关键代码
复写onOverScrolled监听滑动过界
/**
* 自然滚动超越屏幕
* @param scrollX
* @param scrollY
* @param clampedX
* @param clampedY
*/
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
Log.i("ElasticScrollView1", "scrollY " + scrollY + " clampedY " + clampedY + " " + mIsTouch);
//1.手未接触,滚动到底
if(!mIsTouch && clampedY){
Log.i("ElasticScrollView1", "scroll over!!!");
mCurrentStatus = STATUS_SCROLLOVER;
//向下滚动距离
int dataLength = Math.abs(mVelocityY/50);
//向下滚动时间
int dataTime = Math.abs(mVelocityY/50);
if(scrollY < 0 && mPullDownEnable){ //向下滚动 滑到顶端
autoScrollAnimation(mChildView, dataLength, dataTime);
}else if(scrollY > 0 && mDragUpEnable){ //向上滚动 滑到底端
autoScrollAnimation(mChildView, -dataLength, dataTime);
}
}
mScrollViewMeasuedHeight = mChildView.getMeasuredHeight();
}
复写onTouchEvent监听拖动事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
mYScrollLengh = this.getScrollY();
switch (action){
case MotionEvent.ACTION_DOWN:
mIsTouch = true; //触摸标志位置位
mDownY = (int) ev.getY(); //按下Y轴基准
mViewHeight = this.getHeight(); //获取当前ScrollView的高度
mScrollViewMeasuedHeight = mChildView.getMeasuredHeight();//获取内层控件的高度
mCurrentStatus = STATUS_NORMAL;
mYDrahLengh = 0; //Y轴拖动距离复位
break;
case MotionEvent.ACTION_MOVE:
mMoveY = (int) ev.getY();
mYDrahLengh = mDownY - mMoveY; //获取Y轴真实拖动距离
mYDrahLengh = mYDrahLengh/2; //转换为比例拖动距离
//上拉
if(mYDrahLengh > 0 && mDragUpEnable) {
//两种情况,切换到拉动
//1.mYScrollLengh == 0 scrollview中内容较短,没有滑动起来 时拉动
//2.mYScrollLengh + mViewHeight == mScrollViewMeasuedHeight
//滚动,并且滚动到头 时拉动
//当滑动距离+scrollView == scrollView内部控件高度,说明滚动到头,切换为
拉动
if (mYScrollLengh + mViewHeight == mScrollViewMeasuedHeight ||
(mYScrollLengh == 0)) { //上拉
if(mCurrentStatus == STATUS_NORMAL){
mStartMoveY = mMoveY;
}
mCurrentStatus = STATUS_DRAG;
//根据拖到距离设置控件实际移动距离
mMoveLength = (mStartMoveY - mMoveY)/2;
//移动控件
mChildView.setTranslationY(-mMoveLength);
}
}else if(mYDrahLengh < 0 && mPullDownEnable){ //下拉
//如果滚动距离为0 则切换为下拉状态
if(mYScrollLengh == 0) {
if(mCurrentStatus == STATUS_NORMAL){
mStartMoveY = mMoveY;
}
mCurrentStatus = STATUS_PULL;
mMoveLength = (mStartMoveY - mMoveY)/2;
mChildView.setTranslationY(-mMoveLength);
}
}
break;
case MotionEvent.ACTION_UP:
if(mCurrentStatus == STATUS_DRAG){
reductAnimation(mChildView, mMoveLength);
}else if(mCurrentStatus == STATUS_PULL){
reductAnimation(mChildView, mMoveLength);
}
mIsTouch = false;
break;
}
return super.onTouchEvent(ev);
}