Touch事件分发 -- 酷狗侧滑菜单效果

  1. 说明
    前边几节课我们已经把自定义View和自定义ViewGroup的套路及源码已经讲解完了,那么我们接下来就会带大家去做一些炫酷的效果,来充分把我们之前所讲解的这一波理论知识来实践一遍,来操作一遍,要不然如果只是讲解理论知识,而不去实践,那么我觉得这个是没什么卵用的,废话不多说,下边就开始我们今天的课程,我们今天的任务就是做一个 —— 酷狗侧滑菜单效果。

  2. 思路分析
    目前像这种侧边栏滑动的效果可以实现的方式有很多,我们就来罗列下几种常用的方式。
    2.1 直接用系统的DrawerLayout 这个控件一般直接是用作侧滑效果
    2.2 自定义ViewGroup + 手势处理类 (处理比较麻烦,代码也比较多)
    2.3 自定义ScrollView

  3. 代码实现分析
    3.1 自己定义一个类继承HorizontalScrollView,写好两个布局文件(menu,content),表示侧边栏的布局文件和主页面的布局文件,运行后看看效果;
    3.2 运行后布局全是乱套的,menu和content的宽度不对,我们的应对方法就是:content宽度就是屏幕宽度,menu宽度就是屏幕宽度 - 最右边的一段距离(可以用自定义属性);
    3.3 该侧滑默认是关闭的,手指抬起的时候要判断是关闭的还是打开的状态,然后用代码滚动到它对应的位置;
    3.4 处理快速滑动;
    3.5 处理内容部分缩放,菜单部分有位移和透明度,需要时时刻刻监听当前的滚动位置;
    3.6 充分考虑前几次看的源码(理论+实践)

在这里需要注意一个问题,就是菜单打开和菜单关闭时候的getScrollX()的值,可能有些小伙伴分不清楚,这里直接看这张图就可以。


分析菜单打开和关闭.png

自定义侧边栏SlidingMeun代码如下:

/**
 * Email 2185134304@qq.com
 * Created by JackChen on 2018/2/10.
 * Version 1.0
 * Description:
 */
public class SlidingMenu extends HorizontalScrollView {

    //菜单宽度
    private int mMenuWidth;
    //内容的View、菜单的View
    private View mContentView , mMenuView ;

    //处理快速滑动
    GestureDetector mGestureDetector ;

    //判断菜单是否打开
    boolean mMenuIsOpen = false ;

    //是否拦截
    boolean mIsIntercept = false ;

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

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

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

        // 初始化自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);

        float rightMargin = array.getDimension(
                R.styleable.SlidingMenu_menuRightMargin, ScreenUtils.dip2px(context, 50));
        // 菜单页的宽度是 = 屏幕的宽度 - 右边的一小部分距离(自定义属性)
        mMenuWidth = (int) (getScreenWidth(context) - rightMargin);
        array.recycle();

