ViewDragHelper的妙用二 --QQ侧滑菜单的实现

好了,还是老规矩,先给出效果图,这里就绘制了一个简单框架,各位看官可以任意添加自己的东西。

下面我来解释一下怎么使用我们的ViewDragHelper来实现这个效果

先给出我们的布局

<?xml version="1.0" encoding="UTF-8"?>
<com.jeason.qqmenudemo.widget.SideslipLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg" >

    <FrameLayout
        android:id="@+id/left_panel"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
        <TextView
            android:ems="1"
            android:layout_height="match_parent"
            android:layout_width="wrap_content"
            android:textColor="#FFFFFF"
            android:layout_marginTop="56dp"
            android:textSize="24dp"
            android:layout_gravity="center"
            android:text="@string/menu_block"
            />
    </FrameLayout>

    <com.jeason.qqmenudemo.widget.MyFrameLayout
        android:id="@+id/main_panel"
        android:background="@drawable/bg_3"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <include layout="@layout/custom_actionbar" />

            <FrameLayout
                android:id="@+id/content"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <com.jeason.qqmenudemo.widget.MyTextView
                    android:id="@+id/content_block"
                    android:layout_width="260dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="@string/content_bolock"
                    android:padding="8dp"
                    android:textSize="18dp"
                    />
            </FrameLayout>

        </LinearLayout>
    </com.jeason.qqmenudemo.widget.MyFrameLayout>

</com.jeason.qqmenudemo.widget.SideslipLayout>

我们的布局文件很简单,就是自定义了一个侧滑布局SideslipLayout,然后我们将里面的内容分为2块LeftPanel(菜单区)和MainPanel(主内容区)。

我们目前认为所有的童鞋对于ViewDragHelper都有一定的了解,不了解的可以看一下我的上一篇博文。

强大的ViewDragHelper和ViewDragHelper的妙用

这里,我们先给出代码,再给大家慢慢解释。

package com.jeason.qqmenudemo.widget;

import com.jeason.qqmenudemo.R;
import com.jeason.qqmenudemo.view.ViewHelper;

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

public class SideslipLayout extends FrameLayout{

    private static final String TAG = "SideslipLayout";

    @SuppressWarnings("unused")
    private Context mContext;

    private ViewDragHelper mDragHelper;
    private DragListener mDragListener;
    private int range;
    private int width;
    private int height;
    private int mainLeft;

    private FrameLayout mLeftPanel;
    private MyFrameLayout mMainPanel;

    private Status mDefaultStatus = Status.Close;
    private Status status = mDefaultStatus;

