文档章节

仿QQ侧滑菜单效果

IAM四十二
 IAM四十二
发布于 2016/01/05 08:40
字数 1746
阅读 87
收藏 1

之前使用过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四十二
粉丝 0
博文 2
码字总数 2562
作品 0
朝阳
程序员
私信 提问
Android UI 特效大全

Android UI特效大全 总体传送门:http://git.oschina.net/bob4j/Android-UI 基本上项目中都有效果图可自行查看 , 并且有些项目中都有README.md 文件,使用前请先阅读以下。 1.弧形(圆形)菜单...

不正经啊不正经
2015/07/31
0
0
【压岁干货】精彩技术博客+优秀源码集锦

虽然2015年已经过了一月有余,但在中国,好像只有过了春节才算进入新的一年。眼看着这也春节倒计时了,大家好像又都忙了起来,赶项目赶项目…… 从DevStore整理了一些优秀的技术博客和源码作...

牵着蜗牛去西藏
2015/02/04
1K
4
留白/YQSlideMenuControllerDemo

YQSlideMenuControllerDemo 仿QQ左边菜单侧滑栏 ###先上图(已换小图) ###使用 ####导入 pod 'YQSlideMenController' ####初始化 self.window = [[UIWindow alloc] initWithFrame:[[UIScre......

留白
2015/06/02
0
0
自定义Android侧滑菜单(模仿QQ侧滑效果)

Android自带的侧滑菜单 使用代码 如下 侧滑和主页面内容用按钮和图片代替了, 这样基本的侧滑功能就实现了 下面自定义来实现类似于QQ的侧边栏效果 新建 获取屏幕相关的辅助类 ScreenUtils , ...

lanyu96
2018/12/23
0
0
“高仿手机 QQ 侧滑菜单” 3.0,支持 Swift 2.0

“高仿手机 QQ 侧滑菜单”开源项目 3.0 发布,支持 Swift 2.0。 再造 “手机QQ” 侧滑菜单: 1. 实现侧滑效果 2. 高仿左视图 3. 实现左右视图联动 此开源项目迁移到 Swift 2.0,增加精细到行...

JohnLui
2015/09/13
3.9K
8

没有更多内容

加载失败,请刷新页面

加载更多

Kubernetes云供应商架构的未来

首先,我想分享SIG的使命,因为我们用它来指导我们现在和将来的工作。从我们的章程中直接来看,SIG的使命是简化,开发和维护云供应商集成,作为Kubernetes集群的扩展或附加组件。这背后的动机...

Linux就该这么学
20分钟前
1
0
线程池没你想的那么简单

前言 原以为线程池还挺简单的(平时常用,也分析过原理),这次是想自己动手写一个线程池来更加深入的了解它;但在动手写的过程中落地到细节时发现并没想的那么容易。结合源码对比后确实不得...

crossoverJie
28分钟前
16
0
Scientific Linux开发停止 相关设备将迁移至CentOS上

在经历了将近14年的版本更迭之后,这个专注于科学领域的GNU/Linux发行版本不会发布下个重大版本更新--Scientific Linux 8了。 目前维护该发行版本的成员最终决定是时候休息了,今后将不再发布...

linuxCool
32分钟前
1
0
Redux

Redux概念 Redux = Reducer + Flux,数据层框架,将所有数据都存储到store中 Redux的工作流程 Antd的使用 安装npm install antd --save import 'antd/dist/antd.css'import { Input, Butto......

星闪海洋
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部