仿QQ侧滑菜单效果
2016-01-05 08:40
381 查看
之前使用过SlideMenu,感觉是一个不错的UI交互方式,在最新的QQ6.1里看到最新的侧滑菜单,滑动主屏幕菜单才显示出来,因此就参考SlideMenu模拟了一个侧滑菜单,同时实现了底部设置按钮的点击事件。
从GitHub上的源码可以看到,SlideMenu最原始的做法是,通过属性动画,对View做了要实现QQ6.1上那种侧滑(类似于抽屉)的效果,Scale动画的内容必须完全抛弃了,因为View的大小是不能做改变的,只是对其位置做了整体的移动及移动时的动画实现。好了,废话不多说,上代码。
1.首先是滑动屏幕时,根据滑动的位置实现相应的动画
这里主要是对原先的pressedState == PRESSED_MOVE_HORIZANTAL时的代码做了较大改动动画的实现不再是以Scale,而是以translationX做X轴方向的移动,同时scrollViewMenu(及菜单本身)的动画效果也由原来的alpha动画修改为translation动画。这里需要注意的是,scrollViewMenu需要做位移的大小和viewActivity(及窗口所在的view)是不同的,因为scrollViewMenu是从屏幕左侧看不见的地方移进来,而主屏幕恰好是移出去。关于这个拿张纸画一下相对位置,代码就明白了。同时对这个属性动画的移动大小,也需要做限制,手指一滑动,直接从屏幕从屏幕右侧飞出去,那也不是事儿。这里的translateXParam,可以当做是一个因子(范围在0-1.0之间),即滑动时菜单占据整个屏幕的百分比。可以作为参数,使用时在Activity里set一下。
这里说明一下这个MenuViewWidth ,这个值的内容就是主屏幕侧滑后,最后可见部分的宽度。
2.然后是当滑动位置达到某一个值时,菜单直接进行打开或关闭
由上面的代码,可以看这里是认为菜单未打开时时,手指位置大于屏幕的2/5即认为用户想要打开菜单,就会直接打开菜单,否则保持关闭;而菜单已经在打开时,手指位置小于屏幕的3/5即认为用户想要关闭菜单,就会直接关闭菜单,否则仍保持打开状态。当然实际使用中这个值可以根据自身需求作调整。打开或关闭菜单时相应的动画在原来的基础上也做了对应的调整,代码如下:
这里的动画的差值器使用了linear_interpolator,试了好几次发现translation动画用这个差值器,会让动画整体显得比较柔和一些,不显得过于突兀,当然这只是个人想法。
3.使用ResideMenu
接触了很多这种第三方的UI框架,觉得ResideMenu真的是很神奇,不用在XML文件中做布局,直接在所需使用的Activity做一些初始化工作即可,这一点显得很方便。
最后在MainActivity里设置相应的listener,同时实现具体的方法即可。
4.一些说明
这里对SlideMenu的修改,只是考虑了左侧的侧滑菜单,完全忽视了右侧。右滑平时感觉用的较少,另一方面,SlideMenu以translation属性动画同时实现右侧,细思极恐,所以暂时没有考虑,有兴趣的同学可以说说想法。代码里使用的图片均来自网络,腾讯的那些icon也是度娘上搜的,只是想模仿的像一点而已,哈哈。所有的点击事件,只是实现了一个Toast,因为点击内容实现不是重点。这里实现的功能,可能还有一些瑕疵甚至bug是我没有发现的,欢迎大神们拍砖。最后,完整代码已上传到github,有兴趣和需要的同学可以看看
Github代码地址
从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代码地址
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories