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

——不会的东西你不尝试的去做,你永远都不会做

好了,跟随潮流,还是先看下效果,不然可能都没人想看下去了(不会看到效果后不想看了吧O(∩_∩)O~)

额,图片资源来自QQ_374.APK,里面四五千个图片,找这几个没把我累死,当然感谢QQ的资源,额,.先来看看初始布局,我不知道腾讯是怎么布局的,我自己为了做的像他们一点,我的布局暂时是像下面这样的,到了自定义控件的时候,还会进行重新测量和布局.

嗯,就是让左面板在主面板的下面,所以我们自定义的控件SlideLayout继承FrameLayout.一般自定义控件会涉及到三个方法,onMeasure 测量,onLayout布局,onDraw绘制,如果是继承ViewGroup的话我们一般需要重写布局方法,继承view的话要重写onDraw方法,当然你喜欢的话,都可以重写.我们这里继承的是FrameLayout,它继承的是ViewGroup,即它已经实现了布局方法,但是我说过了,我们要重新测量和布局,主要是对左面板slideView的测量和布局,我们要调整它的宽度和位置,所以我们先来重写onMeasure方法.onMeasure(int widthMeasureSpec, int heightMeasureSpec)看这两个参数,看名字就知道中式英语很像有没有,测量说明书,即我们能通过测量说明书来进行测量,其实这两个数是测量说明书上给定的规范值,所以叫测量规范,结合我们下面的代码进行讲解

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

        mSlideView = getChildAt(0);
        mMainView = getChildAt(1);

        mWd_width = MeasureSpec.getSize(widthMeasureSpec);     //使用测量说明书得到我们想要知道的宽度和高度值,它是match_parent的,所以拿到的就是窗体的宽高度
        mWd_height = MeasureSpec.getSize(heightMeasureSpec);

        int sl_widthSpec = MeasureSpec.makeMeasureSpec((mWd_width /3) * 2,MeasureSpec.EXACTLY); //制作测量说明书规定值
        int sl_heightSpec = MeasureSpec.makeMeasureSpec(mWd_height,MeasureSpec.EXACTLY);
        mSlideView.measure(sl_widthSpec,sl_heightSpec);

        mMainView.measure(widthMeasureSpec,heightMeasureSpec);   //主面板和自己一样,都是填充整个窗体,所以测量说明书的规定值一样

        setMeasuredDimension(mWd_width, mWd_height);            //测量自己用这个方法.
    }

制作测量规范值的函数,第一个参数的意思是,你想要分配多少像素吧,没有给解释,这个不重要….重要的是第二个参数,第二个参数有三个值可以填,分别是UNSPECIFIED,EXACTLY,AT_MOST,我们看它们单词的意思就好理解了,第一个说不确定,就叫它自己看着办;第二个意思是精确的,即我们生活中确定以及肯定,它会禁它最大的努力去按第一个参数的值测量,你的值填的离谱,它也满足不了.第三个就和第二个挺像了,尽量,就是比精确的肯定性差一点.所以我们如果自己确定一个测量规范的话,我确定我要侧滑面板占屏幕的三分之二,所以我们这里用EXACTLY.测量好了,接着就是我们的布局了

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mSl_width = mSlideView.getWidth();
        mSlideView.layout(mSlideView.getLeft() -  mSl_width /3 ,mSlideView.getTop(),mSlideView.getRight(),mSlideView.getBottom());
    }

布局的时候,说明测量已经完成了,所以我们是可以拿到我们左面板的宽度的,然后执行layout(l,t,r,b)看参数就明白了,这个不讲了,我把它的left设为-自己看的三分之一,是自己为了做的像一点的想法.来看看我们重写测量布局后的效果

好了,这时候开始我们的拖动了.视图拖动我们用这个类ViewDragHelper,视图拖动帮助者,首选看看怎么得到它的对象static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)它是个静态方法,所以我们不是new出来的,第一个参数说拖动VIEW的父容器是谁,因为我们拖动的是主面板和左面板,所以他们的父容器是自己,即填this,第二个参数是灵敏度,它还有一个重载函数,只有两个参数,这个参数没写默认是1.0f,所以我们一般也填1.0f.第三个参数是ViewDragHelper 的内部类Callback ,我们需要写个类继承它.