    public enum Status {
        Drag, Open, Close
    }
    public SideslipLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        mContext = context;
        mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);
    }

    @Override
    protected void onFinishInflate() {
        mMainPanel = (MyFrameLayout) findViewById(R.id.main_panel);
        mMainPanel.setDragLayout(this);

        mLeftPanel = (FrameLayout) findViewById(R.id.left_panel);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        width = mLeftPanel.getMeasuredWidth(); //which meaning Full_Window
        height = mLeftPanel.getMeasuredHeight();

        range = (int) (width * 0.6f);

    }    

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        try {
            mDragHelper.processTouchEvent(e);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return true;
    }

    public Status getStatus() {
        if (mainLeft == 0) {
            status = Status.Close;
        } else if (mainLeft == range) {
            status = Status.Open;
        } else {
            status = Status.Drag;
        }
        return status;
    }

    public void open() {
        open(true);
    }

    public void open(boolean animate) {
        if (animate) {
            if (mDragHelper.smoothSlideViewTo(mMainPanel, range, 0)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mMainPanel.layout(range, 0, range * 2, height);
            dispatchDragEvent(range);
        }
    }

    public void close() {
        close(true);
    }

    public void close(boolean animate) {
        if (animate) {
            if (mDragHelper.smoothSlideViewTo(mMainPanel, 0, 0)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mMainPanel.layout(0, 0, width, height);
            dispatchDragEvent(0);
        }
    }

    private void dispatchDragEvent(int mainLeft) {
        if (mDragListener == null) {
            return;
        }
        float percent = mainLeft / (float) range;
        animateView(percent);
        mDragListener.onDrag(percent);
        Status lastStatus = status;
        if (lastStatus != getStatus() && status == Status.Close) {
            mDragListener.onClose();
        } else if (lastStatus != getStatus() && status == Status.Open) {
            mDragListener.onOpen();
        }
    }

    private void animateView(float percent) {
        float f1 = 1 - percent * 0.3f;
        ViewHelper.setScaleX(mMainPanel, f1);
        ViewHelper.setScaleY(mMainPanel, f1);
        ViewHelper.setTranslationX(mLeftPanel, -mLeftPanel.getWidth() / 2.3f + mLeftPanel.getWidth() / 2.3f * percent);
        ViewHelper.setScaleX(mLeftPanel, 0.5f + 0.5f * percent);
        ViewHelper.setScaleY(mLeftPanel, 0.5f + 0.5f * percent);
        ViewHelper.setAlpha(mLeftPanel, percent);
    }

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

    public interface DragListener {
        public void onOpen();

        public void onClose();

        public void onDrag(float percent);
    }

    public void setDragListener(DragListener dragListener) {
        mDragListener = dragListener;
    }    

    private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (mainLeft + dx < 0) {
                return 0;
            } else if (mainLeft + dx > range) {
                return range;
            } else {
                return left;
            }
        }

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return range;
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (xvel > 0) {
                open();
            } else if (xvel < 0) {
                close();
            } else if (releasedChild == mMainPanel && mainLeft > range * 0.3) {
                open();
            } else if (releasedChild == mLeftPanel && mainLeft > range * 0.7) {
                open();
            } else {
                close();
            }
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {
            if (changedView == mMainPanel) {
                mainLeft = left;
            } else {
                mainLeft = mainLeft + left;
            }
            if (mainLeft < 0) {
                mainLeft = 0;
            } else if (mainLeft > range) {
                mainLeft = range;
            }

            if (changedView == mLeftPanel) {
                mLeftPanel.layout(0, 0, width, height);
                mMainPanel.layout(mainLeft, 0, mainLeft + width, height);
            }

            dispatchDragEvent(mainLeft);
        }
    };
}

有我的上一篇文章我们知道,任何特效的View都最终是要通过onInterceptTouchEvent和onTouchEvent来实现的,所以首先我们需要将我们的MotionEvent交给我们的ViewDragHelper 实现

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        try {
            mDragHelper.processTouchEvent(e);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return true;
    }

这里,我们一般最好采用OnTouchEvent中return true;当然如果我们的ChildView有可能需要消耗MotionEvent,那么我们需要在ChildView的OnTouchEvent 中Action_Down 中return true;(如果ChildView在Action_Down return false ;那么最终 MotionEvent走到ParentView
always return true;那么ChildView 将无法得到后续的MotionEvent);

当然,考虑另一种情况,如果我们持续让我们的ParentView 中return false;那么我们可以假设如果此次的MotionEvent 最终没有被消耗(所有的ChildView都 return false ,那么我们的ParentView最终会得到Action_Down但是由于我们在Action_Down
返回了false;name 在得到Action_Down之后不会继续得到Action_UP(这里假设我们的ParentVIew是最上层ViewGroup),那么dispatchTouchEvent返回了false,事件被取消了,那么你会发现我们的ChildView不能够再移动了),这里可以说是ViewDragHelper需要考虑的逻辑问题。

