首页 > 代码库 > 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,实现侧方位滑动菜单