前言
最近看博客,发现自己的实现复杂了,所以优化了一下。之前之所以不断的layout,是因为scroll没起效果。最近才明白,scroll一个视图的时候,内容是移动的,而background是不移动的。所以在这里,fgView的background应该为null。此外,学习使用Scroller类。
- Scroller类的构造器可以传入一个补差器,让滑动更自然。
- 调用startScroll函数之后,需要调用invalidate(),invalidate()会触发computeScroll()。
此外,与我原以为的相反,视图向下滑动的时候,scrollY为负值,因为srcollY代表的是显示窗口相对于视图顶部的偏移。我们知道,ScrollView 的内容长度是比窗口长度大的,所以会有这么一个偏移值。
源码
public class DoubleLayerRelativeLayout extends RelativeLayout {
private static final int SNAP_VELOCITY_THRESHOLD = 600;
private static final int DURATION = 200;
private View bgView;
private View fgView;
private Scroller scroller;
private VelocityTracker velocityTracker;
private float lastY;
private int originalTop;
/**
* if totalDy > dyThreshold, show {@link #bgView}
*/
private float dyThreshold;
public DoubleLayerRelativeLayout(Context context) {
super(context);
init();
}
public DoubleLayerRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DoubleLayerRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
scroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
bgView = getChildAt(0);
fgView = getChildAt(1);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
originalTop = getTop();
dyThreshold = getHeight() * 0.25f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (super.onTouchEvent(event)) {
return true;
}
addVelocityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleDownEvent(event);
break;
case MotionEvent.ACTION_MOVE:
handleMoveEvent(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handleUpEvent(event);
break;
}
return true;
}
private void handleDownEvent(MotionEvent ev) {
lastY = ev.getY();
}
private void handleMoveEvent(MotionEvent ev) {
// update the position of fgView
float currY = ev.getY();
float dy = currY - lastY;
lastY = currY;
fgView.scrollBy(0, (int) -dy);
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
fgView.scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
private void handleUpEvent(MotionEvent ev) {
int curScrollY = fgView.getScrollY();
float snapVelocity = getYVelocity();
if ((curScrollY < dyThreshold && snapVelocity < SNAP_VELOCITY_THRESHOLD) || snapVelocity < -SNAP_VELOCITY_THRESHOLD) {
// reset
scroller.startScroll(0, curScrollY, 0, -curScrollY);
} else {
// show bgView
scroller.startScroll(0, curScrollY, 0, (int) (-getMeasuredHeight()*0.5F-curScrollY));
}
invalidate();
recycleVelocityTracker();
}
private void addVelocityTracker(MotionEvent event) {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
}
private void recycleVelocityTracker() {
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
}
private int getYVelocity() {
velocityTracker.computeCurrentVelocity(1000);
int velocity = (int) velocityTracker.getYVelocity();
return velocity;
}
}