仿QQ侧拉菜单笔记

回忆篇 2015-09-23 15:24

仿QQ侧拉菜单笔记

今天模仿视频上的教程一步一步完成一个类似QQ侧滑菜单的一个demo再次留下笔记,以加强记忆。同时希望看到的各位多加批评多提建议(另外第一次用md排版,不好的话不要见怪)!

应用场景

  • 扩展主面板内容
  • 炫酷菜单展示

实施步骤

  • 分析QQ侧滑是有几部分构成
  • 分析每一部分都有什么功能与动画效果
  • 分析完后就可以开始敲代码了
分析结果

QQ侧滑效果是由三部分组成 :

  1. 背景图(BackGround)
  2. 主面板(MainContent)
  3. 左面版(LeftContent)

每一部分都有什么功能与动画效果

背景图(BackGround)
由暗到亮
主面板(MainContent)
缩小并且平移到屏幕的右侧
左面版(LeftContent)
由屏幕左侧以外放大并且平移到屏幕中间

自定义View代码解说:

<!-- 上代码解说:布局文件 -->
<?xml version="1.0" encoding="utf-8"?>
<!-- com.vdh.view.DragLayout 这个引用自定义的view一定是包名加类名 -->
<com.vdh.view.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg" 
    android:orientation="vertical" >

    <!-- 左面版 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#55aaaa"
        android:orientation="vertical" >

    <!-- 右面版 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffaaaa"
        android:orientation="vertical" >
    </LinearLayout>

</com.vdh.view.DragLayout>
<!-- 自定义的布局看出由三部分组成,与分析的QQ侧滑一致 -->

开始自定义view编写

编写前 :

自定义的DragLayout继承于FrameLayout的原因:
1. 首先因为FrameLayout是继承于GroupView自定义的View有两个视图所以必须继承于GroupView。
2 .Framelayout的子View只有层级关系,不同与其他Layout,不用实现onMeasure(); onLayout();等。

实现:

ViewDragHelper : Google2013年IO大会提出,解决界面控件拖拽问题。(v4包下)。

开始上代码

下面就通过注释一点一点讲解

package com.vdh.view;

import com.nineoldandroids.view.ViewHelper;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.support.v4.widget.ViewDragHelper.Callback;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

@SuppressLint("ClickableViewAccessibility")
public class DragLayout extends FrameLayout {
    private ViewDragHelper mDragHelper; // 控件拖拽帮助类(v4 包下)
    private ViewGroup mLeftContent; // 左视图
    private ViewGroup mMainContent; // 主视图
    protected String tag = "DragLayout"; // log的TAG
    private int mHeight; // 屏幕高度
    private int mWidth; // 屏幕宽度
    private int mRange;  // 主视图能拖拽的最远位置
    /*实现三个方便用户各种场景下调用构造方法*/
    public DragLayout(Context context) {
        this(context, null);
    }

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

