Android仿QQ5.0侧滑菜单ResideMenu源码分析
2015-03-22 01:14
323 查看
转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持!
原创博客地址:点击传送
AndroidResideMenu
github:https://github.com/SpecialCyCi/AndroidResideMenu csdn:http://download.csdn.net/detail/cym492224103/7887801
先看看如何使用:
把项目源码下载下来导入工程,可以看到
ResideMenu为引用工程,再看看如何使用这个引用工程来构建出ResideMenu,
1.先new一个ResideMenu对象
[java] view
plaincopyprint?
resideMenu = new ResideMenu(this);
2.设置它的背景图片
[java] view
plaincopyprint?
resideMenu.setBackground(R.drawable.menu_background);
3.绑定当前Activity
[java] view
plaincopyprint?
resideMenu.attachToActivity(this);
4.设置监听
[java] view
plaincopyprint?
resideMenu.setMenuListener(menuListener);
可以监听菜单打开和关闭状态
[java] view
plaincopyprint?
private ResideMenu.OnMenuListener menuListener = new ResideMenu.OnMenuListener() {
@Override
public void openMenu() {
Toast.makeText(mContext, "Menu is opened!", Toast.LENGTH_SHORT).show();
}
@Override
public void closeMenu() {
Toast.makeText(mContext, "Menu is closed!", Toast.LENGTH_SHORT).show();
}
};
5.设置内容缩放比例(0.1~1f)
[java] view
plaincopyprint?
//valid scale factor is between 0.0f and 1.0f. leftmenu'width is 150dip.
resideMenu.setScaleValue(0.6f);
6.创建子菜单
[java] view
plaincopyprint?
// create menu items;
itemHome = new ResideMenuItem(this, R.drawable.icon_home, "Home");
itemProfile = new ResideMenuItem(this, R.drawable.icon_profile, "Profile");
itemCalendar = new ResideMenuItem(this, R.drawable.icon_calendar, "Calendar");
itemSettings = new ResideMenuItem(this, R.drawable.icon_settings, "Settings");
7.设置点击事件及将刚创建的子菜单添加到侧换菜单中(可以看到它是通过常量来控制子菜单的添加位置)
[java] view
plaincopyprint?
itemHome.setOnClickListener(this);
itemProfile.setOnClickListener(this);
itemCalendar.setOnClickListener(this);
itemSettings.setOnClickListener(this);
resideMenu.addMenuItem(itemHome, ResideMenu.DIRECTION_LEFT);
resideMenu.addMenuItem(itemProfile, ResideMenu.DIRECTION_LEFT);
resideMenu.addMenuItem(itemCalendar, ResideMenu.DIRECTION_RIGHT);
resideMenu.addMenuItem(itemSettings, ResideMenu.DIRECTION_RIGHT);
8.设置title按钮的点击事件,设置左右菜单的开关
[java] view
plaincopyprint?
// You can disable a direction by setting ->
// resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);
findViewById(R.id.title_bar_left_menu).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resideMenu.openMenu(ResideMenu.DIRECTION_LEFT);
}
});
findViewById(R.id.title_bar_right_menu).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resideMenu.openMenu(ResideMenu.DIRECTION_RIGHT);
}
});
9.还重写了dispatchTouchEvent
[java] view
plaincopyprint?
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return resideMenu.dispatchTouchEvent(ev);
}
10.菜单关闭方法
[java] view
plaincopyprint?
resideMenu.closeMenu();
11.屏蔽菜单方法
[java] view
plaincopyprint?
// You can disable a direction by setting ->
// resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);
使用方法已经说完了,接下来,看看它的源码,先看看源码的项目结构。
很多人初学者都曾纠结,看源码,如何从何看起,我个人建议从上面使用的顺序看起,并且在看的时候要带个问题去看去思考,这样更容易理解。
上面的第一步是,创建ResideMenu对象,我们就看看ResideMenu的构造。
[java] view
plaincopyprint?
public ResideMenu(Context context) {
super(context);
initViews(context);
}
从上面代码,看到构造里面就一个初始化view,思考问题:如何初始化view及初始化了什么view。
[java] view
plaincopyprint?
private void initViews(Context context){
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.residemenu, this);
scrollViewLeftMenu = (ScrollView) findViewById(R.id.sv_left_menu);
scrollViewRightMenu = (ScrollView) findViewById(R.id.sv_right_menu);
imageViewShadow = (ImageView) findViewById(R.id.iv_shadow);
layoutLeftMenu = (LinearLayout) findViewById(R.id.layout_left_menu);
layoutRightMenu = (LinearLayout) findViewById(R.id.layout_right_menu);
imageViewBackground = (ImageView) findViewById(R.id.iv_background);
}
原理分析:从上面的代码可以看到,加载了一个residemenu的布局,先看布局
[java] view
plaincopyprint?
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_background"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/iv_shadow"
android:background="@drawable/shadow"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitXY"/>
<ScrollView
android:id="@+id/sv_left_menu"
android:scrollbars="none"
android:paddingLeft="30dp"
android:layout_width="150dp"
android:layout_height="fill_parent">
<LinearLayout
android:id="@+id/layout_left_menu"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</LinearLayout>
</ScrollView>
<ScrollView
android:id="@+id/sv_right_menu"
android:scrollbars="none"
android:paddingRight="30dp"
android:layout_width="150dp"
android:layout_height="fill_parent"
android:layout_gravity="right">
<LinearLayout
android:id="@+id/layout_right_menu"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right">
</LinearLayout>
</ScrollView>
</FrameLayout>
布局显示效果
从布局文件,以及显示效果我们可以看到,它是一个帧布局,第一个ImageView是背景,第二个ImageView是.9的阴影效果的图片(看下面的图),
两个(ScrollView包裹着一个LinerLayout),可以从上面图看到结构分别是左菜单和右菜单
[java] view
plaincopyprint?
<img src="http://img.blog.csdn.net/20140910100807704?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3ltNDkyMjI0MTAz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" style="font-family: Arial; background-color: rgb(255, 255, 255);" alt="" />
1.初始化布局以及布局文件分析完毕,2.接下来是设置背景图,初始化view的时候就已经拿到了背景控件,所以设置背景图也是非常好实现的事情了。
[java] view
plaincopyprint?
public void setBackground(int imageResrouce){
imageViewBackground.setImageResource(imageResrouce);
}
3.绑定activity,思考问题:它做了什么?
[java] view
plaincopyprint?
/**
* use the method to set up the activity which residemenu need to show;
*
* @param activity
*/
public void attachToActivity(Activity activity){
initValue(activity);
setShadowAdjustScaleXByOrientation();
viewDecor.addView(this, 0);
setViewPadding();
}
原理分析:绑定activity做了4件事情,分别是:
1.初始化参数:
[java] view
plaincopyprint?
private void initValue(Activity activity){
this.activity = activity;
leftMenuItems = new ArrayList<ResideMenuItem>();
rightMenuItems = new ArrayList<ResideMenuItem>();
ignoredViews = new ArrayList<View>();
viewDecor = (ViewGroup) activity.getWindow().getDecorView();
viewActivity = new TouchDisableView(this.activity);
View mContent = viewDecor.getChildAt(0);
viewDecor.removeViewAt(0);
viewActivity.setContent(mContent);
addView(viewActivity);
ViewGroup parent = (ViewGroup) scrollViewLeftMenu.getParent();
parent.removeView(scrollViewLeftMenu);
parent.removeView(scrollViewRightMenu);
}
2.正对横竖屏缩放比例进行调整
[java] view
plaincopyprint?
private void setShadowAdjustScaleXByOrientation(){
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
shadowAdjustScaleX = 0.034f;
shadowAdjustScaleY = 0.12f;
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
shadowAdjustScaleX = 0.06f;
shadowAdjustScaleY = 0.07f;
}
}
3.添加当前view
[java] view
plaincopyprint?
viewDecor.addView(this, 0);
4.设置view边距
[java] view
plaincopyprint?
/**
* we need the call the method before the menu show, because the
* padding of activity can't get at the moment of onCreateView();
*/
private void setViewPadding(){
this.setPadding(viewActivity.getPaddingLeft(),
viewActivity.getPaddingTop(),
viewActivity.getPaddingRight(),
viewActivity.getPaddingBottom());
}
4.设置监听,思考问题:它什么时候调用监听,原理分析:动画监听开始执行动画掉哦那个openMenu动画结束调用closeMenu,从此我们可以想到,但它调用openMenu(int direction)和closeMenu()都会设置这个监听。
[java] view
plaincopyprint?
private Animator.AnimatorListener animationListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
if (isOpened()){
showScrollViewMenu();
if (menuListener != null)
menuListener.openMenu();
}
}
@Override
public void onAnimationEnd(Animator animation) {
// reset the view;
if(isOpened()){
viewActivity.setTouchDisable(true);
viewActivity.setOnClickListener(viewActivityOnClickListener);
}else{
viewActivity.setTouchDisable(false);
viewActivity.setOnClickListener(null);
hideScrollViewMenu();
if (menuListener != null)
menuListener.closeMenu();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
5.设置内容缩放比例(0.1~1f),细心的同学会发现在当缩完成后还可以在往里面拉到更小,有种弹性的感觉,挺有趣的。但是有些人的需求不想要有这种弹性效果,我们可以通过修改源码修改这个弹性效果,找到getTargetScale这个方法,修改下面0.5这个数值。使用时设置了0.6的缩放比例,默认下面的弹性参数是0.5所以我们当缩完成后还可以在往里面拉0.1的比例。
[java] view
plaincopyprint?
private float getTargetScale(float currentRawX){
float scaleFloatX = ((currentRawX - lastRawX) / getScreenWidth()) * 0.75f;
scaleFloatX = scaleDirection == DIRECTION_RIGHT ? - scaleFloatX : scaleFloatX;
float targetScale = ViewHelper.getScaleX(viewActivity) - scaleFloatX;
targetScale = targetScale > 1.0f ? 1.0f : targetScale;
targetScale = targetScale < 0.5f ? 0.5f : targetScale;
return targetScale;
}
默认缩放比例:
[java] view
plaincopyprint?
//valid scale factor is between 0.0f and 1.0f.
private float mScaleValue = 0.5f;
[java] view
plaincopyprint?
AnimatorSet scaleDown_activity = buildScaleDownAnimation(viewActivity, mScaleValue, mScaleValue);
[java] view
plaincopyprint?
/**
* a helper method to build scale down animation;
*
* @param target
* @param targetScaleX
* @param targetScaleY
* @return
*/
private AnimatorSet buildScaleDownAnimation(View target,float targetScaleX,float targetScaleY){
AnimatorSet scaleDown = new AnimatorSet();
scaleDown.playTogether(
ObjectAnimator.ofFloat(target, "scaleX", targetScaleX),
ObjectAnimator.ofFloat(target, "scaleY", targetScaleY)
);
scaleDown.setInterpolator(AnimationUtils.loadInterpolator(activity,
android.R.anim.decelerate_interpolator));
scaleDown.setDuration(250);
return scaleDown;
}
6.创建子菜单,看下子菜单的构造,我们通过上面的学习,原理分析:我们可以猜测到,无非就是加载布局设置内容
[java] view
plaincopyprint?
public ResideMenuItem(Context context, int icon, String title) {
super(context);
initViews(context);
iv_icon.setImageResource(icon);
tv_title.setText(title);
}
private void initViews(Context context){
LayoutInflater inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.residemenu_item, this);
iv_icon = (ImageView) findViewById(R.id.iv_icon);
tv_title = (TextView) findViewById(R.id.tv_title);
}
布局文件:
[html] view
plaincopyprint?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingTop="30dp">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:scaleType="centerCrop"
android:id="@+id/iv_icon"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="18sp"
android:layout_marginLeft="10dp"
android:id="@+id/tv_title"/>
</LinearLayout>
显示效果图:
7.子菜单添加到侧换菜单中(可以看到它是通过常量来控制子菜单的添加位置)原理分析:根据不同的常量来区分添加不同菜单的子菜单
[java] view
plaincopyprint?
/**
* add a single items;
*
* @param menuItem
* @param direction
*/
public void addMenuItem(ResideMenuItem menuItem, int direction){
if (direction == DIRECTION_LEFT){
this.leftMenuItems.add(menuItem);
layoutLeftMenu.addView(menuItem);
}else{
this.rightMenuItems.add(menuItem);
layoutRightMenu.addView(menuItem);
}
}
8.设置title按钮的点击事件,设置左右菜单的开关,原理分析:先设置了缩放方向然后在设置动画,正如我们上面想的一样还设置了动画监听。
[java] view
plaincopyprint?
/**
* show the reside menu;
*/
public void openMenu(int direction){
setScaleDirection(direction);
isOpened = true;
AnimatorSet scaleDown_activity = buildScaleDownAnimation(viewActivity, mScaleValue, mScaleValue);
AnimatorSet scaleDown_shadow = buildScaleDownAnimation(imageViewShadow,
mScaleValue + shadowAdjustScaleX, mScaleValue + shadowAdjustScaleY);
AnimatorSet alpha_menu = buildMenuAnimation(scrollViewMenu, 1.0f);
scaleDown_shadow.addListener(animationListener);
scaleDown_activity.playTogether(scaleDown_shadow);
scaleDown_activity.playTogether(alpha_menu);
scaleDown_activity.start();
}
设置缩放方向及计算x,y轴位置。
[java] view
plaincopyprint?
private void setScaleDirection(int direction){
int screenWidth = getScreenWidth();
float pivotX;
float pivotY = getScreenHeight() * 0.5f;
if (direction == DIRECTION_LEFT){
scrollViewMenu = scrollViewLeftMenu;
pivotX = screenWidth * 1.5f;
}else{
scrollViewMenu = scrollViewRightMenu;
pivotX = screenWidth * -0.5f;
}
ViewHelper.setPivotX(viewActivity, pivotX);
ViewHelper.setPivotY(viewActivity, pivotY);
ViewHelper.setPivotX(imageViewShadow, pivotX);
ViewHelper.setPivotY(imageViewShadow, pivotY);
scaleDirection = direction;
}
9.重写dispatchTouchEvent,问题思考:如何到根据手指滑动自动缩放
如果还不了解,dispatchTouchEvent这个函数如何调用?什么时候调用?请先看看/article/1546778.html
[java] view
plaincopyprint?
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float currentActivityScaleX = ViewHelper.getScaleX(viewActivity);
if (currentActivityScaleX == 1.0f)
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(xOffset < -50 || xOffset > 50) {
pressedState = PRESSED_MOVE_HORIZANTAL;
ev.setAction(MotionEvent.ACTION_CANCEL);
}
} else if(pressedState == PRESSED_MOVE_HORIZANTAL) {
if (currentActivityScaleX < 0.95)
showScrollViewMenu();
float targetScale = getTargetScale(ev.getRawX());
ViewHelper.setScaleX(viewActivity, targetScale);
ViewHelper.setScaleY(viewActivity, targetScale);
ViewHelper.setScaleX(imageViewShadow, targetScale + shadowAdjustScaleX);
ViewHelper.setScaleY(imageViewShadow, targetScale + shadowAdjustScaleY);
ViewHelper.setAlpha(scrollViewMenu, (1 - targetScale) * 2.0f);
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 (currentActivityScaleX > 0.56f)
closeMenu();
else
openMenu(scaleDirection);
}else{
if (currentActivityScaleX < 0.94f){
openMenu(scaleDirection);
}else{
closeMenu();
}
}
break;
}
lastRawX = ev.getRawX();
return super.dispatchTouchEvent(ev);
}
上面代码量有点多,看上去有点晕,接下来我们来分别从按下、移动、放开、来原理分析:
MotionEvent.ACTION_DOWN:
记录了X,Y轴的坐标点,判断是否打开,设置了按下的状态为PRESSED_DOWN
MotionEvent.ACTION_MOVE:
拿到当前X,Y减去DOWN下记录下来的X,Y,这样得到了移动的X,Y,
然后判断如果如果移动的X,Y大于25或者小于-25就改变按下状态为PRESSED_MOVE_VERTICAL
如果移动的X,Y大于50或者小于-50就改变状态为PRESSED_MOVE_HORIZANTAL
状态为PRESSED_MOVE_HORIZANTAL就改变菜单主视图内容以及阴影图片大小,在改变的同时还设置了当前菜单的透明度。
MotionEvent.ACTION_UP:
判断是否菜单是否打开状态,在获取当前缩放的X比例,
判断比例小于0.56f,则关闭菜单,反正开启菜单。
看完后,我们在回去看看代码,就会发现其实也不过如此~!
10.菜单关闭方法,同样也设置了动画监听之前的想法也是成立的。
[java] view
plaincopyprint?
/**
* close the reslide menu;
*/
public void closeMenu(){
isOpened = false;
AnimatorSet scaleUp_activity = buildScaleUpAnimation(viewActivity, 1.0f, 1.0f);
AnimatorSet scaleUp_shadow = buildScaleUpAnimation(imageViewShadow, 1.0f, 1.0f);
AnimatorSet alpha_menu = buildMenuAnimation(scrollViewMenu, 0.0f);
scaleUp_activity.addListener(animationListener);
scaleUp_activity.playTogether(scaleUp_shadow);
scaleUp_activity.playTogether(alpha_menu);
scaleUp_activity.start();
}
11.屏蔽菜单方法
[java] view
plaincopyprint?
public void setSwipeDirectionDisable(int direction){
disabledSwipeDirection.add(direction);
}
[java] view
plaincopyprint?
private boolean isInDisableDirection(int direction){
return disabledSwipeDirection.contains(direction);
}
原理分析:在重写dispatchTouchEvent的时候,细心的同学应该会看到,ACTION_MOVE下面有个判断
[java] view
plaincopyprint?
if (isInIgnoredView || isInDisableDirection(scaleDirection))
如果这个方向的菜单被屏蔽了,就滑不出来了。
最后我们会发现我们一直都没说到TouchDisableView,其实initValue的时候就初始化了,它就是viewActivity,是我们的内容视图。
我们来看看它做了什么?
[java] view
plaincopyprint?
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
setMeasuredDimension(width, height);
final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width);
final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);
mContent.measure(contentWidth, contentHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
mContent.layout(0, 0, width, height);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mTouchDisabled;
}
void setTouchDisable(boolean disableTouch) {
mTouchDisabled = disableTouch;
}
boolean isTouchDisabled() {
return mTouchDisabled;
}
动态设置宽高,设置事件是否传递下去的flag。
好了,源码分析已完毕,喜欢这篇文章的就请关注我吧~!
原创博客地址:点击传送
AndroidResideMenu
github:https://github.com/SpecialCyCi/AndroidResideMenu csdn:http://download.csdn.net/detail/cym492224103/7887801
先看看如何使用:
把项目源码下载下来导入工程,可以看到
ResideMenu为引用工程,再看看如何使用这个引用工程来构建出ResideMenu,
1.先new一个ResideMenu对象
[java] view
plaincopyprint?
resideMenu = new ResideMenu(this);
2.设置它的背景图片
[java] view
plaincopyprint?
resideMenu.setBackground(R.drawable.menu_background);
3.绑定当前Activity
[java] view
plaincopyprint?
resideMenu.attachToActivity(this);
4.设置监听
[java] view
plaincopyprint?
resideMenu.setMenuListener(menuListener);
可以监听菜单打开和关闭状态
[java] view
plaincopyprint?
private ResideMenu.OnMenuListener menuListener = new ResideMenu.OnMenuListener() {
@Override
public void openMenu() {
Toast.makeText(mContext, "Menu is opened!", Toast.LENGTH_SHORT).show();
}
@Override
public void closeMenu() {
Toast.makeText(mContext, "Menu is closed!", Toast.LENGTH_SHORT).show();
}
};
5.设置内容缩放比例(0.1~1f)
[java] view
plaincopyprint?
//valid scale factor is between 0.0f and 1.0f. leftmenu'width is 150dip.
resideMenu.setScaleValue(0.6f);
6.创建子菜单
[java] view
plaincopyprint?
// create menu items;
itemHome = new ResideMenuItem(this, R.drawable.icon_home, "Home");
itemProfile = new ResideMenuItem(this, R.drawable.icon_profile, "Profile");
itemCalendar = new ResideMenuItem(this, R.drawable.icon_calendar, "Calendar");
itemSettings = new ResideMenuItem(this, R.drawable.icon_settings, "Settings");
7.设置点击事件及将刚创建的子菜单添加到侧换菜单中(可以看到它是通过常量来控制子菜单的添加位置)
[java] view
plaincopyprint?
itemHome.setOnClickListener(this);
itemProfile.setOnClickListener(this);
itemCalendar.setOnClickListener(this);
itemSettings.setOnClickListener(this);
resideMenu.addMenuItem(itemHome, ResideMenu.DIRECTION_LEFT);
resideMenu.addMenuItem(itemProfile, ResideMenu.DIRECTION_LEFT);
resideMenu.addMenuItem(itemCalendar, ResideMenu.DIRECTION_RIGHT);
resideMenu.addMenuItem(itemSettings, ResideMenu.DIRECTION_RIGHT);
8.设置title按钮的点击事件,设置左右菜单的开关
[java] view
plaincopyprint?
// You can disable a direction by setting ->
// resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);
findViewById(R.id.title_bar_left_menu).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resideMenu.openMenu(ResideMenu.DIRECTION_LEFT);
}
});
findViewById(R.id.title_bar_right_menu).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resideMenu.openMenu(ResideMenu.DIRECTION_RIGHT);
}
});
9.还重写了dispatchTouchEvent
[java] view
plaincopyprint?
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return resideMenu.dispatchTouchEvent(ev);
}
10.菜单关闭方法
[java] view
plaincopyprint?
resideMenu.closeMenu();
11.屏蔽菜单方法
[java] view
plaincopyprint?
// You can disable a direction by setting ->
// resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);
使用方法已经说完了,接下来,看看它的源码,先看看源码的项目结构。
很多人初学者都曾纠结,看源码,如何从何看起,我个人建议从上面使用的顺序看起,并且在看的时候要带个问题去看去思考,这样更容易理解。
上面的第一步是,创建ResideMenu对象,我们就看看ResideMenu的构造。
[java] view
plaincopyprint?
public ResideMenu(Context context) {
super(context);
initViews(context);
}
从上面代码,看到构造里面就一个初始化view,思考问题:如何初始化view及初始化了什么view。
[java] view
plaincopyprint?
private void initViews(Context context){
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.residemenu, this);
scrollViewLeftMenu = (ScrollView) findViewById(R.id.sv_left_menu);
scrollViewRightMenu = (ScrollView) findViewById(R.id.sv_right_menu);
imageViewShadow = (ImageView) findViewById(R.id.iv_shadow);
layoutLeftMenu = (LinearLayout) findViewById(R.id.layout_left_menu);
layoutRightMenu = (LinearLayout) findViewById(R.id.layout_right_menu);
imageViewBackground = (ImageView) findViewById(R.id.iv_background);
}
原理分析:从上面的代码可以看到,加载了一个residemenu的布局,先看布局
[java] view
plaincopyprint?
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_background"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/iv_shadow"
android:background="@drawable/shadow"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitXY"/>
<ScrollView
android:id="@+id/sv_left_menu"
android:scrollbars="none"
android:paddingLeft="30dp"
android:layout_width="150dp"
android:layout_height="fill_parent">
<LinearLayout
android:id="@+id/layout_left_menu"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</LinearLayout>
</ScrollView>
<ScrollView
android:id="@+id/sv_right_menu"
android:scrollbars="none"
android:paddingRight="30dp"
android:layout_width="150dp"
android:layout_height="fill_parent"
android:layout_gravity="right">
<LinearLayout
android:id="@+id/layout_right_menu"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right">
</LinearLayout>
</ScrollView>
</FrameLayout>
布局显示效果
从布局文件,以及显示效果我们可以看到,它是一个帧布局,第一个ImageView是背景,第二个ImageView是.9的阴影效果的图片(看下面的图),
两个(ScrollView包裹着一个LinerLayout),可以从上面图看到结构分别是左菜单和右菜单
[java] view
plaincopyprint?
<img src="http://img.blog.csdn.net/20140910100807704?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3ltNDkyMjI0MTAz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" style="font-family: Arial; background-color: rgb(255, 255, 255);" alt="" />
1.初始化布局以及布局文件分析完毕,2.接下来是设置背景图,初始化view的时候就已经拿到了背景控件,所以设置背景图也是非常好实现的事情了。
[java] view
plaincopyprint?
public void setBackground(int imageResrouce){
imageViewBackground.setImageResource(imageResrouce);
}
3.绑定activity,思考问题:它做了什么?
[java] view
plaincopyprint?
/**
* use the method to set up the activity which residemenu need to show;
*
* @param activity
*/
public void attachToActivity(Activity activity){
initValue(activity);
setShadowAdjustScaleXByOrientation();
viewDecor.addView(this, 0);
setViewPadding();
}
原理分析:绑定activity做了4件事情,分别是:
1.初始化参数:
[java] view
plaincopyprint?
private void initValue(Activity activity){
this.activity = activity;
leftMenuItems = new ArrayList<ResideMenuItem>();
rightMenuItems = new ArrayList<ResideMenuItem>();
ignoredViews = new ArrayList<View>();
viewDecor = (ViewGroup) activity.getWindow().getDecorView();
viewActivity = new TouchDisableView(this.activity);
View mContent = viewDecor.getChildAt(0);
viewDecor.removeViewAt(0);
viewActivity.setContent(mContent);
addView(viewActivity);
ViewGroup parent = (ViewGroup) scrollViewLeftMenu.getParent();
parent.removeView(scrollViewLeftMenu);
parent.removeView(scrollViewRightMenu);
}
2.正对横竖屏缩放比例进行调整
[java] view
plaincopyprint?
private void setShadowAdjustScaleXByOrientation(){
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
shadowAdjustScaleX = 0.034f;
shadowAdjustScaleY = 0.12f;
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
shadowAdjustScaleX = 0.06f;
shadowAdjustScaleY = 0.07f;
}
}
3.添加当前view
[java] view
plaincopyprint?
viewDecor.addView(this, 0);
4.设置view边距
[java] view
plaincopyprint?
/**
* we need the call the method before the menu show, because the
* padding of activity can't get at the moment of onCreateView();
*/
private void setViewPadding(){
this.setPadding(viewActivity.getPaddingLeft(),
viewActivity.getPaddingTop(),
viewActivity.getPaddingRight(),
viewActivity.getPaddingBottom());
}
4.设置监听,思考问题:它什么时候调用监听,原理分析:动画监听开始执行动画掉哦那个openMenu动画结束调用closeMenu,从此我们可以想到,但它调用openMenu(int direction)和closeMenu()都会设置这个监听。
[java] view
plaincopyprint?
private Animator.AnimatorListener animationListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
if (isOpened()){
showScrollViewMenu();
if (menuListener != null)
menuListener.openMenu();
}
}
@Override
public void onAnimationEnd(Animator animation) {
// reset the view;
if(isOpened()){
viewActivity.setTouchDisable(true);
viewActivity.setOnClickListener(viewActivityOnClickListener);
}else{
viewActivity.setTouchDisable(false);
viewActivity.setOnClickListener(null);
hideScrollViewMenu();
if (menuListener != null)
menuListener.closeMenu();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
5.设置内容缩放比例(0.1~1f),细心的同学会发现在当缩完成后还可以在往里面拉到更小,有种弹性的感觉,挺有趣的。但是有些人的需求不想要有这种弹性效果,我们可以通过修改源码修改这个弹性效果,找到getTargetScale这个方法,修改下面0.5这个数值。使用时设置了0.6的缩放比例,默认下面的弹性参数是0.5所以我们当缩完成后还可以在往里面拉0.1的比例。
[java] view
plaincopyprint?
private float getTargetScale(float currentRawX){
float scaleFloatX = ((currentRawX - lastRawX) / getScreenWidth()) * 0.75f;
scaleFloatX = scaleDirection == DIRECTION_RIGHT ? - scaleFloatX : scaleFloatX;
float targetScale = ViewHelper.getScaleX(viewActivity) - scaleFloatX;
targetScale = targetScale > 1.0f ? 1.0f : targetScale;
targetScale = targetScale < 0.5f ? 0.5f : targetScale;
return targetScale;
}
默认缩放比例:
[java] view
plaincopyprint?
//valid scale factor is between 0.0f and 1.0f.
private float mScaleValue = 0.5f;
[java] view
plaincopyprint?
AnimatorSet scaleDown_activity = buildScaleDownAnimation(viewActivity, mScaleValue, mScaleValue);
[java] view
plaincopyprint?
/**
* a helper method to build scale down animation;
*
* @param target
* @param targetScaleX
* @param targetScaleY
* @return
*/
private AnimatorSet buildScaleDownAnimation(View target,float targetScaleX,float targetScaleY){
AnimatorSet scaleDown = new AnimatorSet();
scaleDown.playTogether(
ObjectAnimator.ofFloat(target, "scaleX", targetScaleX),
ObjectAnimator.ofFloat(target, "scaleY", targetScaleY)
);
scaleDown.setInterpolator(AnimationUtils.loadInterpolator(activity,
android.R.anim.decelerate_interpolator));
scaleDown.setDuration(250);
return scaleDown;
}
6.创建子菜单,看下子菜单的构造,我们通过上面的学习,原理分析:我们可以猜测到,无非就是加载布局设置内容
[java] view
plaincopyprint?
public ResideMenuItem(Context context, int icon, String title) {
super(context);
initViews(context);
iv_icon.setImageResource(icon);
tv_title.setText(title);
}
private void initViews(Context context){
LayoutInflater inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.residemenu_item, this);
iv_icon = (ImageView) findViewById(R.id.iv_icon);
tv_title = (TextView) findViewById(R.id.tv_title);
}
布局文件:
[html] view
plaincopyprint?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingTop="30dp">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:scaleType="centerCrop"
android:id="@+id/iv_icon"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="18sp"
android:layout_marginLeft="10dp"
android:id="@+id/tv_title"/>
</LinearLayout>
显示效果图:
7.子菜单添加到侧换菜单中(可以看到它是通过常量来控制子菜单的添加位置)原理分析:根据不同的常量来区分添加不同菜单的子菜单
[java] view
plaincopyprint?
/**
* add a single items;
*
* @param menuItem
* @param direction
*/
public void addMenuItem(ResideMenuItem menuItem, int direction){
if (direction == DIRECTION_LEFT){
this.leftMenuItems.add(menuItem);
layoutLeftMenu.addView(menuItem);
}else{
this.rightMenuItems.add(menuItem);
layoutRightMenu.addView(menuItem);
}
}
8.设置title按钮的点击事件,设置左右菜单的开关,原理分析:先设置了缩放方向然后在设置动画,正如我们上面想的一样还设置了动画监听。
[java] view
plaincopyprint?
/**
* show the reside menu;
*/
public void openMenu(int direction){
setScaleDirection(direction);
isOpened = true;
AnimatorSet scaleDown_activity = buildScaleDownAnimation(viewActivity, mScaleValue, mScaleValue);
AnimatorSet scaleDown_shadow = buildScaleDownAnimation(imageViewShadow,
mScaleValue + shadowAdjustScaleX, mScaleValue + shadowAdjustScaleY);
AnimatorSet alpha_menu = buildMenuAnimation(scrollViewMenu, 1.0f);
scaleDown_shadow.addListener(animationListener);
scaleDown_activity.playTogether(scaleDown_shadow);
scaleDown_activity.playTogether(alpha_menu);
scaleDown_activity.start();
}
设置缩放方向及计算x,y轴位置。
[java] view
plaincopyprint?
private void setScaleDirection(int direction){
int screenWidth = getScreenWidth();
float pivotX;
float pivotY = getScreenHeight() * 0.5f;
if (direction == DIRECTION_LEFT){
scrollViewMenu = scrollViewLeftMenu;
pivotX = screenWidth * 1.5f;
}else{
scrollViewMenu = scrollViewRightMenu;
pivotX = screenWidth * -0.5f;
}
ViewHelper.setPivotX(viewActivity, pivotX);
ViewHelper.setPivotY(viewActivity, pivotY);
ViewHelper.setPivotX(imageViewShadow, pivotX);
ViewHelper.setPivotY(imageViewShadow, pivotY);
scaleDirection = direction;
}
9.重写dispatchTouchEvent,问题思考:如何到根据手指滑动自动缩放
如果还不了解,dispatchTouchEvent这个函数如何调用?什么时候调用?请先看看/article/1546778.html
[java] view
plaincopyprint?
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float currentActivityScaleX = ViewHelper.getScaleX(viewActivity);
if (currentActivityScaleX == 1.0f)
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(xOffset < -50 || xOffset > 50) {
pressedState = PRESSED_MOVE_HORIZANTAL;
ev.setAction(MotionEvent.ACTION_CANCEL);
}
} else if(pressedState == PRESSED_MOVE_HORIZANTAL) {
if (currentActivityScaleX < 0.95)
showScrollViewMenu();
float targetScale = getTargetScale(ev.getRawX());
ViewHelper.setScaleX(viewActivity, targetScale);
ViewHelper.setScaleY(viewActivity, targetScale);
ViewHelper.setScaleX(imageViewShadow, targetScale + shadowAdjustScaleX);
ViewHelper.setScaleY(imageViewShadow, targetScale + shadowAdjustScaleY);
ViewHelper.setAlpha(scrollViewMenu, (1 - targetScale) * 2.0f);
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 (currentActivityScaleX > 0.56f)
closeMenu();
else
openMenu(scaleDirection);
}else{
if (currentActivityScaleX < 0.94f){
openMenu(scaleDirection);
}else{
closeMenu();
}
}
break;
}
lastRawX = ev.getRawX();
return super.dispatchTouchEvent(ev);
}
上面代码量有点多,看上去有点晕,接下来我们来分别从按下、移动、放开、来原理分析:
MotionEvent.ACTION_DOWN:
记录了X,Y轴的坐标点,判断是否打开,设置了按下的状态为PRESSED_DOWN
MotionEvent.ACTION_MOVE:
拿到当前X,Y减去DOWN下记录下来的X,Y,这样得到了移动的X,Y,
然后判断如果如果移动的X,Y大于25或者小于-25就改变按下状态为PRESSED_MOVE_VERTICAL
如果移动的X,Y大于50或者小于-50就改变状态为PRESSED_MOVE_HORIZANTAL
状态为PRESSED_MOVE_HORIZANTAL就改变菜单主视图内容以及阴影图片大小,在改变的同时还设置了当前菜单的透明度。
MotionEvent.ACTION_UP:
判断是否菜单是否打开状态,在获取当前缩放的X比例,
判断比例小于0.56f,则关闭菜单,反正开启菜单。
看完后,我们在回去看看代码,就会发现其实也不过如此~!
10.菜单关闭方法,同样也设置了动画监听之前的想法也是成立的。
[java] view
plaincopyprint?
/**
* close the reslide menu;
*/
public void closeMenu(){
isOpened = false;
AnimatorSet scaleUp_activity = buildScaleUpAnimation(viewActivity, 1.0f, 1.0f);
AnimatorSet scaleUp_shadow = buildScaleUpAnimation(imageViewShadow, 1.0f, 1.0f);
AnimatorSet alpha_menu = buildMenuAnimation(scrollViewMenu, 0.0f);
scaleUp_activity.addListener(animationListener);
scaleUp_activity.playTogether(scaleUp_shadow);
scaleUp_activity.playTogether(alpha_menu);
scaleUp_activity.start();
}
11.屏蔽菜单方法
[java] view
plaincopyprint?
public void setSwipeDirectionDisable(int direction){
disabledSwipeDirection.add(direction);
}
[java] view
plaincopyprint?
private boolean isInDisableDirection(int direction){
return disabledSwipeDirection.contains(direction);
}
原理分析:在重写dispatchTouchEvent的时候,细心的同学应该会看到,ACTION_MOVE下面有个判断
[java] view
plaincopyprint?
if (isInIgnoredView || isInDisableDirection(scaleDirection))
如果这个方向的菜单被屏蔽了,就滑不出来了。
最后我们会发现我们一直都没说到TouchDisableView,其实initValue的时候就初始化了,它就是viewActivity,是我们的内容视图。
我们来看看它做了什么?
[java] view
plaincopyprint?
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
setMeasuredDimension(width, height);
final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width);
final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);
mContent.measure(contentWidth, contentHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
mContent.layout(0, 0, width, height);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mTouchDisabled;
}
void setTouchDisable(boolean disableTouch) {
mTouchDisabled = disableTouch;
}
boolean isTouchDisabled() {
return mTouchDisabled;
}
动态设置宽高,设置事件是否传递下去的flag。
好了,源码分析已完毕,喜欢这篇文章的就请关注我吧~!
相关文章推荐
- ym——Android仿QQ5.0侧滑菜单ResideMenu源码分析
- Android 自定义SlidingMenu 实现QQ5.0侧滑菜单动画效果
- 仿QQ5.0侧滑菜单【AndroidResideMenu】
- 使用AndroidResideMenu开源框架实现QQ5.0侧滑菜单
- Android基础入门教程——2.4.15 DrawerLayout(官方侧滑菜单)的简单使用
- 自己实现android侧滑菜单
- Android 侧滑菜单
- android 动画实现侧滑菜单效果
- Android:SlidingMenu(侧滑菜单)用法示例
- Android--高仿 QQ5.0 侧滑菜单效果 自定义控件
- Android笔记——Drawerlayout创建侧滑出菜单
- Android中DrawerLayout(仿QQ双向侧滑菜单效果)
- Android实现网易新闻客户端侧滑菜单(1)
- Android 自定义控件打造史上最简单的侧滑菜单
- android组件之DrawerLayout(抽屉导航)-- 侧滑菜单效果
- Android侧滑菜单(最简)
- Android基础入门教程——2.4.15 DrawerLayout(官方侧滑菜单)的简单使用
- Android自定义控件——侧滑菜单
- Android开发全程记录(三)——侧滑菜单的实现
- Android 侧滑菜单最简单的模板