Android 自定义ViewGroup,实现侧方位滑动菜单

侧方位滑动菜单

1.现在adnroid流行的应用当中很多都是用的侧方位滑动菜单如图:

将菜单显示在左边,内容页面显示在右边,通过滑动或则按钮点击来隐藏和显示菜单。

2.首先对ViewGroup进行个了解:

  View是ViewGroup的父类,ViewGroup具有View的所有特性,ViewGroup主要用用来充当View的容器,将其中的View作为自己孩子,

并对其进行管理,当然孩子也是可以是ViewGroup类型。

  View类一般用于绘图操作,重写他的onDraw方法,但它不可以包含其他组件,没有addView(View view)方法

  ViewGroup是一个组件容器,它可以包含任何组件,但必须重写onLayout(boolean changed,int ;,int t,int r,int b)和

onMeasure(int widthMeasureSpec,int heightMeasureSpec)方法,否则ViewGroup中添加组件是不会显示的。

  View 的layout(int left,int top,int right,int bottom)方法负责把该view放在参数指定位置,所以如果我们在自定义的ViewGroup::onLayout中遍历每一个子view并用 view.layout()指定其位置,每一个子View又会调用onLayout,这就构成了一个递归调用的过程

如 果在ViewGroup中重写onDraw方法,需要在构造方法中调用this.setWillNoDraw(flase); 此时,系统才会调用重写过的onDraw(Canvas cancas)方法,否则系统不会调用onDraw(Canvas canvas)方法

  *** 这里要注意下l是view左边界相对于父左边的的距离,t是上边界相对于父上边的距离,r是view右边界相对于父左边界的距离,b是view下边界相对于父上边界的距离,如果l,t,r,b没设置好则onMeasure设置的View的大小可能不显示

 1 @Override  
 2         protected void onLayout(boolean changed, int l, int t, int r,  
 3                 int b) {  
 4             int childCount = getChildCount();  
 5             int left = 0;  
 6             int top = 10;  
 7             for (int i = 0; i < childCount; i++) {  
 8                 View child = getChildAt(i);  
 9                 child.layout(left, top, left + 60, top + 60);  
10                 top += 70;  
11             }  
12         }  

  

  通过重写onMeasure方法不但可以为ViewGroup指定大小,还可以通过遍历为每一个子View指定大小,在自定义ViewGroup中添加上面代码为ViewGroup中的每一个子View分配了显示的宽高。

  onMeasure传入的两个参数是由上一层控件传入的大小,有多种情况,重写该方法时需要对计算控件的实际大小,然后调用setMeasuredDimension(int, int)设置实际大小。
    onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值而是将模式和尺寸组合在一起的数值

  ***onMeasure()方法有时可能对子View设置了大小却没有效果可能原因有:mode是AT_MOST,就只显示layout所占的区域来显示,换成其他值子View不变就可以将mode模式改成EXACTLY

  

 1 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
 2         
 3     int childCount = getChildCount();    
 4 //设置该ViewGroup的大小    
 5     int specSize_width = MeasureSpec.getSize(widthMeasureSpec);    
 6     int specSize_height = MeasureSpec.getSize(heightMeasureSpec);     
 7     setMeasuredDimension(specSize_width, specSize_height);    
 8         
 9     for (int i = 0; i < childCount; i++) {    
10         View childView = getChildAt(i);    
11         childView.measure(80, 80);    
12     }    
13 } 

3.在菜单滚动的时候是要用到Scroller类的,这个类的知识前面有行查阅。

4.现在对滑动菜单的思路进行一下整理:

  •   自定义一个MyScrollView继承自ViewGroup,添加两个方法setMenu(View menu)和setPrimary(View primary)来给ViewGroup里面添加子View,复写onLayout方法和onMeasure方法。
  •   在复写onLayout()要利用View是没有边界的,所以将菜单和内容页是拼接起来的,整个界面的真实宽度是超过手机屏幕宽度的。
  •   复写onMeasure()的时候为子View设置宽度
  •   复写onTouchEvent()来根据手势来滑动菜单
  •   定义一个接口来满足菜单隐藏或显示时的一些逻辑处理

