阅读背景:

Android自定义View - 实现仿QQ拖拽效果_AD钙奶-lalala的博客

来源:互联网 

实际开发中,我们经常需要实现如下效果:

上代码:

package com.lzy.draglayout.ui;

import android.animation.ArgbEvaluator;
import android.animation.FloatEvaluator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Scroller;

import com.nineoldandroids.view.ViewHelper;

public class DragLayout extends FrameLayout {

    private ViewDragHelper helper;  // 拖拽辅助工具
    private ViewGroup mLeftMenu;    // 左菜单
    private ViewGroup mMainContent; // 主面板
    private int mHeight; // 控件高度
    private int mWidth; // 控件宽度
    private int mRange; // 拖拽范围
    private Status status = Status.Close; //  关闭
    private OnDragChangeListener onDragChangeListener;

    public static enum Status{ // 状态的枚举
        Close,  // 关闭
        Open,   // 打开
        Draging;// 拖拽中
    }

    // Java new使用
    public DragLayout(Context context) {
        this(context, null);
    }

    // XML
    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // 让两个面板动起来
    // XML -> style
    public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

//        1. 创建ViewDragHelper
        // forParent 传递被拖拽控件的父节点.
        // sensitivity 敏感度, 越大越敏感
        // callback 提供信息, 接收事件
        helper = ViewDragHelper.create(this, 1.0f, callback);


    }
    // 3. 重写事件回调
    ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * a. 当前任何子控件被触摸到时, 此方法被执行, 尝试捕获子控件
         * @param child 被触摸到的子控件
         * @param pointerId 多点触摸手指id
         * @return 返回值决定了, 被拖拽的子控件能否被拖拽
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
//            System.out.println("child: " + child);
            return true;
        }

        // 返回拖拽最大范围 > 0 即可, 不会限制位置
        // 用来判断水平方向移动是否需要拦截
        // 用来计算动画的执行时长
        @Override
        public int getViewHorizontalDragRange(View child) {
            return mRange;
        }

        /**
         * b. 修正控件的水平方向的位置.
         * @param child 被拖拽的子控件
         * @param left 框架建议移动到的水平方向位置
         * @param dx 水平方向将要发生的瞬时偏移量
         * @return 返回值决定了最终移动到的位置
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            int oldLeft = child.getLeft();
            System.out.println("clampViewPositionHorizontal: " +
                    "left: " + left + " dx: " + dx + " oldLeft: " + oldLeft);
            if(child == mMainContent){
                left = fixLeft(left);
            }
            return left;
        }

        /**
         * c. 当子控件位置发生变化时, 被执行. 执行伴随动画, 更新状态, 执行监听回调
         * @param changedView 发生位置变化的子控件
         * @param left 子控件最新的水平方向位置
         * @param top  子控件最新的竖直方向位置
         * @param dx   子控件刚刚在水平方向发生的偏移量
         * @param dy   子控件刚刚在竖直方向发生的偏移量
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            System.out.println("onViewPositionChanged: left: " + left + " dx: " + dx);

            // 传递拖拽事件
            if(changedView == mLeftMenu) {
                // 拖拽左菜单时, 把左菜单放回去
                mLeftMenu.layout(0, 0, 0 + mWidth , 0 + mHeight);
                // 将其变化量传递给主面板
                int newLeft = mMainContent.getLeft() + dx;
                newLeft = fixLeft(newLeft);
                // 让主面板新的left生效
                mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
            }
            
            dispatchEvent();
            
        }

        /**
         * d. 当控件被释放的时候, 被执行. 执行动画
         * @param releasedChild 被释放的子控件
         * @param xvel  释放时水平方向的速度, 向右+, 向左-
         * @param yvel  释放时竖直方向的速度, 向下+, 向上-
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            System.out.println("onViewReleased xvel: " + xvel);

            // 速度比较小, 根据位置判断
            if(Math.abs(xvel) <= 50 && mMainContent.getLeft() > mRange * 0.5f){
                open();
            }else if(xvel > 50){
                open();
            }else {
                close();
            }

        }
    };

    ArgbEvaluator argbEvaluator =  new ArgbEvaluator();

    private void dispatchEvent() {
        // 计算一个时间轴
        float percent = mMainContent.getLeft() * 1.0f / mRange;
        System.out.println("percent: " + percent);

        // 0.0 -> 1.0
        animViews(percent); // 给View执行动画

        if(onDragChangeListener != null){
            onDragChangeListener.onDraging(percent);
        }

        Status lastStatus = status;
        // 执行状态的更新
        status = updateStatus(percent);
        System.out.println("status: " + status);

        // 状态发生变化时,执行监听回调
        if(lastStatus != status && onDragChangeListener != null){
            if(status == Status.Open){
                onDragChangeListener.onOpen();
            }else if(status == Status.Close){
                onDragChangeListener.onClose();
            }
        }


    }

    /**
     * 根据当前百分比获取最新状态
     * @param percent
     * @return
     */
    private Status updateStatus(float percent) {
        if(percent == 0){
            return Status.Close;
        }else if(percent == 1.0f){
            return Status.Open;
        }

        return Status.Draging;
    }

    private void animViews(float percent) {
        //        1. 左菜单: 缩放动画 ,平移动画, 透明度
        // 缩放动画 0.0 -> 1.0 >> 0.0 -> 0.5 >> 0.5 -> 1.0
//        mLeftMenu.setScaleX(0.5f + percent * 0.5f);
//        mLeftMenu.setScaleY(0.5f + percent * 0.5f);
//        mLeftMenu.setScaleX(evaluate(percent, 0.5f, 1.0f));
//        mLeftMenu.setScaleY(evaluate(percent, 0.5f, 1.0f));
        ViewHelper.setScaleX(mLeftMenu, evaluate(percent, 0.5f, 1.0f));
        ViewHelper.setScaleY(mLeftMenu, evaluate(percent, 0.5f, 1.0f));
        // 平移动画 -mWidth/2.0f -> 0
        ViewHelper.setTranslationX(mLeftMenu, evaluate(percent, -mWidth/2.0f, 0));
        //  透明度, 0.3 -> 1.0
        ViewHelper.setAlpha(mLeftMenu, evaluate(percent, 0.3f, 1.0f));

//        2. 主面板: 缩放动画 1.0f -> 0.8
        ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
        ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));

