前言
IOS拖拽回弹给用户的体验不得不赞。然后Android原生的API各种不支持,于是乎出现的很多仿IOS拖拽回弹的Android控件ReboundScrollView
,比起原生的ScrollView
,体验上好了很多,但是还是不尽人意:RebounScrollView
里的内容超出屏幕时,你想一次性在显示完ScrollView
里的内容紧跟着拽出屏幕,你发现你做不到,臣妾也做不到!于是乎,我想我是不是可以试试?下面分享一个让你也可以有IOS拖拽回弹般体验的ReboundFrameLayout。既然Android原生的ScrollView
做不到,那我干脆舍弃不用,我们自己做类似ScrollView
的滑动,这样方便我们在滑动到顶部(或底部)时可以继续向下(或向上滑动)。
这里链接两个大神写的拖拽回弹控件:
1、ReboundScrollView 仿IOS 拖拽回弹
2、 Android上实现仿IOS弹性ScrollView
我的思路
思路图
截屏效果
立即体验
扫描以下二维码下载体验App(从0.2.3
版本开始,体验App内嵌版本更新检测功能):
JSCKit库传送门:https://github.com/JustinRoom/JSCKit
源码简析
利用OverScroller方式实现滚动。
ReboundFrameLayout.java关键逻辑代码(请参阅代码里的详细注释):
- 重写
onTouchEvent(MotionEvent event)
方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (getChildCount() == 0)
return super.onTouchEvent(event);
// trackerEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastTouchY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float curTouchY = event.getY();
float dy = mLastTouchY - curTouchY;
int deltaY = (int) (dy);
mLastTouchY = curTouchY;
//执行滑动处理
executeScroll(getChildAt(0), deltaY);
break;
case MotionEvent.ACTION_UP:
//计算滑动速度
// int initialVelocity = getYVelocity();
//释放滑动事件跟踪器
// recycleVelocityTracker();
// if (Math.abs(initialVelocity) > mMinimumVelocity) {
// mOverScroller.fling(0, getScrollY(), 0, - initialVelocity, 0, 0, -getHeight() / 2, getHeight() / 2);
// invalidate();
// } else {
// rebound();
// }
rebound();
break;
default:
break;
}
return true;
}
- 滑动处理:
/**
* 滑动处理
* <p>
* * @param target target
*
* @param deltaY deltaY
*/
private void executeScroll(View target, int deltaY) {
//为了方便下面的描述,我们用content代表target的内容,即:content=target的内容。
//content下滑的高度
int verticalOffset = target.getScrollY();
int scrollDistance = 0;
int scrollY = -getScrollY();
if (deltaY < 0) {//向下滑动
//target底部已经向上离开ReboundFrameLayout的底部
if (scrollY < 0) {
scrollDistance = Math.max(deltaY, scrollY);
scrollBy(0, (int) (scrollDistance * scrollRatio));
deltaY = deltaY - scrollDistance;
if (deltaY >= 0)
return;
}
scrollDistance = Math.max(deltaY, -verticalOffset);
target.scrollBy(0, scrollDistance);
deltaY = deltaY - scrollDistance;
if (deltaY >= 0)
return;
scrollBy(0, (int) (deltaY * scrollRatio));
} else if (deltaY > 0) {//向上滑动
//content在target中的可见区域
Rect rect = new Rect();
target.getLocalVisibleRect(rect);
//content的实际高度
int realHeight = target.getMeasuredHeight();
//content底部与target底部对齐时需要向上滑动的距离。
// 如果target底部已向上经拉出屏幕外,则认为distanceFromBottom为0
int distanceFromBottom = realHeight - rect.bottom;
distanceFromBottom = Math.max(distanceFromBottom, 0);
//target顶部已经向下离开ReboundFrameLayout的顶部
if (scrollY > 0) {
scrollDistance = Math.min(deltaY, scrollY);
scrollBy(0, (int) (scrollDistance * scrollRatio));
deltaY = deltaY - scrollDistance;
if (deltaY <= 0)
return;
}
scrollDistance = Math.min(deltaY, distanceFromBottom);
target.scrollBy(0, scrollDistance);
deltaY = deltaY - scrollDistance;
if (deltaY <= 0)
return;
scrollBy(0, (int) (deltaY * scrollRatio));
}
}
- 执行反弹:
/**
* 执行反弹
*/
private void rebound() {
if (getScrollY() == 0)
return;
mOverScroller.startScroll(0, getScrollY(), 0, -getScrollY(), calculateDurationByScrollY());
invalidate();
}
我们来看看下面几处重要的地方:
int realHeight = target.getMeasuredHeight();
- 关于获取
target
的内容高度问题。
1、如果target
是从layout
布局文件中解释出来的,我们可以通过target.measure(0, 0)
测量出来。
2、如果target
是new
出来的,通过target.measure(0, 0)
是测量不出来的。
为了考虑到上面两种情况,所以我们在protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法中预先测量target
。 - 在测量之前,我们先看看
ViewGroup
的解析流程,根据测试结果,流程图如下:
ViewGroup解析流程图.jpg
根据上图,不管是从layout
布局文件中解释出来的还是new
出来的,最终都会走测量方法onMeasure(int widthMeasureSpec, int heightMeasureSpec)
,那么接下来我们就在这里测量好target
就ok了 - 测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//先调用super方法固定本身的高度
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量target的实际内容高度
if (getChildCount() > 0) {
View target = getChildAt(0);
ViewGroup.LayoutParams params = target.getLayoutParams();
int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.AT_MOST);
target.measure(widthMeasureSpec, newHeightMeasureSpec);
}
}
公共方法
结尾
不求打赏,点个赞加个关注吧!我是JustinRoom,QQ:1006368252
。