        mGestureDetector = new GestureDetector(context , mGestureListener) ;
    }


    private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            //快速往左边滑是负数,往右边滑动是正数
            Log.e("TAG", "velocityX -> " + velocityX);

            //由于我们只关心快速滑动,只要快速滑动就会回调onFling
            //当打开的时候,需要快速向右边滑动,就让关闭
            //当关闭的时候,需要快速向左边滑动,就让打开
            if (mMenuIsOpen){
                //关闭
                if (velocityX < 0){
                    closeMenu();
                    return true ;
                }
            }else{
                //打开
                if (velocityX > 0){
                    openMenu();
                    return true ;
                }
            }

            return super.onFling(e1, e2, velocityX, velocityY);

        }
    } ;


    //1.运行之后发现宽度不对(乱套了),这个时候需要去指定宽度,使用下边方法
    //这个方法是布局解析完毕后 即setContentView之后调用 也就是XML文件解析完毕后调用
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        /* ========== 指定宽高 START ========== */
        //1.内容页的宽度 = 屏幕的宽度
        //由于activity_main布局是 LinearLayout包裹了layout_home_menu和layout_home_content,
        // 所以先获取LinearLayout,然后从LinearLayout中获取2个子View

        //获取LinearLayout  这个为什么不是根布局 com.view.day12.SlidingMeun
        ViewGroup container = (ViewGroup) getChildAt(0);

        //这里获取LinearLayout容器中所有子View个数,判断只能放置2个子View,如果不是2个则抛异常
        int childCount = container.getChildCount();
        if (childCount != 2){
            throw new RuntimeException("只能放置两个子View!") ;
        }
        mMenuView = container.getChildAt(0);//获取LinearLayout的第一个子View,即菜单页
        ViewGroup.LayoutParams menuParams = mMenuView.getLayoutParams();//设置宽高只能通过 LayoutParams
        menuParams.width = mMenuWidth ;
        mMenuView.setLayoutParams(menuParams);//7.0以下手机必须采用下边的方式


        //2.菜单页宽度 = 屏幕宽度 - 右边一小段距离(自定义属性)
        mContentView = container.getChildAt(1) ;  //获取LinearLayout的第二个子View,即内容页
        ViewGroup.LayoutParams contentParams = mContentView.getLayoutParams();
        contentParams.width=getScreenWidth(getContext()) ;
        mContentView.setLayoutParams(contentParams);//7.0以下手机必须采用下边的方式

        /*  此时侧滑默认是打开的 , 我们需要将侧滑关闭,需要调用smoothScrollTo()来关闭,并且需要在onLayout()方法调用*/

    }



    //4. 处理右边的缩放,左边呢的缩放和透明度,需要不断的获取当前滚动的位置
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        // 算一个梯度值
        float scale = 1f * l / mMenuWidth;// scale 变化是 1 - 0
        // 右边的缩放: 最小是 0.7f, 最大是 1f
        float rightScale = 0.7f + 0.3f * scale;
        // 设置右边的缩放,默认是以中心点缩放
        // 设置缩放的中心点位置
        ViewCompat.setPivotX(mContentView, 0);
        ViewCompat.setPivotY(mContentView, mContentView.getMeasuredHeight() / 2);
        ViewCompat.setScaleX(mContentView,rightScale);
        ViewCompat.setScaleY(mContentView, rightScale);

        // 菜单的缩放和透明度
        // 透明度是 半透明到完全透明  0.5f - 1.0f
        float leftAlpha = 0.5f + (1-scale)*0.5f;
        ViewCompat.setAlpha(mMenuView,leftAlpha);
        // 缩放 0.7f - 1.0f
        float leftScale = 0.7f + (1-scale)*0.3f;
        ViewCompat.setScaleX(mMenuView,leftScale);
        ViewCompat.setScaleY(mMenuView, leftScale);

        // 最后一个效果 退出这个按钮刚开始是在右边,安装我们目前的方式永远都是在左边
        // 设置平移,先看一个抽屉效果
        // ViewCompat.setTranslationX(mMenuView,l);
        // 平移 l*0.7f
        ViewCompat.setTranslationX(mMenuView, 0.25f*l);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        smoothScrollTo(mMenuWidth , 0);
    }


    //3.手指抬起时二选一,侧滑要么关闭、要么打开

    @Override
    public boolean onTouchEvent(MotionEvent ev) {


        //如果有拦截,就不要执行自己的onTouchEvent了
        if (mIsIntercept){
            return true ;
        }

        //这里需要注意:如果快速滑动了,下边的代码就不要执行了
        //这里我们需要把onTouchEvent交给mGestureDetector来处理
         if (mGestureDetector.onTouchEvent(ev)){
             return true ;
         }




        //获取手指滑动的速率,当其大于一定值就认为是快速滑动,GestureDetector(系统提供好的类)
        //当菜单打开的时候,手指触摸右边内容部分需要关闭菜单,这个时候点击头像是没有反应的,不让其响应点击事件,
        // 所以这里还需要事件拦截,想都不用想,肯定在onInterceptTouchEvent()方法处理

        if (ev.getAction() == MotionEvent.ACTION_UP){
            //这个时候只需要管手指抬起,根据当前滚动的距离来判断
            int currentX = getScrollX() ;

            //由我画的图三分析可知,如果getScrollX(),就需要关闭侧滑;否则打开
            if (currentX > mMenuWidth/2){
                closeMenu() ;
            }else{
                //打开
                openMenu() ;
            }

            //确保super.onTouchEvent(ev)不会执行
            return true ;
        }
        return super.onTouchEvent(ev);
    }


    /**
     * 当菜单打开时,点击右上角头像,让菜单关闭;当菜单关闭时点击头像,就会进入个人中心页面
     *
     * 只要当这个方法 return true,就表示拦截:
     *     所以需要判断:
     *          1.菜单是打开的情况
     *          2.点击了头像
     *          这个时候就return true,去拦截,不让响应点击事件即可
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        mIsIntercept = false ;

        if (mMenuIsOpen){
            float currentX = ev.getX() ;
            if (currentX > mMenuWidth){
                //1.关闭菜单
                closeMenu();
                //2.子View不需要相应任何事件的点击和触摸 , 拦截子View的事件,直接return true即可

                //这里需要注意:如果返回true,代表我会拦截子View的事件,但是我会相应自己的onTouchEvent事件
                mIsIntercept = true ;
                return true ;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 打开菜单 滚动到 0 的位置
     */
    private void openMenu() {
        // smoothScrollTo 有动画
        smoothScrollTo(0, 0);
        mMenuIsOpen = true ;
    }

    /**
     * 关闭菜单 滚动到 mMenuWidth 的位置
     */
    private void closeMenu() {
        smoothScrollTo(mMenuWidth, 0);
        mMenuIsOpen = false ;
    }

    /**
     * 获得屏幕高度
     *
     * @param context
     * @return
     */
    private int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }

    /**
     * Dip into pixels
     */
    private int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