//        3. 背景  : 亮度变化
        Drawable background = getBackground();
        // 给背景添加一个颜色过滤器 Color.BLACK -> Color.TRANSPARENT
        background.setColorFilter(
                (Integer) argbEvaluator.evaluate(percent, Color.BLACK, Color.TRANSPARENT), // 估算中间颜色
                PorterDuff.Mode.SRC_OVER);
    }
    // android.animation.FloatEvaluator

    /**
     * float类型估值器
     * @param fraction 分度值
     * @param startValue 开始值
     * @param endValue 结束值
     * @return 估算值
     */
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
    /**
     * 关闭
     */
    public void close() {
        close(true);
    }
    public void close(boolean isSmooth){
        int finalLeft = 0;

        if(isSmooth){ // 执行平滑动画
            // 1. 触发一个平滑动画
            if(helper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
                // true 动画还没结束, 需要触发界面的重绘
                ViewCompat.postInvalidateOnAnimation(this); // -> drawChild -> child.draw -> computeScroll
            }
        }else {
            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
        }
    }

    /**
     * 打开
     */
    public void open() {
        open(true);
    }
    public void open(boolean isSmooth){
        int finalLeft = mRange;

        if(isSmooth){ // 执行平滑动画
            // 1. 触发一个平滑动画
            if(helper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
                // true 动画还没结束, 需要触发界面的重绘
                ViewCompat.postInvalidateOnAnimation(this); // -> drawChild -> child.draw -> computeScroll
            }
        }else {
            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        // 2. 维持动画的继续
        if(helper.continueSettling(true)){ // 执行判断, 进行真正控件移动操作
            // true 动画还没结束, 需要触发界面的重绘
            ViewCompat.postInvalidateOnAnimation(this);// -> drawChild -> child.draw -> computeScroll
        }

    }

    // 限定主面板的拖拽范围
    private int fixLeft(int left) {
        if(left < 0){
            left = 0;
        }else if(left > mRange){
            left = mRange;
        }
        return left;
    }


    // 2. 转交拦截判断, 处理触摸事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return helper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        try {
            // 由框架处理触摸事件
            helper.processTouchEvent(event);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return true; // 消费事件
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        if(getChildCount() < 2){
            throw new IllegalStateException("Your viewgroup must contains 2 child at least. 你的布局必须至少包含两个子控件");
        }

        if(!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)){
            throw new IllegalArgumentException("Your child must be an instance of ViewGroup. 子控件必须是ViewGroup的实现类");
        }

        mLeftMenu = (ViewGroup) getChildAt(0);
        mMainContent = (ViewGroup) getChildAt(1);
    }

    // 在onMeasure 之后 , 在onLayout之前
    // 宽高变化时执行
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mHeight = getMeasuredHeight();
        mWidth = getMeasuredWidth();

        mRange = (int) (mWidth * 0.6f);

    }

    public void setOnDragChangeListener(OnDragChangeListener onDragChangeListener) {
        this.onDragChangeListener = onDragChangeListener;
    }

    public interface OnDragChangeListener {

        void onClose();

        void onOpen();

        void onDraging(float percent);
    }

}
package com.lzy



你的当前访问异常,请进行认证后继续阅读剩余内容。

分享到: