关闭

简单的侧滑菜单实现

1297人阅读 评论(0) 收藏 举报

本文参考自郭霖和鸿洋的多篇文章,无法逐一列举了

  1. 实现思路
    利用自定义的HorizontalScrollView实现。 HorizontalScrollView中管理两个视图,一个视图为“菜单”,另一个为“正文”。初始时“菜单”部分在屏幕可视区域以外。当利用HorizontalScrollView的滑动机制进行水平滑动时,将隐藏在屏幕外的“菜单”部分“拖入”屏幕可视区域。
  2. 效果增强
    以自定义的方式继承HorizontalScrollView并改写了其中的一些方法,可以在侧滑时增加一些滑动效果。比如滑动中“正文”的逐步缩小,“菜单”的逐步放大以及透明度的变化等。
  3. 实现步骤

    1. 初步示意图
      这里写图片描述
      初始时只会在屏幕中看见正文区域
      随着向右“滑动”,“菜单”会出现在屏幕的可视区域内
      这里写图片描述
    2. 代码实现
      写一个自定义的View继承自HorizontalScrollView
    MySlidingMenu extends HorizontalScrollView

    然后利用自定义的MySlidingMenu构建MainActivity中的布局

     <com.tarena.myviewtest.view.MySlidingMenu
            android:id="@+id/msm_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@mipmap/img_frame_background"
            app:menu_padding="100dp">
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:orientation="horizontal">
                <include
                    layout="@layout/left_menu_layout"
                />
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@mipmap/qq">
            </LinearLayout>
            </LinearLayout>
        </com.tarena.myviewtest.view.MySlidingMenu>

    其中left_menu_layout就是一个简单的LinearLayout,罗列了几个TextView形式的菜单项,正文区域是一幅图片

  4. 方法重写
    为了实现初始时“菜单”部分在屏幕可视区域的左侧,需要重写MySlidingMenu的一些方法。这些方法包括:
    onMeasure方法:在这里要确认一下菜单区域和正文区域的大小
    onLayout方法:在这里完成对MySlidingMenu中内容的“摆放”。
  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        wrapper = (LinearLayout) this.getChildAt(0);
        menu = wrapper.getChildAt(0);
        content = wrapper.getChildAt(1);
        menuWidth = screenWidth - menuRightPadding;
        menu.getLayoutParams().width = menuWidth;
        content.getLayoutParams().width = screenWidth;

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            this.scrollTo(menuWidth, 0);
        }
    }

在onMeasure方法中screenWidth是屏幕的宽度:

 DisplayMetrics outMetrics = new DisplayMetrics();
        ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(outMetrics);
        screenWidth = outMetrics.widthPixels;

menuRightPadding是“菜单”打开后,菜单距离屏幕右侧的距离。该值可以被当做一个自定义属性在布局文件中由使用者传入。默认值为80dp

 TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.MySlidingMenu);
        menuRightPadding = t.getDimensionPixelSize(R.styleable.MySlidingMenu_menu_padding,
                (int) TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_DIP, 80, getResources().getDisplayMetrics()));
        t.recycle();

在确定了“菜单”部分的宽度后,在onLayout方法中调用scrollTo方法,该方法就是将MySlidingMenu的“内容区域”的左边缘移动到MySlidingMenu左边缘的左侧,距离恰好是“菜单”的宽度。这样就保证了“菜单”恰好位于屏幕可视区域之外。
此时就可以运行代码,实现“菜单”的侧滑了。
这里写图片描述

  1. 效果增强
    基本效果实现了,接下来利用代码来实现效果的增强:
    如果在滑动过程中松手,菜单已经显示的区域如果大于菜单尺寸的1/2则应该自动的完成菜单显示的后续滑动,反之则应该让菜单完成收起的后续动作。
    上述逻辑写在MySlidingMenu的onTouchEvent中,因为HorizontalScrollView本身实现了一套onTouchEvent的逻辑,这里我们只针对MontionEvent的Action_Up动作即可:
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_UP) {
            int sx = getScrollX();
            if (sx >= menuWidth / 2) {
                smoothScrollTo(menuWidth, 0);
                isOpen = false;
            } else {
                smoothScrollTo(0, 0);
                isOpen = true;
            }
            return true;
        }
        return super.onTouchEvent(ev);
    }

这里在完成滑动时直接调用了HorizontalScrollView的smoothScrollTo方法,这样可以实现一个平滑的滑动过程,而不会像scrollTo那样瞬间完成滑动。

在上面的实现中,“菜单”是从左侧被“拖”出来的,接下来实现一种正文好像覆盖在“菜单”上面的感觉,先看示意图,对比一下两者的差异:
这里写图片描述
可以明显看出两者的效果差异吧。
要从“拖出”效果转为“覆盖”效果,仅仅需要一行代码:
重写一下父类的onScrollChanged方法即可:

@Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {

        ViewHelper.setTranslationX(menu, l);
        super.onScrollChanged(l, t, oldl, oldt);
    }