class MyDragCallBack extends ViewDragHelper.Callback {

        @Override  //表示要捕获那个孩子,即处理哪个孩子的拖到事件,返回false不处理
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

这个是抽象方法,我们必须实现,因为两个孩子都要拖动,所以直接返回true,要像横向拖动,我们还得重写它的一个方法

   @Override //
        public int clampViewPositionHorizontal(View child, int left, int dx) {  //left相对于屏幕左侧的偏移值,是拖动的建议值

            if (child == mMainView){            //主面板拖动范围
                if (left < 0){
                    left = 0;
                }else if (left > (mSl_width)){
                    left = mSl_width;
                }

            }else if (child == mSlideView){      //左面板拖动范围
                if (left > 0){
                    left = 0;
                }else if (left < -mSl_width){
                    left = mSl_width;
                }

            }
            return left;
        }

它默认是返回0的,即拖动不了的,同理它还有纵向的,这里我们不需要,所以不重新,到这里还是拖不动的,因为,我们的ViewDragHelper拚什么拖动呢,我们还要把touch事件传递给他,它才能决定该不该拖动

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        return mViewDragHelper.shouldInterceptTouchEvent(event);   //交给它去判断该不该拦截
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        try {

            mViewDragHelper.processTouchEvent(event);    //有可能会出错,所以try一下
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

好了,这里就可以拖动了,但是,只是一个view拖动了,另一个并不跟着动啊,所以我们还要另外重写一个函数,onViewPositionChanged看名字就知道了吧,拖动的view改变的时候调用,所以我们在这里动态的layout就好了

@Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);

            if (changedView == mMainView){

                mSlideView.layout( left/3 - mSl_width/3,mSlideView.getTop(),left/3 - mSl_width/3+mSl_width,mSlideView.getBottom());

            }else if (changedView == mSlideView){

                mMainView.layout(mSl_width + left,mMainView.getTop(),mSl_width + left + mWd_width,mMainView.getBottom());
            }

            invalidate();
        }

还有,当松开手的时候,我们应该看看左面板滑出来多少了,然后根据值判断是不是该关闭,即我们重写onViewReleased这个方法

@Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {

            if (mMainView.getLeft() < mSl_width/2){

                closeMenu();
            }else {

                openMenu();

            }
        }

 @Override
    public void computeScroll() {  //不停计算,不停调用
        if (mViewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(this);  //不停刷新直到停止滑动
        }
    }

    public void closeMenu() {

        mViewDragHelper.smoothSlideViewTo(mMainView,0,mMainView.getTop());
        ViewCompat.postInvalidateOnAnimation(this);  //兼容,刷新界面.
        mMenuIsOpen = false;

    }

    public void openMenu() {

        mViewDragHelper.smoothSlideViewTo(mMainView,mSl_width,mMainView.getTop());
        ViewCompat.postInvalidateOnAnimation(this);
        mSlideView.layout(0,mSlideView.getTop(),mSl_width,mSlideView.getBottom());
        mMenuIsOpen = true;
    }

在这里介绍下smoothSlideViewTo(View child, int finalLeft, int finalTop)看参数就明白了是吧,但是实现它的效果我们要ViewCompat.postInvalidateOnAnimation(this)刷新界面,这是兼容国产各大rom的,还要重写computeScroll()这个方法,continueSettling(true)返回true表示还没滑玩,所以继续刷新界面.好了到这里我们就做的差不多了,来看看效果吧

