仿QQ6.0侧滑之ViewDragHelper的使用(一)

相信大家也都是用QQ,自从QQ更新至6.0后,侧滑由原先的划出后主面板缩小变成了左右平滑,个人觉得这样还是听美观的(个人观点),于是自己就尝试着看看自己能不能理解一下里面的各种逻辑,于是乎,自己就找了些资料,研究研究。
知道这里面的一个主要类是ViewDragHelper,那么首先我们要先来了解一下这个ViewDragHelper类,正所谓打蛇打七寸,我们就先来看看官方文档怎么介绍的,有什么奇特的功能。

首先继承:
  • java.lang.Object
    ↳ android.support.v4.widget.ViewDragHelper
    直接父类是Object。
类概述
  • ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.
  • 他是一个编写自定义ViewGroup的工具类,本省提供了许多有用的方法和状态允许用户去拖拽和绘制他们在父ViewGroup中的轨迹和位置。
Nested Classes(嵌套类)
  • ViewDragHelper.Callback
  • A Callback is used as a communication channel with the ViewDragHelper back to the parent view using it.
  • 一个回调是用作ViewDragHelper和他的父view的通信的接口

一个公开静态方法:

我们可以知道,ViewDragHelper是通过create()方法构造出来。这个在后面会有详细介绍。

让我们在来看下需要用到的里面的几个方法:

  • public boolean tryCaptureView(View child, int pointerId) {}
  • public int getViewHorizontalDragRange(View child) {}
  • public int clampViewPositionHorizontal(View child, int left, int dx) {}
  • public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
  • public void onViewReleased(View releasedChild, float xvel, float yvel) {}
    上面的几个方法,会在代码中有详细的注释,在这里只是看下我们需要重写的方法

好了哈,说了这么多,我们就先来个简单的,就是可以实现拖拽(相信当你的两个view可以拖拽的时候,你会发现,哦这么简单几步么):

第一步实现拖拽功能(简单3步实现)
//1、通过静态方法初始化操作
        mDragHelper = ViewDragHelper.create(this, mCallback);
/**
     * 2、传递触摸事件
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //让自己的控件自行判断是否去拦截
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            //自己去处理触摸事件
            mDragHelper.processTouchEvent(event);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回true,这样才能持续接收,要不然我们不会传递而是被拦截了
        return true;
    }
 ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {

      /**
         * 根据返回结果决定当前child是否可以拖拽
         * 尝试捕获的时候调用,如果返回的是主面板,那么负面版是不能被调用的
         * @param child    当前被拖拽的view
         * @param pointerId    区分多点触摸的id
         * @return  返回true 是都可以拖拽   
         *          返回child == mLeftContent   左侧可以移动
         *          返回child == mMainContent   右侧可以移动
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            //这里要返回true,要不然不能拖拽
            return true;
        }

        /**
         * 根据建议值修正将要移动的位置   此时并没有发生真正的移动(左右)
         *
         * @param child    当前拖拽的view
         * @param left     新的位置的建议值  oldLeft + dx
         * @param dx       变化量   和变化之前位置的差值
         * @return
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            //返回值该child现在的位置
            return left;
        }
    };

好了,第一个效果图可以出来了,就是可以拖拽了,是不是 很简单的就实现了呢:
这里写图片描述
但是,你要是做成这样提交任务,是不是不想干活了哈,好了下面我们就来控制一下拖拽位置,不能让他乱拖拽了哈。、

第二步,控制拖拽范围

我们想要控制拖拽范围,首先我们得需要拿到这两个控件,取到有关这两个控件的属性,我们才能去操作。于是我们重写了一下的方法:

/**
     * Finalize inflating a view from XML.  This is called as the last phase
     * of inflation, after all child views have been added.
     *   当xml填充完的时候去掉用,在这里我们可以找到我们要去操控的那两个布局
     * <p>Even if the subclass overrides onFinishInflate, they should always be
     * sure to call the super method, so that we get called.
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        /**
         * 根据索引来找
         */

        /**
         * 得到左边的布局
         */
        mLeftContent = (ViewGroup) getChildAt(0);

        /**
         * 得到主main布局
         */
        mMainContent = (ViewGroup) getChildAt(1);
    }