其中ViewHelper是nineoldandroids.jar中的一个类,nineoldandroids不用过多介绍了,它是一个兼容3.0以下版本实现属性动画的一个安卓类库。setTranslationX的意思就是设定“菜单”这个视图的translationX的值。方法中将menu视图的translationX的值设定为了onScrollChanged方法的第一个参数值。这是因为:
当MySlidingMenu的scrollTo方法被调用的时候(scrollBy和smoothScrollTo方法内部也是在调用scrollTo方法),scrollTo方法内部做的事情就是设定好滑动终止时的目标scrollX和scrollY的值以及滑动开始时的scrollX与scrollY的值,然后调用onScrollChanged方法,将这四个值作为onScrollChanged方法的四个参数,因此l参数的意思实际就是当滑动结束时“菜单”视图的scrollX的值。
MySlidingMenu的本身的视图宽度为屏幕的宽度,而MySlidingMenu中管理的内容是“菜单”视图+“正文”视图,内容的宽度是大于MySlidingMenu本身的宽度的,scrollX代表的就是MySlidingMenu现实的内容左边缘与MySlidingMenu本身左边缘的距离。
如果此时不做任何额外的操作,那么“菜单”视图停留在MySlidingMenu左侧边缘的左侧,距离MySlidingMenu左侧边缘scrollX个像素。现在希望它能够出现在可视区域中,也就是让“菜单”视图的左边缘恰好出现在MySlidingMenu的左边缘,那就需要让“菜单”视图从当前自己的左边缘开始再多滑动scrollX个像素的距离。如何滑动呢,就设定“菜单”视图的translationX属性值即可,translationX属性值代表让视图产生移动,视图移动后左侧边缘与视图left属性之间的距离(这部分内容的详情可以参考我的另一篇blogView的坐标系)。所以只要设定“菜单”视图的translationX属性值为滑动结束后的scrollX的值即可,这样每次滑动结束后,利用translationX再让“菜单”视图多滑动scrollX个像素值。这样就实现了每次滑动时,“菜单”视图始终都是左侧与MySlidingMenu的左侧保持一致的效果。

接下来再增加一些滑动中的效果。比如随着滑动,让“内容”区域产生一个由大到小的变化,让“菜单”区域产生一个由小到大的变化,并伴随一个透明度的变化:
这一切的发生都是随着滑动而发生的,因此这里需要找到一个随着滑动而不断变化的比例值:

@Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    float scale = l*1.0f/menuWidth;
        ViewHelper.setTranslationX(menu, l);
        super.onScrollChanged(l, t, oldl, oldt);
    }

随着滑动l的值会不断发生变化,但是“菜单”的宽度是在onMeasure方法中设定好的,并不会变化,而滑动的整体变化过程或者说l的取值范围就是从0到menuWidth。有了这个scale后,就可以利用它实现各种滑动过程中的变化了:

 @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        //随着滑动,l的值不断变化(从menuWidth到0)。将绝对值值转化为一个比值
        //scale的变化区间是1--->0
        float scale = l*1.0f/menuWidth;
        ViewHelper.setTranslationX(menu, l);
        //随着拖动,content区域由大到小从100%--->70%左右
        ViewHelper.setScaleX(content,0.7f+0.3f*scale);
        ViewHelper.setScaleY(content,0.7f+0.3f*scale);
        ViewHelper.setPivotX(content,0);
        ViewHelper.setPivotY(content,content.getHeight()/2);
        //随着拖动,菜单区域的透明度在变化,变化区间大概是0.7--->1
        ViewHelper.setAlpha(menu,0.5f+0.5f*(1-scale));
        //随着拖动,菜单区域的大小在变化变化区间大概是0.7--->1
        ViewHelper.setScaleX(menu,0.7f+0.3f*(1-scale));
        ViewHelper.setScaleY(menu,0.7f+0.3f*(1-scale));
        super.onScrollChanged(l, t, oldl, oldt);
    }

代码运行后的效果如图所示:
这里写图片描述

还可以再增加一点效果。此时的效果是随着拖动“菜单”视图恰好每次都是出现在MySlidingMenu的左边缘。如果每次不让“菜单”视图都移动scrollX的距离,而是移动的少一点,这样在MySlidingMenu的滑动过程除了上述已经有的效果外,还可以再带着一点“菜单”平滑拖出的效果。
只需要修改setTranslationX部分即可,原先移动l,现在让l乘以一个比例值,就可以不移动scrollX了:

 @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {

        //随着滑动,l的值不断变化(从menuWidth到0)。将绝对值值转化为一个比值
        //scale的变化区间是1--->0
        float scale = l*1.0f/menuWidth;
        //ViewHelper.setTranslationX(menu, l);
        ViewHelper.setTranslationX(menu, l*0.7f);
        //随着拖动,content区域由大到小
        //但是并不是到0,大概是从100%--->70%左右
        ViewHelper.setScaleX(content,0.7f+0.3f*scale);
        ViewHelper.setScaleY(content,0.7f+0.3f*scale);
        ViewHelper.setPivotX(content,0);
        ViewHelper.setPivotY(content,content.getHeight()/2);
        //随着拖动,菜单区域的透明度在变化
        //变化区间大概是0.7--->1
        ViewHelper.setAlpha(menu,0.5f+0.5f*(1-scale));
        //随着拖动,菜单区域的大小在变化
        //变化区间大概是0.7--->1
        ViewHelper.setScaleX(menu,0.7f+0.3f*(1-scale));
        ViewHelper.setScaleY(menu,0.7f+0.3f*(1-scale));

        super.onScrollChanged(l, t, oldl, oldt);

    }

看一下这次的运行效果:
这里写图片描述
可以看到“菜单”区域除了透明度、大小变化之外,还有了侧拉效果。

该策滑菜单的基本实现需要掌握自定义View的基本知识,以及scrollTo的用法。在效果增强部分,需要先理解View中的几个坐标点的差异包括left,scrollX,translationX等,这样才能逐步实现效果。

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:66491次
    • 积分:1384
    • 等级:
    • 排名:千里之外
    • 原创:58篇
    • 转载:84篇
    • 译文:0篇
    • 评论:16条
    文章分类
    最新评论