好吧,为什么会生成倒的gif图片我也不知道,因为总是大了,弄了好几次,把握不到度啊,治疗治疗颈椎病吧.看到这应该发现问题了吧,当在ViewGroup和listView上左右滑时划不动.看到这也许有人说了,去ononInterceptTouchEvent那设置滑动判断然后返回true拦截事件,再去Listview 的onTouchEvent判断返回false表示我不消费这个事件,我想说我都做了,而且log显示进入了我的判断拦截事件.还是不行,我都开始怀疑人生.怀疑我对touch事件的认知,就去网上看别人怎么说,说的都是我认知的,都不能解决这个问题.真的而且36度的天啊,很热,很沮丧的感觉.反正弄了将近一天,真的////各种自己重写view拦截…全部没用…所以没原理没方法真的基本做不出来..突然重写了ViewDragHelper.callback的一个方法就好了,是的,就是这个方法,我真的无法解释,我都无语了.因为我想我事件都交给ViewDragHelper处理了,点ViewDragHelper.shouldInterceptTouchEvent(event)进去看源码,看不懂…看到一个变量名有drag什么,所以试一试的心态重写了这个方法的.真的不知道这是撒原理,平常我们不重写这个不是也能拖动么,所以很少重写,以后我每次都重写了

 @Override
        public int getViewHorizontalDragRange(View child) {
            // 返回拖拽的范围
            return mSl_width/3 * 2;
        }

重写后效果就能拖了,各控件的touch事件也正常,到这基本完了,最后来弄我们的拖动监听吧

public interface OnSlideListener{
        /**
         * @param view    //触摸的是主界面还是菜单界面
         * @param left  //滑动了多长,负数向左滑,正数向右
         * @param persent //滑动相对于总长度的百分比
         */
        void onSlide(View view,int left,float persent);
    }

    private OnSlideListener mOnSlideListener;
    public void setOnSlideListener(OnSlideListener listener){
        mOnSlideListener = listener;
    }

在 onViewPositionChanged中添加


            if (mOnSlideListener != null){
                mOnSlideListener.onSlide(changedView,left,left * 1.0f / mSl_width);
            }

嗯,这里还是贴下完整代码吧,等下资源审核通过后我把项目文件下载路径放在最下面,感兴趣的朋友可以去下载看看

import android.content.Context;
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.widget.FrameLayout;

/**
 * Created by Root on 2016/6/20.
 */
public class SlideLayout extends FrameLayout{

    private View mSlideView;
    private View mMainView;
    private ViewDragHelper mViewDragHelper;
    private int mWd_width;
    private int mWd_height;
    private int mSl_width;
    private boolean mMenuIsOpen;

    public SlideLayout(Context context) {
        this(context,null);
    }

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

