首页 > 代码库 > Android 自定义ViewGroup,实现侧方位滑动菜单

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     @Override132     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     @Override10     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             @Override27             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             @Override39             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             @Override54             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 }

运行后的效果如上图所示

源码下载

 

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