5.直接上代码:

  自定义View:

  1 public class MyScrollView extends ViewGroup {
  2 
  3     private Context mContext;
  4     private int mWidth;
  5     private int mHeight;
  6     private float mMenuWeight = 3.0f / 5;
  7 
  8     private View mMenuView;
  9     private View mPriView;
 10     private boolean mIsShowMenu;
 11     private float mDownx;
 12 
 13     private Scroller mScroller;
 14     private OnMenuChangerLister mListrer;
 15 
 16     public MyScrollView(Context context) {
 17         super(context);
 18     }
 19 
 20     public MyScrollView(Context context, AttributeSet attrs) {
 21         super(context, attrs);
 22         this.mContext = context;
 23         mScroller = new Scroller(mContext);
 24     }
 25 
 26     public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
 27         super(context, attrs, defStyle);
 28     }
 29 
 30     @Override
 31     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 32         System.out.println("执行了onLyout");
 33         mMenuView.layout(-(int) (mWidth * mMenuWeight), 0, 0, mHeight);
 34         mPriView.layout(0, 0, mWidth, mHeight);
 35     }
 36 
 37     @Override
 38     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 39         System.out.println("执行了onMeasure'");
 40         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 41         /*
 42          * onMeasure传入的两个参数是由上一层控件传入的大小,有多种情况,重写该方法时需要对计算控件的实际大小,
 43          * 然后调用setMeasuredDimension(int, int)设置实际大小。
 44          * onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值
 45          * ,而是将模式和尺寸组合在一起的数值。 我们需要通过int mode =
 46          * MeasureSpec.getMode(widthMeasureSpec)得到模式, 用int size =
 47          * MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
 48          * mode共有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY,
 49          * MeasureSpec.AT_MOST。 MeasureSpec.EXACTLY是精确尺寸,
 50          * 当我们将控件的layout_width或layout_height指定为具体数值时如andorid
 51          * :layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
 52          * MeasureSpec
 53          * .AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时
 54          * ,控件大小一般随着控件的子空间或内容进行变化
 55          * ,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
 56          * MeasureSpec
 57          * .UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
 58          */
 59         mWidth = MeasureSpec.getSize(widthMeasureSpec); // 获取MyScrollView 的宽度
 60         mHeight = MeasureSpec.getSize(heightMeasureSpec);// 获取MyScrillView的高度
 61 
 62         setMeasuredDimension(mWidth, mHeight);
 63         int widthSpec = MeasureSpec.makeMeasureSpec(
 64                 (int) (mWidth * mMenuWeight), MeasureSpec.EXACTLY);
 65         int heightSpec = MeasureSpec.makeMeasureSpec(mHeight,
 66                 MeasureSpec.EXACTLY);
 67         mMenuView.measure(widthSpec, heightSpec);
 68 
 69         widthSpec = MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY);
 70         mPriView.measure(widthSpec, heightSpec);
 71 
 72     }
 73 
 74     /**
 75      * 设置右滑菜单
 76      * 
 77      * @param menu
 78      */
 79     public void setMenu(View menu) {
 80         mMenuView = menu;
 81         addView(mMenuView);
 82     }
 83 
 84     /**
 85      * 设置主界面的View
 86      * 
 87      */
 88     public void setPrimary(View primary) {
 89         mPriView = primary;
 90         addView(mPriView);
 91     }
 92 
 93     @Override
 94     public boolean onTouchEvent(MotionEvent event) {
 95         float x = event.getX();
 96         switch (event.getAction()) {
 97         case MotionEvent.ACTION_DOWN:
 98             System.out.println("Action_Down");
 99             mDownx = x;
100             break;
101 
102         case MotionEvent.ACTION_MOVE:
103             break;
104 
105         case MotionEvent.ACTION_UP:
106             System.out.println("ACTION_UP");
107             int dis = (int) (x - mDownx);
108             if (Math.abs(dis) > (mWidth * mMenuWeight / 2)) {
109                 if (dis > 0) {
110                     shwoMenu();
111                 } else {
112                     hideMenu();
113 
114                 }
115 
116             }
117 
118             break;
119         default:
120             break;
121         }
122 
123         return true;
124     }
125 
126     public boolean isShowMenu() {
127         return mIsShowMenu;
128 
129     }
130 
131     @Override
132     public void computeScroll() {
133         super.computeScroll();
134         if (mScroller.computeScrollOffset()) {
135             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
136             postInvalidate();
137         }
138     }
139 
140     public void hideMenu() {
141         if (!mIsShowMenu) {
142             return;
143         }
144 
145         mIsShowMenu = false;
146         int dx = (int) (mWidth * mMenuWeight);
147         mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
148         if (mListrer != null) {
149             mListrer.onChanged(mIsShowMenu);
150         }
151         invalidate();
152     }
153 
154     public void shwoMenu() {
155 
156         if (mIsShowMenu) {
157             return;
158         }
159         mIsShowMenu = true;// 标记菜单已经显示
160         int dx = (int) (mWidth * mMenuWeight);
161         mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
162         if (mListrer != null) {
163             mListrer.onChanged(mIsShowMenu);
164         }
165 
166         invalidate();
167     }
168 
169     // 在操作界面,当显示menu的时候要做什么事和隐藏menu要做什么事
170     public interface OnMenuChangerLister {
171         public void onChanged(boolean isShow);
172     }
173 
174     public void setOnMenuChangedLister(OnMenuChangerLister listener) {
175         mListrer = listener;
176     }
177 
178 }

  MianActivity:

  

 1 public class MainActivity extends Activity {
 2 
 3     private MyScrollView mRightScrollView;
 4     private Button mShowMenuBtn;
 5     private ListView mMenuList;
 6     private ArrayAdapter<String> mAdapter;
 7     private String[] menus = { "附近的人", "我的资料", "设置", "游戏", "即系聊天" };
 8 
 9     @Override
10     protected void onCreate(Bundle savedInstanceState) {
11         super.onCreate(savedInstanceState);
12         setContentView(R.layout.activity_main);
13         mRightScrollView = (MyScrollView) findViewById(R.id.rightscrollview_test);
14         View menu = LayoutInflater.from(MainActivity.this).inflate(
15                 R.layout.rightscrollview_menu, null);
16         final View primary = LayoutInflater.from(MainActivity.this).inflate(
17                 R.layout.rightscrollview_primary, null);
18         mMenuList = (ListView) menu.findViewById(R.id.list_right_menu);
19         mShowMenuBtn = (Button) primary.findViewById(R.id.btn_showmenu);
20         mAdapter = new ArrayAdapter<String>(this,
21                 android.R.layout.simple_list_item_1, menus);
22         mMenuList.setAdapter(mAdapter);
23 
24         mShowMenuBtn.setOnClickListener(new OnClickListener() {
25 
26             @Override
27             public void onClick(View v) {
28                 if (mRightScrollView.isShowMenu()) {
29                     mRightScrollView.hideMenu();
30                 } else {
31                     mRightScrollView.shwoMenu();
32                 }
33             }
34         });
35 
36         mRightScrollView.setOnMenuChangedLister(new OnMenuChangerLister() {
37 
38             @Override
39             public void onChanged(boolean isShow) {
40                 if (isShow) {
41                     mShowMenuBtn.setText("隐藏菜单");
42                 } else {
43                     mShowMenuBtn.setText("显示菜单");
44                 }
45 
46             }
47         });
48 
49         mRightScrollView.setMenu(menu);
50         mRightScrollView.setPrimary(primary);
51         mMenuList.setOnItemClickListener(new OnItemClickListener() {
52 
53             @Override
54             public void onItemClick(AdapterView<?> parent, View arg1,
55                     int position, long id) {
56                 switch (position) {
57                 case 0:
58                     primary.setBackgroundColor(Color.CYAN);
59                     break;
60                 case 1:
61                     primary.setBackgroundColor(Color.BLUE);
62                     break;
63                 case 2:
64                     primary.setBackgroundColor(Color.GRAY);
65                     break;
66                 case 3:
67                     primary.setBackgroundColor(Color.MAGENTA);
68                     break;
69                 case 4:
70                     primary.setBackgroundColor(Color.YELLOW);
71                     break;
72                 }
73             }
74         });
75 
76     }
77 
78 }

运行后的效果如上图所示

源码下载

 

posted @ 2014-10-13 22:30  perfect亮  阅读(627)  评论(0编辑  收藏  举报