对于滑动菜单栏SlidingMenu,大家应该都不陌生,在市场上的一些APP应用里经常可以见到,比如人人网,FaceBook等。
前段时间QQ5.0版本出来后也采用了这种设计风格:(下面是效果图)
之前在GitHub上看到过关于此设计风格的开源项目,它只需要引入对应的类库,就可以定制灵活、各种阴影和渐变以及动画的滑动效果的侧滑菜单。
但作为开发人员,在学习阶段还是建议尽可能的去自己实现,所以今天我不讲此开源项目的使用方式,我们用自定义HorizontalScrollView来实现此效果。
下面先看下实现效果图:
上图的效果是用自定义HorizontalScrollView来实现的,在HorizontalScrollView里潜入一个横向排列的线性布局,然后在线性布局里分别加入菜单布局和内容布局,在我们初始化的时候把HorizontalScrollView的滚动条向左拉至左边菜单距离即可实现菜单布局的隐藏,关于缩放,移动效果我们可以使用开源动画库nineoldandroids来实现,只需要几行代码。
好了,接着直接上代码吧:
首先,先看下布局文件:
1、菜单栏布局文件:
1 <?xml version="1.0" encoding="utf-8"?>
2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent">
5
6 <LinearLayout
7 android:layout_width="match_parent"
8 android:layout_height="match_parent"
9 android:orientation="vertical" >
10
11 <RelativeLayout
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:layout_centerInParent="true" >
15
16 <ImageView
17 android:id="@+id/menuimage1"
18 android:layout_width="50dp"
19 android:layout_height="50dp"
20 android:layout_centerVertical="true"
21 android:layout_marginLeft="20dp"
22 android:layout_marginTop="20dp"
23 android:src="@drawable/img_1" />
24
25 <TextView
26 android:id="@+id/menutext1"
27 android:layout_width="wrap_content"
28 android:layout_height="wrap_content"
29 android:layout_centerVertical="true"
30 android:layout_marginLeft="20dp"
31 android:layout_marginTop="20dp"
32 android:layout_toRightOf="@id/menuimage1"
33 android:text="菜单一"
34 android:textColor="@android:color/white"
35 android:textSize="20dp" />
36 </RelativeLayout>
37
38
39 <RelativeLayout
40 android:layout_width="match_parent"
41 android:layout_height="wrap_content"
42 android:layout_centerInParent="true" >
43
44 <ImageView
45 android:id="@+id/menuimage2"
46 android:layout_width="50dp"
47 android:layout_height="50dp"
48 android:layout_centerVertical="true"
49 android:layout_marginLeft="20dp"
50 android:layout_marginTop="20dp"
51 android:src="@drawable/img_2" />
52
53 <TextView
54 android:id="@+id/menutext2"
55 android:layout_width="wrap_content"
56 android:layout_height="wrap_content"
57 android:layout_centerVertical="true"
58 android:layout_marginLeft="20dp"
59 android:layout_marginTop="20dp"
60 android:layout_toRightOf="@id/menuimage2"
61 android:text="菜单二"
62 android:textColor="@android:color/white"
63 android:textSize="20dp" />
64 </RelativeLayout>
65
66 <RelativeLayout
67 android:layout_width="match_parent"
68 android:layout_height="wrap_content"
69 android:layout_centerInParent="true" >
70
71 <ImageView
72 android:id="@+id/menuimage3"
73 android:layout_width="50dp"
74 android:layout_height="50dp"
75 android:layout_centerVertical="true"
76 android:layout_marginLeft="20dp"
77 android:layout_marginTop="20dp"
78 android:src="@drawable/img_3" />
79
80 <TextView
81 android:id="@+id/menutext3"
82 android:layout_width="wrap_content"
83 android:layout_height="wrap_content"
84 android:layout_centerVertical="true"
85 android:layout_marginLeft="20dp"
86 android:layout_marginTop="20dp"
87 android:layout_toRightOf="@id/menuimage3"
88 android:text="菜单三"
89 android:textColor="@android:color/white"
90 android:textSize="20dp" />
91 </RelativeLayout>
92
93
94 <RelativeLayout
95 android:layout_width="match_parent"
96 android:layout_height="wrap_content"
97 android:layout_centerInParent="true" >
98
99 <ImageView
100 android:id="@+id/menuimage4"
101 android:layout_width="50dp"
102 android:layout_height="50dp"
103 android:layout_centerVertical="true"
104 android:layout_marginLeft="20dp"
105 android:layout_marginTop="20dp"
106 android:src="@drawable/img_4" />
107
108 <TextView
109 android:id="@+id/menutext4"
110 android:layout_width="wrap_content"
111 android:layout_height="wrap_content"
112 android:layout_centerVertical="true"
113 android:layout_marginLeft="20dp"
114 android:layout_marginTop="20dp"
115 android:layout_toRightOf="@id/menuimage4"
116 android:text="菜单四"
117 android:textColor="@android:color/white"
118 android:textSize="20dp" />
119 </RelativeLayout>
120
121
122 <RelativeLayout
123 android:layout_width="match_parent"
124 android:layout_height="wrap_content"
125 android:layout_centerInParent="true" >
126
127 <ImageView
128 android:id="@+id/menuimage5"
129 android:layout_width="50dp"
130 android:layout_height="50dp"
131 android:layout_centerVertical="true"
132 android:layout_marginLeft="20dp"
133 android:layout_marginTop="20dp"
134 android:src="@drawable/img_5" />
135
136 <TextView
137 android:id="@+id/menutext5"
138 android:layout_width="wrap_content"
139 android:layout_height="wrap_content"
140 android:layout_centerVertical="true"
141 android:layout_marginLeft="20dp"
142 android:layout_marginTop="20dp"
143 android:layout_toRightOf="@id/menuimage5"
144 android:text="菜单五"
145 android:textColor="@android:color/white"
146 android:textSize="20dp" />
147 </RelativeLayout>
148 </LinearLayout>
149
150 </RelativeLayout>
2、主内容布局文件:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:background="@drawable/img_frame_background" >
6
7 <com.example.sidesliptest.MyHorizontalScrollView
8 android:layout_height="match_parent"
9 android:layout_width="match_parent"
10 android:scrollbars="none"
11 >
12 <LinearLayout
13 android:layout_height="match_parent"
14 android:layout_width="match_parent"
15 android:orientation="horizontal"
16 >
17 <include layout="@layout/left_menu"/>
18 <LinearLayout
19 android:layout_width="match_parent"
20 android:layout_height="match_parent"
21 android:background="@drawable/qq"
22 ></LinearLayout>
23
24 </LinearLayout>
25
26 </com.example.sidesliptest.MyHorizontalScrollView>
27
28 </RelativeLayout>
3、自定义View(HorizontalScrollView)类:
自定义Viewi实现步骤:
1、继承要自定义View的类,并实现带有参数的构造方法
2、重写onMeasure(确定自定义View的大小)和onLayout(确定自定义View的位置)方法
关于HorizontalScrollView的滑动,我们可以用onScrollChanged来监听参数L:
打印日志:可以发现,当滚动条向左(画面向右滑动)的时候,L的值是逐渐增大的,所以我们可以通过它来作为动画的变化梯度值。
注释很全,具体看注释吧。
1 package com.example.sidesliptest;
2
3 import android.content.Context;
4 import android.util.AttributeSet;
5 import android.util.DisplayMetrics;
6 import android.util.Log;
7 import android.util.TypedValue;
8 import android.view.MotionEvent;
9 import android.view.ViewGroup;
10 import android.view.WindowManager;
11 import android.widget.HorizontalScrollView;
12 import android.widget.LinearLayout;
13
14 import com.nineoldandroids.view.ViewHelper;
15
16 public class MyHorizontalScrollView extends HorizontalScrollView {
17
18 // 在HorizontalScrollView有个LinearLayout
19 private LinearLayout linearLayout;
20 // 菜单,内容页
21 private ViewGroup myMenu;
22 private ViewGroup myContent;
23 //菜单宽度
24 private int myMenuWidth;
25
26 // 屏幕宽度
27 private int screenWidth;
28 // 菜单与屏幕右侧的距离(dp)
29 private int myMenuPaddingRight = 50;
30
31 // 避免多次调用onMeasure的标志
32 private boolean once = false;
33
34 /**
35 * 自定义View需要实现带有Context、AttributeSet这2个参数的构造方法,否则自定义参数会出错
36 * 当使用了自定义属性时,会调用此构造方法
37 *
38 * @param context
39 * @param attrs
40 */
41 public MyHorizontalScrollView(Context context, AttributeSet attrs) {
42 super(context, attrs);
43 // 获取屏幕宽度
44 WindowManager windowManager = (WindowManager) context
45 .getSystemService(Context.WINDOW_SERVICE);
46 DisplayMetrics outMetrics = new DisplayMetrics();
47 windowManager.getDefaultDisplay().getMetrics(outMetrics);
48 screenWidth = outMetrics.widthPixels;// 屏幕宽度
49
50 // 将dp转换px
51 myMenuPaddingRight = (int) TypedValue.applyDimension(
52 TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources()
53 .getDisplayMetrics());
54
55 }
56
57 /**
58 * 设置子View的宽高,决定自身View的宽高,每次启动都会调用此方法
59 */
60 @Override
61 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
62 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
63 if (!once) {//使其只调用一次
64 // this指的是HorizontalScrollView,获取各个元素
65 linearLayout = (LinearLayout) this.getChildAt(0);// 第一个子元素
66 myMenu = (ViewGroup) linearLayout.getChildAt(0);// HorizontalScrollView下LinearLayout的第一个子元素
67 myContent = (ViewGroup) linearLayout.getChildAt(1);// HorizontalScrollView下LinearLayout的第二个子元素
68
69 // 设置子View的宽高,高于屏幕一致
70 myMenuWidth=myMenu.getLayoutParams().width = screenWidth - myMenuPaddingRight;// 菜单的宽度=屏幕宽度-右边距
71 myContent.getLayoutParams().width = screenWidth;// 内容宽度=屏幕宽度
72 // 决定自身View的宽高,高于屏幕一致
73 // 由于这里的LinearLayout里只包含了Menu和Content所以就不需要额外的去指定自身的宽
74 once = true;
75 }
76 }
77
78 //设置View的位置,首先,先将Menu隐藏(在eclipse中ScrollView的画面内容(非滚动条)正数表示向左移,向上移)
79 @Override
80 protected void onLayout(boolean changed, int l, int t, int r, int b) {
81 super.onLayout(changed, l, t, r, b);
82 //刚载入界面的时候隐藏Menu菜单也就是ScrollView向左滑动菜单自身的大小
83 if(changed){
84 this.scrollTo(myMenuWidth, 0);//向左滑动,相当于把右边的内容页拖到正中央,菜单隐藏
85 }
86
87 }
88
89 @Override
90 public boolean onTouchEvent(MotionEvent ev) {
91 int action=ev.getAction();
92 switch (action) {
93 case MotionEvent.ACTION_UP:
94 int scrollX=this.getScrollX();//滑动的距离scrollTo方法里,也就是onMeasure方法里的向左滑动那部分
95 if(scrollX>=myMenuWidth/2){
96 this.smoothScrollTo(myMenuWidth,0);//向左滑动展示内容
97 }else{
98 this.smoothScrollTo(0, 0);
99 }
100 return true;
101 }
102 return super.onTouchEvent(ev);
103 }
104
105
106 @Override
107 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
108 super.onScrollChanged(l, t, oldl, oldt);
109 Log.i("tuzi",l+"");
110 float scale = l * 1.0f / myMenuWidth; // 1 ~ 0
111
112 /**
113 * 区别1:内容区域1.0~0.7 缩放的效果 scale : 1.0~0.0 0.7 + 0.3 * scale
114 *
115 * 区别2:菜单的偏移量需要修改
116 *
117 * 区别3:菜单的显示时有缩放以及透明度变化 缩放:0.7 ~1.0 1.0 - scale * 0.3 透明度 0.6 ~ 1.0
118 * 0.6+ 0.4 * (1- scale) ;
119 *
120 */
121 float rightScale = 0.7f + 0.3f * scale;
122 float leftScale = 1.0f - scale * 0.3f;
123 float leftAlpha = 0.6f + 0.4f * (1 - scale);
124
125 // 调用属性动画,设置TranslationX
126 ViewHelper.setTranslationX(myMenu, myMenuWidth * scale * 0.8f);
127
128 ViewHelper.setScaleX(myMenu, leftScale);
129 ViewHelper.setScaleY(myMenu, leftScale);
130 ViewHelper.setAlpha(myMenu, leftAlpha);
131 // 设置content的缩放的中心点
132 ViewHelper.setPivotX(myContent, 0);
133 ViewHelper.setPivotY(myContent, myContent.getHeight() / 2);
134 ViewHelper.setScaleX(myContent, rightScale);
135 ViewHelper.setScaleY(myContent, rightScale);
136 }
137
138 }
4、主程序类:
package com.example.sidesliptest;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
}
}