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 }
运行后的效果如上图所示