    public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mViewDragHelper = ViewDragHelper.create(this,1.0f,new MyDragCallBack());

    }

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

        mSlideView = getChildAt(0);
        mMainView = getChildAt(1);

        mWd_width = MeasureSpec.getSize(widthMeasureSpec);     //使用测量说明书得到我们想要知道的宽度和高度值
        mWd_height = MeasureSpec.getSize(heightMeasureSpec);

        int sl_widthSpec = MeasureSpec.makeMeasureSpec((mWd_width /3) * 2,MeasureSpec.EXACTLY); //制作测量说明书规定值
        int sl_heightSpec = MeasureSpec.makeMeasureSpec(mWd_height,MeasureSpec.EXACTLY);
        mSlideView.measure(sl_widthSpec,sl_heightSpec);

        mMainView.measure(widthMeasureSpec,heightMeasureSpec);   //主面板和自己一样,都是填充整个窗体,所以测量说明书的规定值一样

        setMeasuredDimension(mWd_width, mWd_height);            //测量自己用这个方法.
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mSl_width = mSlideView.getWidth();
        mSlideView.layout(mSlideView.getLeft() -  mSl_width /3 ,mSlideView.getTop(),mSlideView.getRight(),mSlideView.getBottom());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        return mViewDragHelper.shouldInterceptTouchEvent(event);   //交给它去判断该不该拦截
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        try {

            mViewDragHelper.processTouchEvent(event);    //有可能会出错,所以try一下
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    class MyDragCallBack extends ViewDragHelper.Callback {

        @Override  //表示要捕获那个孩子,即处理哪个孩子的拖到事件,返回false不处理
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        @Override //
        public int clampViewPositionHorizontal(View child, int left, int dx) {  //left相对于屏幕左侧的偏移值,是拖动的建议值

            if (child == mMainView){            //主面板拖动范围
                if (left < 0){
                    left = 0;
                }else if (left > (mSl_width)){
                    left = mSl_width;
                }

            }else if (child == mSlideView){      //左面板拖动范围
                if (left > 0){
                    left = 0;
                }else if (left < -mSl_width){
                    left = mSl_width;
                }

            }
            return left;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            // 返回拖拽的范围
            return mSl_width/3 * 2;
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);

            if (changedView == mMainView){

                mSlideView.layout( left/3 - mSl_width/3,mSlideView.getTop(),left/3 - mSl_width/3+mSl_width,mSlideView.getBottom());

            }else if (changedView == mSlideView){

                mMainView.layout(mSl_width + left,mMainView.getTop(),mSl_width + left + mWd_width,mMainView.getBottom());
            }

            if (mOnSlideListener != null){
                mOnSlideListener.onSlide(changedView,left,left * 1.0f / mSl_width);
            }

            invalidate();
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {

            if (mMainView.getLeft() < mSl_width/2){

                closeMenu();
            }else {

                openMenu();

            }
        }

    }

    @Override
    public void computeScroll() {  //不停计算,不停调用
        if (mViewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(this);  //不停刷新直到停止滑动
        }
    }

    public void closeMenu() {

        mViewDragHelper.smoothSlideViewTo(mMainView,0,mMainView.getTop());
        ViewCompat.postInvalidateOnAnimation(this);  //兼容,刷新界面.
        mMenuIsOpen = false;

    }

    public void openMenu() {

        mViewDragHelper.smoothSlideViewTo(mMainView,mSl_width,mMainView.getTop());
        ViewCompat.postInvalidateOnAnimation(this);
        mSlideView.layout(0,mSlideView.getTop(),mSl_width,mSlideView.getBottom());
        mMenuIsOpen = true;
    }

    public boolean isMenuIsOpen(){
        return mMenuIsOpen;
    }

    public interface OnSlideListener{
        /**
         * @param view    //触摸的是主界面还是菜单界面
         * @param left  //滑动了多长,负数向左滑,正数向右
         * @param persent //滑动相对于总长度的百分比
         */
        void onSlide(View view,int left,float persent);
    }

    private OnSlideListener mOnSlideListener;
    public void setOnSlideListener(OnSlideListener listener){
        mOnSlideListener = listener;
    }

}

完整代码下载路径http://download.csdn.net/detail/z8z87878/9556794

时间: 2024-10-30 20:38:42

鹅厂系列一 : 仿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

仿QQ侧滑删除

模仿QQ侧滑删除,需要的可以学习下 下载地址:http://www.devstore.cn/code/info/788.html 运行截图:

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

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

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

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

仿QQ侧滑删除ListView——2015第一博

一直感觉QQ最近联系人那个侧滑删除功能挺高大上的,经过几经波折,终于在新的一年里实现了该功能.实现这个功能真是费了老劲了,好几次有了想法,兴奋的去写代码实现,结果让代码打了自己一个耳光,最终还是用margin的方式实现了这种效果,好吧, 先上效果! 看完效果,就来说一下思路吧: 1.item的左右滑动效果我是用的magin实现的. 2.虽然item布局的时候文本TextView的宽度设置的是match_parent,但在点下去的时候就将这个值设置为了固定值:屏幕的宽度 3.通过提供一个方法来处理

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

好了,还是老规矩,先给出效果图,这里就绘制了一个简单框架,各位看官可以任意添加自己的东西. 下面我来解释一下怎么使用我们的ViewDragHelper来实现这个效果 先给出我们的布局 <?xml version="1.0" encoding="UTF-8"?> <com.jeason.qqmenudemo.widget.SideslipLayout xmlns:android="http://schemas.android.com/apk

OC仿QQ侧滑

之前做侧滑用的控件的DDMenu,总感觉好像差了点什么,自己尝试写了一个,三层叠加,感觉效果不理想,偶然间看到了一篇博客,与大家分享,再次申明,该代码不是我写的,只是为了给自己留一个查找资料的机会 下载地址:https://github.com/WiitterSimithYU/CWLateralSlide 博客地址:https://mp.weixin.qq.com/s/N9zPQ4NweEHRh768wPk1qA 原文地址:https://www.cnblogs.com/hualuoshuiji