仿QQ侧滑菜单效果

之前使用过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和CSDN,有兴趣和需要的同学可以看看

CSDN代码地址

Github代码地址

你可能感兴趣的:(UI,动画)