自定义View---抽屉效果

说明

  1. 这个自定义View,没有处理好多点触摸问题
  2. View跟着手指移动,没有采用传统的scrollBy方法,而是通过不停地重新布局子View的方式,来使得子View产生滚动效果menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight);
  3. 相应的,由于没有使用scrollBy方法,就没有产生getScrollX值,所以不能通过Scroller的startScroll方法来完成手指离开后的平滑滚动效果,而是使用了Animation动画的applyTransformation方法来完成插值,从而实现动画效果

主要算法是:动画当前值=起始值+(目标值-起始值)*interpolatedTime

其中interpolatedTime是一个0.0f~1.0f的数字,系统自己插值计算好了(默认是线性变化的),当然你可以自己写插值器

 /**
     * 由于上面不能使用scrollBy,那么这里就不能使用Scroller这个类来完成平滑移动了,还好我们有动画
     */
    class MyAnimation extends Animation {

        private int viewCurrentLfet;
        private int viewStartLfet;
        private int viewTargetLfet;
        private int viewWidth;
        private View view;
        private int cha;

        public MyAnimation(View view, int viewStartLfet, int viewTargetLfet, int viewWidth) {
            this.view = view;
            this.viewStartLfet = viewStartLfet;
            this.viewTargetLfet = viewTargetLfet;
            this.viewWidth = viewWidth;
            cha = viewTargetLfet - viewStartLfet;
            setDuration(Math.abs(cha));
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);

            viewCurrentLfet = (int) (viewStartLfet + cha * interpolatedTime);
            view.layout(viewCurrentLfet, 0, viewCurrentLfet + viewWidth, menuHeight);


        }
    }

完整代码

package com.sunshine.choutidemo;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Transformation;

/**
 * Created by a on 2016/8/15.
 */
public class ChouTiView extends ViewGroup {

    private View mainView;
    private View menuView;
    private int menuWidth;
    private int downX;
    private int lastX;
    private int moveX;
    private int deltaX;
    private int menuLeft;
    private int mainLeft;
    private int menuHeight;
    private int mainWidth;
    private int mainHeight;
    private int menuLeftBorder;
    private int mainLeftBorder;
    private int menuRightBorder;
    private int mainRightBorder;
    private int mMaxVelocity;
    private VelocityTracker mVelocityTracker;
    private int mPointerId;
    private float velocityX;
    private float velocityY;

    public ChouTiView(Context context) {
        super(context);
        init();
    }


    public ChouTiView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
//      0.获得此次最大速率
        mMaxVelocity = ViewConfiguration.get(getContext()).getMaximumFlingVelocity();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mainView.measure(widthMeasureSpec, heightMeasureSpec);
        menuView.measure(widthMeasureSpec, heightMeasureSpec);
//        获得子View的正确宽度(只能获取具体的数字值),但是不能这样获取高度,因为这里match—parent为-1
        menuWidth = menuView.getLayoutParams().width;
        menuLeft = (int) (-menuWidth * 0.5);
        menuLeftBorder = (int) (-menuWidth * 0.5);
        menuRightBorder = 0;
        mainLeft = 0;
        mainLeftBorder = 0;
        mainRightBorder = menuWidth;

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        menuHeight = b;
        mainWidth = r;
        mainHeight = b;
        mainView.layout(l, t, r, b);
        menuView.layout(menuLeft, t, menuLeft + menuWidth, b);

    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mainView = getChildAt(1);
        menuView = getChildAt(0);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getActionMasked();

        acquireVelocityTracker(event);  //1.向VelocityTracker添加MotionEvent
        final VelocityTracker verTracker = mVelocityTracker;
        switch (action) {

            case MotionEvent.ACTION_DOWN:
                //2.求第一个触点的id, 此时可能有多个触点,但至少一个
                // 获取索引为0的手指id
                mPointerId = event.getPointerId(0);
                downX = (int) event.getX();
                lastX = downX;
                break;

            case MotionEvent.ACTION_MOVE:
// 获取当前手指id所对应的索引,虽然在ACTION_DOWN的时候,我们默认选取索引为0
                // 的手指,但当有第二个手指触摸,并且先前有效的手指up之后,我们会调整有效手指

                // 屏幕上可能有多个手指,我们需要保证使用的是同一个手指的移动轨迹,
                // 因此此处不能使用event.getActionIndex()来获得索引
                final int pointerIndex = event.findPointerIndex(mPointerId);


                moveX = (int) event.getX(pointerIndex);
                deltaX = moveX - lastX;
//                把触摸移动引起的增量,体现在menu和main的左侧left上
                menuLeft = (int) (menuLeft + deltaX * 0.43);//让菜单移动的慢一点
                mainLeft = mainLeft + deltaX;
//                让菜单根据手指增量移动,考虑两侧边界问题(通过不停地layout实现移动效果)
//                为何不适用scrollBy,因为scrollBy移动的是外层的大View,现在需求是分别移动这个大view内的两个小View
//                scrollBy的话,会让菜单和主页面同时移动,不会产生错位效果,
//                你会想,那让小view自己scrollBy,这样也是不行的,
//                因为让小view,例如menu调用scrollBy的话,会让menu自己的边框在动,
//                看上去,是menu内部的文字在移动,但是menu并没有在外层的大View里移动
//                说的很拗口,但是真的不能用scrollBy
                if (menuLeft >= menuRightBorder) {
                    menuLeft = menuRightBorder;
                } else if (menuLeft <= menuLeftBorder) {
                    menuLeft = menuLeftBorder;
                }
                menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight);


//                让主页面根据手指增量移动,考虑两侧边界问题
                if (mainLeft >= mainRightBorder) {
                    mainLeft = mainRightBorder;
                } else if (mainLeft <= mainLeftBorder) {
                    mainLeft = mainLeftBorder;
                }
                mainView.layout(mainLeft, 0, mainLeft + mainWidth, mainHeight);