layout_home_content和layout_home_menu布局文件如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:orientation="vertical">


    <TextView
        android:layout_width="wrap_content"
        android:layout_centerInParent="true"
        android:text="主页内容"
        android:layout_height="wrap_content" />

</RelativeLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="72dp"
        android:orientation="vertical">

        <LinearLayout
            android:id="@+id/enter_login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="23dp"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/user_head_iv"
                android:layout_width="56dp"
                android:layout_height="56dp"
                android:src="@drawable/morentouxiang" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="22dp"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/user_name_tv"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:drawablePadding="10dp"
                    android:drawableRight="@drawable/user_write_paint"
                    android:text="请登录"
                    android:textColor="#c6b178"
                    android:textSize="18dp" />

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="42dp"
                    android:orientation="horizontal">

                    <TextView
                        android:id="@+id/user_attention_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:drawablePadding="10dp"
                        android:text="关注 0"
                        android:textColor="#c6b178"
                        android:textSize="12dp" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="40dp"
                        android:drawablePadding="10dp"
                        android:text="粉丝 0"
                        android:textColor="#c6b178"
                        android:textSize="12dp" />

                </LinearLayout>
            </LinearLayout>
        </LinearLayout>

        <ListView
            android:id="@+id/menu_item_lv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:divider="@null"
            android:dividerHeight="0dp"
            android:layout_marginTop="60dp"/>

    </LinearLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="20dp"
        android:text="退出"
        android:textColor="#FFFFFF"
        android:layout_height="wrap_content" />
</RelativeLayout>

具体代码已上传至github:
https://github.com/shuai999/View_day12_1.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,093评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,064评论 25 707
  • 虽然用时奢侈,但既然画,就要努力画好。
    晶童阅读 144评论 0 1
  • 自从妈妈去世后我经常以泪洗面,痛心不已。女儿每每看到,也总是陪我一起落泪。于是,在我生日的那天,收到了一份意...
    吴语而笑阅读 234评论 0 2
  • 早上8点起床 走1个小时去驾校 1点从驾校走回学校 2点去打工 坐了1个小时车到目的地 3点半开始上班 6点终于吃...
    么么么扎阅读 115评论 0 0