当让如果我们需要我们的ChildView可能也要Action_Move时,那么就需要我们严格把控onInterceptTouchEvent,因为如果我们直接将我们的MotionEvent交给我们的ViewDragHelper,在这种情况下,因为我们的ChildView是可以移动的,所以onInterceptTouchEvent直接就返回了true,ChildView就没有机会了。那么我们可以怎么做呢。比如

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean result;
        if(ev.getAction() == MotionEvent.ACTION_MOVE){
            if(mChildView.requestTouchEvent(ev)){
                result = false;
            }else{
                result = mDragHelper.shouldInterceptTouchEvent(ev);
            }
        }else{
            result = mDragHelper.shouldInterceptTouchEvent(ev);
        }

        return result;
    }

这里仅仅给大家做一个说明,避免复杂情况下大家发现我们的ViewDragHelper出现各种不是那么灵敏的情形,或者不是那么容易掌控。反正这里还是要提醒大家一定要充分理解onInterceptTouchEvent和onTouchEvent和dispatchTouchEvent之间的逻辑关系。

好的,走到这里我们才完成了将我们的移动处理逻辑交给了ViewDragHelper。按照我上次分析的我们的移动实现要交给我们的CallBack

 private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (mainLeft + dx < 0) {        //计算ChildView左边界
                return 0;
            } else if (mainLeft + dx > range) {
                return range;
            } else {
                return left;
            }
        }

        @Override
        public boolean tryCaptureView(View child, int pointerId) {   //这里表示我们的Left和Main都可以移动
            return true;
        }

        @Override
        public int getViewHorizontalDragRange(View child) { //框定水平移动范围
            return range;
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) { //移动结束后判断开合状态自动切换状态
            super.onViewReleased(releasedChild, xvel, yvel);
            if (xvel > 0) {
                open();
            } else if (xvel < 0) {
                close();
            } else if (releasedChild == mMainPanel && mainLeft > range * 0.3) {
                open();
            } else if (releasedChild == mLeftPanel && mainLeft > range * 0.7) {
                open();
            } else {
                close();
            }
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {
            if (changedView == mMainPanel) {
                mainLeft = left;
            } else {
                mainLeft = mainLeft + left;
            }
            if (mainLeft < 0) {
                mainLeft = 0;
            } else if (mainLeft > range) {
                mainLeft = range;
            }

            if (changedView == mLeftPanel) { //移动Left时候,保持Left不动改变Main的位置
                mLeftPanel.layout(0, 0, width, height);
                mMainPanel.layout(mainLeft, 0, mainLeft + width, height);
            }

            dispatchDragEvent(mainLeft); //开启移动的时候的动画效果缩放等
        }
    };

分析完了CallBack,好像就没有什么说的了,源码下载

http://download.csdn.net/download/jaysong2012/8961751

下面继续给大家分享的更复杂一点的ViewDragHelper的妙用,有兴趣的话大家可以尝试一下。重点提示一下在onInterceptTouchEvent中的处理,有机会的话再把源码共享给大家。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-08 23:48:00

ViewDragHelper的妙用二 --QQ侧滑菜单的实现的相关文章

仿QQ侧滑菜单

仿QQ侧滑菜单 1.仿QQ侧滑(淡入淡出) 2.点击侧滑菜单相应地方响应事件 3.可以自定义侧滑菜单哦 下载地址:http://www.devstore.cn/code/info/846.html  运行截图:

Android自定义View之仿QQ侧滑菜单实现

最近,由于正在做的一个应用中要用到侧滑菜单,所以通过查资料看视频,学习了一下自定义View,实现一个类似于QQ的侧滑菜单,顺便还将其封装为自定义组件,可以实现类似QQ的侧滑菜单和抽屉式侧滑菜单两种菜单. 下面先放上效果图: 我们这里的侧滑菜单主要是利用HorizontalScrollView来实现的,基本的思路是,一个布局中左边是菜单布局,右边是内容布局,默认情况下,菜单布局隐藏,内容布局显示,当我们向右侧滑,就会将菜单拉出来,而将内容布局的一部分隐藏,如下图所示: 下面我们就一步步开始实现一个

android:自定义HorizontalScrollView实现qq侧滑菜单

