双层布局 DoubleLayerLayout

前言

之前用过赶集生活 App,主界面向下滑动可以露出天气信息。效果不错。不仅充分的利用了空间,也给用户以发现的乐趣。所以,我也做了一个这样的布局。

效果图

这里写图片描述

思路

DoubleLayerLayout 继承自 RelativeLayout。正常情况下,foreground view 挡住 background view。滑动时,让 foreground 跟随手指移动,露出 background view。同时,关注手指移动的加速度,实现“猛地”一滑,foreground view 顺势滑开的效果。

参考链接

链接1

知识点

  1. View 中几个比较重要的 hook 的调用顺序:
    onFinishInflate();
    onAttachedToWindow();
    onWindowVisiblityChanged();
    onMeasure();
    onSizeChanged();
    onLayout();
    onDraw();
  2. 打算处理手势之前,触发一个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);
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值