前言
之前用过赶集生活 App,主界面向下滑动可以露出天气信息。效果不错。不仅充分的利用了空间,也给用户以发现的乐趣。所以,我也做了一个这样的布局。
效果图
思路
DoubleLayerLayout 继承自 RelativeLayout。正常情况下,foreground view 挡住 background view。滑动时,让 foreground 跟随手指移动,露出 background view。同时,关注手指移动的加速度,实现“猛地”一滑,foreground view 顺势滑开的效果。
参考链接
知识点
- View 中几个比较重要的 hook 的调用顺序:
onFinishInflate();
onAttachedToWindow();
onWindowVisiblityChanged();
onMeasure();
onSizeChanged();
onLayout();
onDraw(); - 打算处理手势之前,触发一个cancelEvent
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
onTouchEvent(cancelEvent);
源码
package com.example.com.example.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.RelativeLayout;
import android.widget.Scroller;
public class DoubleLayerLayout 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 DoubleLayerLayout(Context context) {
super(context);
init();
}
public DoubleLayerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DoubleLayerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
scroller = new Scroller(getContext());
}
@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) {
// return super.onTouchEvent(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.layout(fgView.getLeft(), ((int) (fgView.getTop() + dy)), fgView.getRight(), ((int) (fgView.getTop() + getMeasuredHeight() + dy)));
// fgView.scrollBy(0, (int) -dy);
}
private void handleUpEvent(MotionEvent ev) {
float totalDy = fgView.getTop() - originalTop;
float snapVelocity = getYVelocity();
if ((totalDy < dyThreshold && snapVelocity < SNAP_VELOCITY_THRESHOLD) || snapVelocity < -SNAP_VELOCITY_THRESHOLD){
// reset
// fgView.layout(fgView.getLeft(), originalTop, fgView.getRight(), originalTop+getMeasuredHeight());
animateLayout(fgView, originalTop-fgView.getTop());
} else {
// show bgView
// fgView.layout(fgView.getLeft(), ((int) (originalTop + getMeasuredHeight() * 0.5f)), getRight(), ((int) (originalTop + getMeasuredHeight() * 1.5f)));
animateLayout(fgView, ((int) (originalTop + getMeasuredHeight() * 0.5f - fgView.getTop())));
}
recycleVelocityTracker();
}
private void animateLayout(View view, int dy){
Animation layoutAnimation = new LayoutAnimation(view, dy);
layoutAnimation.setDuration(DURATION);
layoutAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
startAnimation(layoutAnimation);
}
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;
}
class LayoutAnimation extends Animation {
private View view;
private int dy;
private int t;
private int h;
private int l;
private int r;
public LayoutAnimation(View view, int dy) {
super();
this.view = view;
this.dy = dy;
t = view.getTop();
l = view.getLeft();
r = view.getRight();
h = view.getMeasuredHeight();
}
@Override
protected void applyTransformation(float interpolatedTime,
Transformation transformation) {
int dy = (int) (interpolatedTime * this.dy);
view.layout(l, t + dy, r, t + h + dy);
}
}
}