                lastX = moveX;
                break;


            case MotionEvent.ACTION_UP:
                //3.求伪瞬时速度
                verTracker.computeCurrentVelocity(1000, mMaxVelocity);
                velocityX = verTracker.getXVelocity(mPointerId);
                Log.e("qwe", velocityX + "/" + mMaxVelocity);
                if (velocityX > 1000) {
                    smoothToMenu();
                } else if (velocityX < -2000) {
                    smoothToMain();
                } else {
//               判断松手的位置,如果大于1/2.5的菜单宽度就打开菜单,否则打开主页面

                    if (mainLeft > menuWidth / 2.5) {
                        Log.e("qqq", "显示菜单");
                        smoothToMenu();
                    } else {
                        Log.e("qqq", "显示主页面");
                        smoothToMain();
                    }
                }
//                4.ACTION_UP释放VelocityTracker,交给其他控件使用
                releaseVelocityTracker();
                break;
            case MotionEvent.ACTION_CANCEL:

//                4.ACTION_UP释放VelocityTracker,交给其他控件使用
                releaseVelocityTracker();

            case MotionEvent.ACTION_POINTER_UP:
                // 获取离开屏幕的手指的索引
                int pointerIndexLeave = event.getActionIndex();
                int pointerIdLeave = event.getPointerId(pointerIndexLeave);
                if (mPointerId == pointerIdLeave) {
                    // 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker
                    int reIndex = pointerIndexLeave == 0 ? 1 : 0;
                    mPointerId = event.getPointerId(reIndex);
                    // 调整触摸位置,防止出现跳动
                    downX = (int) event.getX(reIndex);
//                    y = event.getY(reIndex);
                    releaseVelocityTracker();
                }
                releaseVelocityTracker();

                break;
        }


        return true;
    }

    private void smoothToMain() {
        MyAnimation menuAnimation = new MyAnimation(menuView, menuLeft, menuLeftBorder, menuWidth);
        MyAnimation mainAnimation = new MyAnimation(mainView, mainLeft, mainLeftBorder, mainWidth);
        AnimationSet animationSet = new AnimationSet(true);
        animationSet.addAnimation(menuAnimation);
        animationSet.addAnimation(mainAnimation);
        startAnimation(animationSet);
        //一定记得更新menu和main的左侧状态,这影响到了,再次手指触摸时候的动画,否则突变
        menuLeft = menuLeftBorder;
        mainLeft = mainLeftBorder;
    }

    private void smoothToMenu() {
        MyAnimation menuAnimation = new MyAnimation(menuView, menuLeft, menuRightBorder, menuWidth);
        MyAnimation mainAnimation = new MyAnimation(mainView, mainLeft, mainRightBorder, mainWidth);
        AnimationSet animationSet = new AnimationSet(true);
        animationSet.addAnimation(menuAnimation);
        animationSet.addAnimation(mainAnimation);
        startAnimation(animationSet);
        //一定记得更新menu和main的左侧状态,这影响到了,再次手指触摸时候的动画,否则突变
        menuLeft = menuRightBorder;
        mainLeft = mainRightBorder;
    }


    /**
     * @param event 向VelocityTracker添加MotionEvent
     * @see android.view.VelocityTracker#obtain()
     * @see android.view.VelocityTracker#addMovement(MotionEvent)
     */
    private void acquireVelocityTracker(final MotionEvent event) {
        if (null == mVelocityTracker) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

    /**
     * 释放VelocityTracker
     *
     * @see android.view.VelocityTracker#clear()
     * @see android.view.VelocityTracker#recycle()
     */
    private void releaseVelocityTracker() {
        if (null != mVelocityTracker) {
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }


    /**
     * 由于上面不能使用scrollBy,那么这里就不能使用Scroller这个类来完成平滑移动了,还好我们有动画
     */
    class MyAnimation extends Animation {

        private int viewCurrentLfet;
        private int viewStartLfet;
        private int viewTargetLfet;
        private int viewWidth;
        private View view;
        private int cha;

        public MyAnimation(View view, int viewStartLfet, int viewTargetLfet, int viewWidth) {
            this.view = view;
            this.viewStartLfet = viewStartLfet;
            this.viewTargetLfet = viewTargetLfet;
            this.viewWidth = viewWidth;
            cha = viewTargetLfet - viewStartLfet;
            setDuration(Math.abs(cha));
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);

            viewCurrentLfet = (int) (viewStartLfet + cha * interpolatedTime);
            view.layout(viewCurrentLfet, 0, viewCurrentLfet + viewWidth, menuHeight);


        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值