有效果图才有看下去的动力,也能快速的知道是不是你想要的
一、实现思路
ScrollView中只能有个子View。我们的目的就是在这个子View到达顶部或是底部的时,当我们继续向下或向上滑动的时候依然可以让我们的子View向下或是向上移动,当我们松开的时候回到顶部或是底部。
二、具体分析
1、需要判断View是否到顶部或是底部。
getScrollY() == 0; // 到顶(有坑:必须知道子View的大小不然一直都是0)
getScrollY() == childView.getMeasuredHeight() - getHeight(); // 到底
2、在开始移动子View的时候,要保存初始状态,目的是为了之后的恢复初始状态。这里我们用一个Rect 做参照物在onLayout中初始化大小(位置),用的参数是子View的属性
rect.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
3、在onTouchEvent中的 MotionEvent.ACTION_MOVE 中从新放置我们的子View,先判断判断是否可以开始移动,然后开始移动,(如果在down记录按下位置,记录移动距离的话,或出现手指移动距离反应在视图上等指数增长,因为childView.getTop = childView.getTop + distanceY,distanceY在增大childView.getTop也在增大,所以就是用了一个相对距离)
if (isNeedMove()) {
float startY = y;// 上次位置就是现在开始的位置
float nowY = ev.getY();
int distanceY = (int) (nowY - startY);// 现在的位置和上次位置的差
if (!isCount) {// 保证每次从0开始,因为这里的startY是全局变量 有点时候第一次 nowY - startY<0 有的时候>>0
distanceY = 0; // 在这里要归0.
}
y = nowY;// 记录上一次位置
// 从新摆放子View的位置
Log.d("aaa", "distanceY = " + distanceY + "---childView.getTop()=" + childView.getTop());
childView.layout(
childView.getLeft(),
childView.getTop() + distanceY / 3,
childView.getRight(),
childView.getBottom() + distanceY / 3);
isCount = true;
}
4、 在onTouchEvent中的 MotionEvent.ACTION_UP 做回弹操作就是就是将我们的子View放在最开始的位置,还记得我们定义的参考物吗?这个时候他就起到了作用,将参照物的位置属性赋值给我们的子View,让我们的子View回到正常的位置,加上动画会自然一些
/**
* 重置子View
*/
private void resetChildView() {
TranslateAnimation animation = new TranslateAnimation(0, 0, childView.getTop(), rect.top);
animation.setDuration(200);
childView.startAnimation(animation);
childView.layout(rect.left, rect.top, rect.right, rect.bottom);
isCount = false;
}
三、完整代码
public class SpringScrollView extends ScrollView {
private View childView;// 子view
private Rect rect = new Rect();// 复位用的参照物
private float downY;// 记录上一个位置
private float y;// 记录上一个位置
private boolean isCount;// 是否重置距离
private Paint paint;
private Rect tvRect = new Rect();
private final String content;
public SpringScrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
paint.setTextSize(50);
paint.setColor(Color.GRAY);
content = "我是一个回弹的ScrollView";
}
/**
* 布局填充完毕,获取子View对象
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
childView = getChildAt(0);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// 设置复位参照
rect.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 画文字
paint.getTextBounds(content, 0, content.length() - 1, tvRect);
canvas.drawText(content, (getWidth() - tvRect.width()) / 2, tvRect.height() * 1.2f, paint);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (isNeedMove()) {
float startY = y;// 上次位置就是现在开始的位置
float nowY = ev.getY();
int distanceY = (int) (nowY - startY);// 现在的位置和上次位置的差
if (!isCount) {// 保证每次从0开始,因为这里的startY是全局变量 有点时候第一次 nowY - startY<0 有的时候>>0
distanceY = 0; // 在这里要归0.
}
y = nowY;// 记录上一次位置
// 从新摆放子View的位置
Log.d("aaa", "distanceY = " + distanceY + "---childView.getTop()=" + childView.getTop());
childView.layout(
childView.getLeft(),
childView.getTop() + distanceY / 3,
childView.getRight(),
childView.getBottom() + distanceY / 3);
isCount = true;
}
break;
case MotionEvent.ACTION_UP:
// 重置子View
if (isCount) {
resetChildView();
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 重置子View
*/
private void resetChildView() {
TranslateAnimation animation = new TranslateAnimation(0, 0, childView.getTop(), rect.top);
animation.setDuration(200);
childView.startAnimation(animation);
childView.layout(rect.left, rect.top, rect.right, rect.bottom);
isCount = false;
}
/**
* 判断是否需要弹性
*
* @return
*/
public boolean isNeedMove() {
int offset = childView.getMeasuredHeight() - getHeight();// 到底
int scroll = getScrollY();// 到顶
return scroll == 0 || offset == scroll;
}
}
最后有一个小问题,我发现网上但部分弹性ScrollView都会有着问题,在我下拉回弹不松手,然后向上移动的时候出现的,感觉ScrollView也跟着向上走了,应该因为这个时候,子view.getTop>0但是却不走重置子view的方法导致。希望哪位大能可以帮个忙,解决一下。如果哪天我知道了 我也会解决更新一下。