    public DragLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // 初始化操作(通过静态) 
        mDragHelper = ViewDragHelper.create(this, mCallback);
        // 初始化 mDragHelper  = ViewDragHelper.create(this,mCallback);这里注意一定是通过create初始化操作
    }

    ViewDragHelper.Callback mCallback = new Callback() {
        // 根据返回结果决定当前child是否可以拖拽
        // child 当前拖拽的子View
        // pointerId 区分多点触摸的手指ID(一般不用)
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            Log.d(tag, "tryCaptureView");
            // 这个方法关键看return 
            // return true 表示子view都可以拖拽
            // return false 表示所有子View都不会被拖拽
            // return child == mMainContent 表示mMainContent 可以被拖拽
            // return child == mLeftContent 表示mLeftContent 可以被拖拽
            return true; 
        }

        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            Log.d(tag, "onViewCaptured");
            // 当Captruechild被捕获时调用
            super.onViewCaptured(capturedChild, activePointerId);
        }

        // 根据建议值修正将要移动的位置(重要) 此时没有发生真正的移动 
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            // child: 当前拖拽的View left: 新的位置的建议 dx:变化量
            if (child == mMainContent) {
                left = fixLeft(left); // 修正left值保证mMainContent在目标区域内滑动
            }
            return left;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            // 返回拖拽的范围,不对拖拽进行真正的限制、getViewHorizontalDragRange仅仅决定了了动画执行速度
            return super.getViewHorizontalDragRange(child);
        }
        // 修正left值
        public int fixLeft(int left) {
            if (left < 0) {
                return 0;
            } else if (left > mRange) {
                return mRange;
            }
            return left;
        }

        // 当view位置改变的时候,处理重要的事情(更新状态,伴随动画,重绘界面)
        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {
            // TODO Auto-generated method stub
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            Log.d(tag, "onViewPositionChanged:" + " dx: " + dx + " left: "
                    + left);
            int newleft = left;
            if (changedView == mLeftContent) {
                newleft = mMainContent.getLeft() + dx;
                newleft = fixLeft(newleft);
                mLeftContent.layout(0, 0, mWidth, mHeight);
                mMainContent.layout(newleft, 0, mWidth + newleft, mHeight);
            }
            dispatchDragEvent(newleft); // 根据newleft执行动画
            invalidate();// 为了兼容低版本要进行重绘
        }

        // 手指松开调用该回调
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            // releasedChild 要松开的子View
            // xvel x轴移动方向 x > 0 向右移动 x < 0 向左移动 x = 0 不移动
            // yvel y轴移动方向 同上x
            super.onViewReleased(releasedChild, xvel, yvel);
            // 在此考虑完open的可能后else的都是close的事件
            if (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f) {
                openMainView();
            } else if (xvel > 0) {
                openMainView();
            } else {
                closeMainView();
            }
        }

        @Override
        public void onViewDragStateChanged(int state) {
            // TODO Auto-generated method stub
            super.onViewDragStateChanged(state);
        }

    };
    /*此方法是View屏幕测量完成后调用,用于获取屏幕的高度和宽度*/
    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);
    }

    /**
     * 更新状态执行动画
     * 
     * @param newleft
     */
    protected void dispatchDragEvent(int newleft) {
        float percent = newleft * 1.0f / mRange;
        // 左面版动画
        ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
        ViewHelper.setScaleY(mLeftContent, 0.5f * percent + 0.5f);
        // -wigth/2.0f >>> 0.0f 平移动画
        ViewHelper.setTranslationX(mLeftContent,
                evaluate(percent, -mWidth / 2.0f, 0));
        ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f));
        // 主面板缩小动画
        ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
        ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
        // ViewHelper.setTranslationX(mMainContent, evaluate(percent, 0.0f,
        // 0.8f));

    }

    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
        // 这里自行画图理解,不祥述
    }

    protected void openMainView() {
        openMainView(true); // 默认平滑打开
    }

    protected void closeMainView() {
        closeMainView(true); // 默认平滑关闭
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
     // 
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    /**
     * 关闭MainView
     */
    public void closeMainView(boolean isSmooth) {
        int finalLeft = 0;
        if (isSmooth) {
            if (mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
        }
    }

    /**
     * 打开MainView
     */
    public void openMainView(boolean isSmooth) {
        int finalLeft = mRange;
        if (isSmooth) {
            if (mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
        }
    }

    // 传递事件(
    public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
        // 判断是否需要拦截事件
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            mDragHelper.processTouchEvent(event);// 处理touch事件
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true; // return true, 可以持续接受事件
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // GitHub 写注释 容错性检查(至少有两个子view,子view必须是viewGroup的子类)
        if (getChildCount() < 0) {
            throw new IllegalStateException(
                    "your ViewGroup must have 2 childern at lest.");
        }
        if (!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)) {
            throw new IllegalArgumentException(
                    "your childer must be instance of ViewGroup.");
        }
        mLeftContent = (ViewGroup) getChildAt(0);
        mMainContent = (ViewGroup) getChildAt(1);
    }

}
最后最重要的一步,就是在MainActivity引用定义好的布局
package com.vdh.activity;

import com.example.viewdraghelpertest.R;

import android.app.Activity;
import android.os.Bundle;

public class Main extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.drag_layout);
    }
}
the end

THANKS FOR YOUR TIME


这篇博客来自2015-09-23 15:24 一晃两年过去了,曾经摸索开发,开发的路上迷茫不知所措,还好我没放弃。加油!这是一篇给我很多鼓励的博客。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值