您的位置:首页 > 移动开发 > Android开发

仿QQ侧滑菜单效果

2016-01-05 08:40 381 查看
之前使用过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代码地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  UI Android 动画