接下来我们就要来得到有关宽和高了,我们知道onMessure()中可以获取,不过查看了一下,下面的这个方法也是可以获取到的:

/**
     * 当尺寸变化的时候去调用
     * This is called during layout when the size of this view has changed
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);


        /**
         * 屏幕的宽度和高度
         */
        mHeight = getMeasuredHeight();
        mWidth = getMeasuredWidth();

        /**
         * 自定义左侧view拖拽出来的距离
         */
        mRange = (int) (mWidth * 0.7);
    }

好了,有关我们需要的宽、高、拖拽距离我们已经获取到了,那么我们接下来就要来限制了。

我们只需要在clampViewPositionHorizontal()方法中加入这个,就能限制主面板不能往左移,左面板只能移动到右侧的mRange位置。(这里可以去尝试一下哈)
   if (child == mMainContent) {
                left = fixLeft(left);
            }


 /**
     * 修正方法
     * 根据范围去修正左侧的view的可见
     *
     * @param left
     * @return
     */
    private int fixLeft(int left) {
        if (left < 0) {
            return 0;
        } else if (left > mRange) {
            return mRange;
        }
        return left;
    }

这个时候,我们基本上的大致框架已经出来了,但是还是有一个问题,那就是虽然我们达到了滑动过的效果(右侧的定了,但是当我们把LeftView滑动出来的时候,还是可以往右滑)我们要想办法去限制,就是限制,左侧的view我只能右滑mRange的距离这个时候我们就要查看方法了,哪个方法可以拿到变化的position的值,然后去改变:


        /**
         * 当view位置改变的时候,处理要做的事情,更新状态,伴随动画,重绘界面
         *
         * 此时view已经发生了位置的改变
         *
         * @param changedView   改变的位置view
         * @param left   新的左边值
         * @param top
         * @param dx   水平变化量
         * @param dy
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);

            int newLeft = left;

            //如果我们拖拽的是左面板
            if (changedView == mLeftContent) {
                //新的左侧位置是我们的主面板的左侧加上水平变化量
                newLeft = mMainContent.getLeft() + dx;
            }

            //进行修正(不能超出我们的规定的范围)
            newLeft = fixLeft(newLeft);

            if (changedView == mLeftContent) {
                //当左面板移动之后,在强制放回去
                mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
                mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
            }

            //兼容低版本   强制重绘
            invalidate();
        }

好了,这个时候我们的大致效果已经出来了,看下效果图。(这里的中间出来的白到属于录制问题,亲测没问题)
这里写图片描述
到这里,大致的拖拽已经可以实现了,当然了哈,我的现在布局就是下面的简单的实现(要先加一些控件,这个自己在两个LinearLayout中加入即可)

<?xml version="1.0" encoding="utf-8"?>
<com.example.qqsliding.DragLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.qqsliding.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@mipmap/sidebar_bg">

    </LinearLayout>

    <LinearLayout

        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#FFFFFF">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="#0CB8F6"
            android:gravity="center_vertical">

            <ImageView
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginLeft="15dp"
                android:src="@mipmap/icon_avatar_white"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:text="Header"/>

            <ImageView
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_alignParentRight="true"
                android:layout_marginRight="15dp"
                android:src="@mipmap/icon_search"/>
        </RelativeLayout>
    </LinearLayout>
</com.example.qqsliding.DragLayout>

但是细心的可能会发现,我们拖拽的时候,特别生硬,那是因为我们没有加上动画效果,只是生生的给拖拽出来了,具体的加入动画,定义回调效果,我会在稍后的仿QQ6.0侧滑优化中介绍:http://blog.csdn.net/wuyinlei/article/details/50617408,本人小白一枚,希望自己的知识总结能够帮助到他人,如有交流,请QQ:1069584784.鄙人会不胜感激。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值