美文网首页技术Android程序员
高仿QQ6.1的侧滑菜单

高仿QQ6.1的侧滑菜单

作者: IAM四十二 | 来源:发表于2015-12-27 11:50 被阅读600次

    之前使用过SlideMenu,感觉是一个不错的UI交互方式,在最新的QQ6.1里看到最新的侧滑菜单,滑动主屏幕菜单才显示出来,因此就参考SlideMenu模拟了一个侧滑菜单,同时实现了底部设置按钮的点击事件。


    这里写图片描述

    从GitHub上的源码可以看到,SlideMenu最原始的做法是,通过属性动画,对View做了要实现QQ6.1上那种侧滑(类似于抽屉)的效果,Scale动画的内容必须完全抛弃了,因为View的大小是不能做改变的,只是对其位置做了整体的移动及移动时的动画实现。好了,废话不多说,上代码。

    • 1.首先是滑动屏幕时,根据滑动的位置实现相应的动画
     public boolean dispatchTouchEvent(MotionEvent ev) {
            float currentActivityTranslateX = ViewHelper.getTranslationX(viewActivity);
            // System.err.println("the currentActivityTranslateX is " +
            // currentActivityTranslateX);
    
            setScaleDirectionByRawX(ev.getRawX());
    
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    lastActionDownX = ev.getX();
                    lastActionDownY = ev.getY();
                    isInIgnoredView = isInIgnoredView(ev) && !isOpened();
                    pressedState = PRESSED_DOWN;
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    if (isInIgnoredView || isInDisableDirection(scaleDirection))
                        break;
    
                    if (pressedState != PRESSED_DOWN && pressedState != PRESSED_MOVE_HORIZANTAL)
                        break;
    
                    int xOffset = (int) (ev.getX() - lastActionDownX);
                    int yOffset = (int) (ev.getY() - lastActionDownY);
    
                    if (pressedState == PRESSED_DOWN) {
                        if (yOffset > 25 || yOffset < -25) {
                            pressedState = PRESSED_MOVE_VERTICAL;
                            break;
                        }
    
                        if (isOpened()) {
                            if (xOffset < -50) {
                                pressedState = PRESSED_MOVE_HORIZANTAL;
                                ev.setAction(MotionEvent.ACTION_CANCEL);
                            }
    
                        } else {
                            if (xOffset > 50) {
                                pressedState = PRESSED_MOVE_HORIZANTAL;
                                ev.setAction(MotionEvent.ACTION_CANCEL);
                            }
                        }
    
                    } else if (pressedState == PRESSED_MOVE_HORIZANTAL) {
    
                        if (currentActivityTranslateX < screenWidth * translateXParam) {
                            scrollViewMenu.setVisibility(View.VISIBLE);
                        }
    
                        float targetTranslateX = ev.getRawX();
                        if (targetTranslateX > screenWidth) {
                            targetTranslateX = screenWidth;
                        }
                        float moveX = targetTranslateX - MenuViewWidth;
                        if (moveX <= 0) {
                            moveX = 0;
                        }
    
                        ViewHelper.setTranslationX(viewActivity, moveX);
                        ViewHelper.setTranslationX(imageViewShadow, moveX);
                        //这里对scrollViewMenu位移距离乘以2,完全是为了是滑动时,动画效果明显一定,
                        //否则,若MenuViewWidth ,过于窄,将造成动画效果不明显。
                        ViewHelper.setTranslationX(scrollViewMenu, (targetTranslateX / screenWidth - 1) * MenuViewWidth * 2);
    
                        lastRawX = ev.getRawX();
                        return true;
                    }
    
                    break;
    
                case MotionEvent.ACTION_UP:
    
                    if (isInIgnoredView)
                        break;
                    if (pressedState != PRESSED_MOVE_HORIZANTAL)
                        break;
    
                    pressedState = PRESSED_DONE;
                    if (isOpened()) {
                        if (currentActivityTranslateX < screenWidth * 0.6) {
                            closeMenu();
                        } else {
                            openMenu(scaleDirection);
                        }
                    } else {
                        if (currentActivityTranslateX > screenWidth * 0.4) {
                            openMenu(scaleDirection);
                        } else {
                            closeMenu();
                        }
                    }
    
                    break;
    
            }
            lastRawX = ev.getRawX();
            return super.dispatchTouchEvent(ev);
        }
    

    这里主要是对原先的pressedState == PRESSED_MOVE_HORIZANTAL时的代码做了较大改动动画的实现不再是以Scale,而是以translationX做X轴方向的移动,同时scrollViewMenu(及菜单本身)的动画效果也由原来的alpha动画修改为translation动画。这里需要注意的是,scrollViewMenu需要做位移的大小和viewActivity(及窗口所在的view)是不同的,因为scrollViewMenu是从屏幕左侧看不见的地方移进来,而主屏幕恰好是移出去。关于这个拿张纸画一下相对位置,代码就明白了。同时对这个属性动画的移动大小,也需要做限制,手指一滑动,直接从屏幕从屏幕右侧飞出去,那也不是事儿。这里的translateXParam,可以当做是一个因子(范围在0-1.0之间),即滑动时菜单占据整个屏幕的百分比。可以作为参数,使用时在Activity里set一下。

        public void setTranslateXParam(float translateXParam) {
            this.translateXParam = translateXParam;
            this.translateXParam = translateXParam > 1.0f ? 1.0f : translateXParam;
            this.translateXParam = translateXParam < 0.0f ? 0.0f : translateXParam;
            MenuViewWidth = (1 - this.translateXParam) * screenWidth;
        }
    

    这里说明一下这个MenuViewWidth ,这个值的内容就是主屏幕侧滑后,最后可见部分的宽度。

    • 2.然后是当滑动位置达到某一个值时,菜单直接进行打开或关闭
      由上面的代码,可以看这里是认为菜单未打开时时,手指位置大于屏幕的2/5即认为用户想要打开菜单,就会直接打开菜单,否则保持关闭;而菜单已经在打开时,手指位置小于屏幕的3/5即认为用户想要关闭菜单,就会直接关闭菜单,否则仍保持打开状态。当然实际使用中这个值可以根据自身需求作调整。打开或关闭菜单时相应的动画在原来的基础上也做了对应的调整,代码如下:
    /**
     * 打开菜单; 
     * */ 
        public void openMenu(int direction) {
            setScaleDirection(direction);
            isOpened = true;
            AnimatorSet scaleDown_activity = buildActivityDownAnimation(viewActivity);
            AnimatorSet scaleDown_menu = buildActivityDownAnimation(scrollViewMenu);
            AnimatorSet scaleDown_shadow = buildActivityDownAnimation(imageViewShadow);
            scaleDown_shadow.addListener(animationListener);
            scaleDown_activity.playTogether(scaleDown_shadow);
            scaleDown_activity.playTogether(scaleDown_menu);
            scaleDown_activity.start();
        } 
        /**
         * 关闭菜单;
         */
        public void closeMenu() {
    
            isOpened = false;
            AnimatorSet scaleUp_activity = buildActivityUpAnimation(viewActivity);
            AnimatorSet scaleUp_menu = buildActivityUpAnimation(scrollViewMenu);
            AnimatorSet scaleUp_shadow = buildActivityUpAnimation(imageViewShadow);
            scaleUp_activity.addListener(animationListener);
            scaleUp_activity.playTogether(scaleUp_shadow);
            scaleUp_activity.playTogether(scaleUp_menu);
            scaleUp_activity.start();
        }
    
    /**
     * 打开菜单时动画; 
    */ 
        private AnimatorSet buildActivityDownAnimation(View target) {
    
            AnimatorSet scaleDown = new AnimatorSet();
            float movex = 0.0f;
            if (target == scrollViewMenu) {
                movex = 0.0f;
            } else {
                movex = (float) (screenWidth * translateXParam);
            }
            scaleDown.play(ObjectAnimator.ofFloat(target, "translationX", movex));
            scaleDown.setInterpolator(AnimationUtils.loadInterpolator(activity, android.R.anim.linear_interpolator));
            scaleDown.setDuration(250);
            return scaleDown;
        }
    /** *
    关闭菜单时动画
     */
      private AnimatorSet buildActivityUpAnimation(View target) {
    
            AnimatorSet scaleUp = new AnimatorSet();
            if (target == scrollViewMenu) {
                scaleUp.play(ObjectAnimator.ofFloat(target, "translationX", -MenuViewWidth * 2));
            } else {
                scaleUp.play(ObjectAnimator.ofFloat(target, "translationX", 0.0f));
            }
            scaleUp.setInterpolator(AnimationUtils.loadInterpolator(activity, android.R.anim.linear_interpolator));
            scaleUp.setDuration(250);
            return scaleUp;
        }
    

    这里的动画的差值器使用了linear_interpolator,试了好几次发现translation动画用这个差值器,会让动画整体显得比较柔和一些,不显得过于突兀,当然这只是个人想法。

    • 3.使用ResideMenu
      接触了很多这种第三方的UI框架,觉得ResideMenu真的是很神奇,不用在XML文件中做布局,直接在所需使用的Activity做一些初始化工作即可,这一点显得很方便。
    public class MainActivity extends AppCompatActivity implements View.OnClickListener, ResideMenu.SettingLayoutListener {
    
        private ResideMenu resideMenu;
        String[] menuItems;
    
        private ResideMenuInfo info;
    
        private boolean is_closed = false;
        private long mExitTime;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
                }
            });
    
            setUpMenu();
    
        }
    
        private void setUpMenu() {
    
            // attach to current activity;
            resideMenu = new ResideMenu(this);
            resideMenu.setSettingListener(this);
            resideMenu.setBackground(R.drawable.menuback);
            resideMenu.attachToActivity(this);
            resideMenu.setMenuListener(menuListener);
            // valid scale factor is between 0.0f and 1.0f. leftmenu'width is
            // 150dip.
            resideMenu.setTranslateXParam(0.85f);
            // 禁止使用右侧菜单
            resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);
    
            // create menu items;
    
            menuItems =new String[] {"开通会员", "QQ钱包", "个性装扮", "我的收藏", "我的相册", "我的文件"};
            int[] icons = {R.drawable.gco, R.drawable.charge_icon, R.drawable.kwz, R.drawable.feo,
                    R.drawable.fdh, R.drawable.ept};
    
            for (int i = 0; i < menuItems.length; i++) {
                ResideMenuItem menuItem = new ResideMenuItem(this, icons[i], menuItems[i]);
                menuItem.setOnClickListener(this);
                //为了方便在Click方法中实现,这里手动添加一个id.
                menuItem.setId(i);
                resideMenu.addMenuItem(menuItem, ResideMenu.DIRECTION_LEFT);
    
            }
    
            info = new ResideMenuInfo(this, R.drawable.fsf, "魑魅魍魉", "32 级");
            resideMenu.addMenuInfo(info);
    
            info.setOnClickListener(this);
    
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            return resideMenu.dispatchTouchEvent(ev);
        }
    
        @Override
        public void onClick(View v) {
            String msg = " ";
            switch (v.getId()) {
                case 0:
                    msg = menuItems[0];
                    break;
                case 1:
                    msg = menuItems[1];
                    break;
                case 2:
                    msg = menuItems[2];
                    break;
                case 3:
                    msg = menuItems[3];
                    break;
                case 4:
                    msg = menuItems[4];
                    break;
                case 5:
                    msg = menuItems[5];
                    break;
                case 6:
                    msg = menuItems[6];
                    break;
                default:
                    msg = "This is default";
                    break;
            }
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    
        }
    
        @Override
        public void clickSetting() {
            // TODO Auto-generated method stub
            Toast.makeText(this, "设置", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void clickComment() {
            // TODO Auto-generated method stub
            Toast.makeText(this, "夜间", Toast.LENGTH_SHORT).show();
        }
    }
    

    最后在MainActivity里设置相应的listener,同时实现具体的方法即可。

    • 4.一些说明
      这里对SlideMenu的修改,只是考虑了左侧的侧滑菜单,完全忽视了右侧。右滑平时感觉用的较少,另一方面,SlideMenu以translation属性动画同时实现右侧,细思极恐,所以暂时没有考虑,有兴趣的同学可以说说想法。代码里使用的图片均来自网络,腾讯的那些icon也是度娘上搜的,只是想模仿的像一点而已,哈哈。所有的点击事件,只是实现了一个Toast,因为点击内容实现不是重点。这里实现的功能,可能还有一些瑕疵甚至bug是我没有发现的,欢迎大神们拍砖。最后,完整代码已上传到github,有兴趣和需要的同学可以看看

    Github代码地址


    相关文章

      网友评论

      • 初凉:内容不错,但手机上看,这个代码的排版也是醉了
        IAM四十二:@月凉 非常感谢你的建议,写这篇文章时,markdown语法还不太熟,所以有点乱,刚更新了一下

      本文标题:高仿QQ6.1的侧滑菜单

      本文链接:https://www.haomeiwen.com/subject/vnsbhttx.html