今天看了鸿洋_大神在慕课网讲的qq5.0侧滑菜单.学了不少的知识,同时也佩服鸿洋_大神思路的清晰. 看了教程课下也自己实现了一下.代码几乎完全相同  别喷我啊..没办法 o(︶︿︶)o 唉 像素不好 没办法 找不到好的制作gif的软件. 我们暂且称侧滑左边界面的为menu,右边为content 首先是menu的布局 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:androi

ViewDragHelper实践之仿Android官方侧滑菜单NavigationDrawer效果

相信经常使用移动应用的用户都很熟悉侧滑菜单栏, 下拉, 下弹, 上弹等应用场景, 几乎主流的移动应用无论IOS 还是Android都能看到. 2.3以前的时候, 很多第三方比如SlidingMenu, MenuDrawer, ActionbarSherlock等等都很大程度的丰富和深化了这种交互理念.能让小小的屏幕, 容纳更多的交互接口. 也是这种趋势, Android官方在v4终于推出了DrawerLayout. 表示对侧滑的重视与肯定. 唠叨到这了. 去看了DrawerLayout的源码和官

Android笔记(五十二) 侧滑菜单SlidingMenu

SlidingMenu是一个优秀的开源项目,可以实现侧滑菜单,简单介绍一下这SlidingMenu的使用: 常用属性和方法: setTouchModeAbove(int i )是否可以通过滑动手势打开menu TOUCHMODE_MARGIN = 0  在边缘滑动 TOUCHMODE_FULLSCREEN = 1 在屏幕任意位置滑动 TOUCHMODE_NONE = 2 滑动无法打开menu setMode(int mode)设置menu出现位置 LEFT = 0    菜单出现在屏幕左侧 RI

Swift实战-小QQ(第2章):QQ侧滑菜单

QQ侧滑实现架构:需要建立以下几个ViewController:1.XQBaseViewController 2.LeftViewController3.RightViewController4.ContentViewController(中间显示的主要内容) 5.SliderViewController(用于控制侧滑动画,控制左右侧栏的显示和隐藏) 本章未完.待续... 谢谢关注.

鹅厂系列一 : 仿QQ侧滑菜单

--不会的东西你不尝试的去做,你永远都不会做 好了,跟随潮流,还是先看下效果,不然可能都没人想看下去了(不会看到效果后不想看了吧O(∩_∩)O~) 额,图片资源来自QQ_374.APK,里面四五千个图片,找这几个没把我累死,当然感谢QQ的资源,额,.先来看看初始布局,我不知道腾讯是怎么布局的,我自己为了做的像他们一点,我的布局暂时是像下面这样的,到了自定义控件的时候,还会进行重新测量和布局. 嗯,就是让左面板在主面板的下面,所以我们自定义的控件SlideLayout继承FrameLayout.一

实现“手机qq”侧滑菜单 -- 吴欧

基本数据采集 经过体验,手机QQ采用的应该是线性动画,即视图缩放比例等随手指在屏幕上滑动的距离以一次方程的形式变化. 提取基本数据,向右侧滑达到最大幅度时: 1.   右侧主视图左边界距离屏幕左边界的距离占屏幕宽度的比例为:78% 2.   右侧主视图的高度占屏幕高度的比例为:77% 分步实现: 1.实现主视图的缩放侧滑: 2.实现主视图与左视图的联动: 第一步,实现主视图的缩放侧滑 此前动手做时参考了一些类似的demo,发现许多是用手势UIPanGestureRecognizer来实现的,而本

android:QQ多种侧滑菜单的实现

在这篇文章中写了 自定义HorizontalScrollView实现qq侧滑菜单 然而这个菜单效果只是普通的侧拉效果 我们还可以实现抽屉式侧滑菜单 就像这样 第一种效果 第二种效果 第三种效果 第四种效果 其它代码都和上篇文章相同,只是在MyHorizontalScrollView.class重写onScrollChanged这个方法 第一种的侧滑效果代码很简单 @Override protected void onScrollChanged(int l, int t, int oldl, in