有关自己定义ViewGroup的文章已经非常多了,我为什么写这篇文章,对于刚開始学习的人或者对自己定义组件比較生疏的朋友尽管能够拿来主义的用了,可是要一步一步的实现和了解当中的过程和原理才干真真脱离别人的代码,举一反三却不easy,非常多博主事实上不愿意一步一步的去写,这样非常耗时,可是假设能对读者有帮助,能从这篇文章中学会自己定义组件就达到我的目的了。

第一步:搭建框架来实现一个3/5和2/5分屏的界面,效果例如以下:


最外层是一个自己定义的ViewGroup布局文件例如以下:

  1. package com.example.testscrollto;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import android.view.View.MeasureSpec;
  7. public class MyScrollView extends ViewGroup{
  8. private int mWidth;
  9. private int mHeight;
  10. private float mMenuWeight = 3.0f / 5; //菜单界面比例
  11. private View mMenuView; //菜单界面
  12. private View mPriView; //内容界面
  13. public MyScrollView(Context context, AttributeSet attrs) {
  14. super(context, attrs);
  15. }
  16. @Override
  17. protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
  18. System.out.println("运行了onLayout");
  19. mMenuView.layout(0, 0, (int)(mWidth * mMenuWeight), mHeight);
  20. mPriView.layout((int)(mWidth * mMenuWeight), 0, mWidth, mHeight);
  21. }
  22. @Override
  23. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  24. System.out.println("运行了onMeasure");
  25. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  26. /*
  27. * onMeasure传入的两个參数是由上一层控件传入的大小,有多种情况,重写该方法时须要对计算控件的实际大小,
  28. * 然后调用setMeasuredDimension(int, int)设置实际大小。
  29. * onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。
  30. * 我们须要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,
  31. * 用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
  32. * mode共同拥有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。
  33. * MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为详细数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
  34. * MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸仅仅要不超过父控件同意的最大尺寸就可以。因此,此时的mode是AT_MOST,size给出了父控件同意的最大尺寸。
  35. * MeasureSpec.UNSPECIFIED是未指定尺寸,这样的情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
  36. */
  37. mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取MyScrollView的宽度
  38. mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
  39. }
  40. /**设置右滑的菜单View*/
  41. public void setMenu(View menu){
  42. mMenuView = menu;
  43. addView(mMenuView);
  44. }
  45. /**
  46. * 设置主界面View
  47. */
  48. public void setPrimary(View primary){
  49. mPriView = primary;
  50. addView(mPriView);
  51. }
  52. }

第二步:按须要设置界面位置

  1. @Override
  2. protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
  3. mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
  4. mPriView.layout(0, 0, mWidth, mHeight);
  5. }

第三步:实现左右滑动

  1. package com.example.testscrollto;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.view.MotionEvent;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import android.widget.Scroller;
  8. public class MyScrollView extends ViewGroup{
  9. private Context mContext;
  10. private int mWidth;
  11. private int mHeight;
  12. private float mMenuWeight = 3.0f / 5; //菜单界面比例
  13. private View mMenuView; //菜单界面
  14. private View mPriView; //内容界面
  15. private boolean mIsShowMenu;
  16. private Scroller mScroller;
  17. public MyScrollView(Context context, AttributeSet attrs) {
  18. super(context, attrs);
  19. mContext = context;
  20. mScroller = new Scroller(mContext);
  21. }
  22. @Override
  23. protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
  24. mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
  25. mPriView.layout(0, 0, mWidth, mHeight);
  26. }
  27. @Override
  28. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  29. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  30. mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取MyScrollView的宽度
  31. mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取MyScrollView的高度
  32. }
  33. /**设置右滑的菜单View*/
  34. public void setMenu(View menu){
  35. mMenuView = menu;
  36. addView(mMenuView);
  37. }
  38. /**
  39. * 设置主界面View
  40. */
  41. public void setPrimary(View primary){
  42. mPriView = primary;
  43. addView(mPriView);
  44. }
  45. private float mDownX;
  46. @Override
  47. public boolean onTouchEvent(MotionEvent event) {
  48. float x = event.getX();
  49. switch (event.getAction()) {
  50. case MotionEvent.ACTION_DOWN:
  51. System.out.println("ACTION_DOWN");
  52. mDownX = x; //记录按下时的x坐标
  53. break;
  54. case MotionEvent.ACTION_UP:
  55. System.out.println("ACTION_UP");
  56. int dis = (int) (x - mDownX); //滑动的距离
  57. if(Math.abs(dis) > (mWidth * mMenuWeight / 2)){
  58. if(dis > 0){ //假设>0则是向右滑动
  59. showMenu();
  60. }else{ //假设<0则是向左滑动
  61. hideMenu();
  62. }
  63. }
  64. break;
  65. default:
  66. break;
  67. }
  68. return true;
  69. }
  70. @Override
  71. public void computeScroll() {
  72. super.computeScroll();
  73. if(mScroller.computeScrollOffset()){
  74. scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  75. postInvalidate();
  76. }
  77. }
  78. public boolean isShowMenu(){
  79. return mIsShowMenu;
  80. }
  81. public void showMenu(){
  82. if(mIsShowMenu){
  83. return;
  84. }
  85. mIsShowMenu = true; //标记菜单已经显示
  86. int dx = (int)(mWidth * mMenuWeight); //滑动到目标位置的距离
  87. mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
  88. invalidate();
  89. }
  90. public void hideMenu(){
  91. if(!mIsShowMenu){
  92. return;
  93. }
  94. mIsShowMenu = false;
  95. int dx = (int)(mWidth * mMenuWeight);
  96. mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
  97. invalidate();
  98. }
  99. }

从上面代码中能够看到以下两句代码触发computeScroll()方法

  1. mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
  2. invalidate();

第四步:加入窗体状态切换监听接口

  1. public interface OnMenuChangedListener{
  2. public void onChanged(boolean isShow);
  3. }
  4. public void setOnMenuChangedListener(OnMenuChangedListener listener){
  5. mListener = listener;
  6. }

将showMenu()方法和hideMenu()方法改动例如以下:

  1. public void showMenu(){
  2. if(mIsShowMenu){
  3. return;
  4. }
  5. mIsShowMenu = true;
  6. int dx = (int)(mWidth * mMenuWeight);
  7. mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
  8. if(mListener != null){
  9. mListener.onChanged(mIsShowMenu);
  10. }
  11. invalidate();
  12. }
  13. public void hideMenu(){
  14. if(!mIsShowMenu){
  15. return;
  16. }
  17. mIsShowMenu = false;
  18. int dx = (int)(mWidth * mMenuWeight);
  19. mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
  20. if(mListener != null){
  21. mListener.onChanged(mIsShowMenu);
  22. }
  23. invalidate();
  24. }

在MainActivity中加入监听

  1. mScrollView.setOnMenuChangedListener(new OnMenuChangedListener() {
  2. @Override
  3. public void onChanged(boolean isShow) {
  4. System.out.println("窗体切换了一次");
  5. }
  6. });

第五步:依据详细业务及需求实现菜单列表及界面(直接贴出代码)

MainActivity.java

  1. package com.example.testrefreshview;
  2. import android.app.Activity;
  3. import android.graphics.Color;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.widget.AdapterView;
  8. import android.widget.AdapterView.OnItemClickListener;
  9. import android.widget.ArrayAdapter;
  10. import android.widget.Button;
  11. import android.widget.ListView;
  12. import com.example.testrefreshview.RightScrollView.OnMenuChangedListener;
  13. /**
  14. * 測试具有右滑菜单功能的ViewGroup,RigthScrollView
  15. *@author Lqh
  16. */
  17. public class MainActivity extends Activity {
  18. private RightScrollView mRightScrollView;
  19. private Button mShowMenuBtn;
  20. private ListView mMenuList;
  21. private ArrayAdapter<String> mAdapter;
  22. private String[] menus = {"附近的人", "我的资料", "设置", "游戏", "即时聊天"};
  23. @Override
  24. protected void onCreate(Bundle savedInstanceState) {
  25. super.onCreate(savedInstanceState);
  26. setContentView(R.layout.rightscrollview_test);
  27. mRightScrollView = (RightScrollView)findViewById(R.id.rightscrollview);
  28. final View menu = getLayoutInflater().inflate(R.layout.rightscrollview_menu, null);
  29. final View primary = getLayoutInflater().inflate(R.layout.rightscrollview_primary, null);
  30. mMenuList = (ListView) menu.findViewById(R.id.list_right_menu);
  31. mShowMenuBtn = (Button) primary.findViewById(R.id.btn_showmenu);
  32. mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, menus);
  33. mMenuList.setAdapter(mAdapter);
  34. mShowMenuBtn.setOnClickListener(new OnClickListener() {
  35. public void onClick(View v) {
  36. if(mRightScrollView.isShowMenu()){
  37. mRightScrollView.hideMenu();
  38. }else{
  39. mRightScrollView.showMenu();
  40. }
  41. }
  42. });
  43. mRightScrollView.setOnMenuChangedListener(new OnMenuChangedListener() {
  44. public void onChanged(boolean isShow) {
  45. if(isShow){
  46. mShowMenuBtn.setText("隐藏菜单");
  47. }else{
  48. mShowMenuBtn.setText("显示菜单");
  49. }
  50. }
  51. });
  52. mRightScrollView.setMenu(menu);
  53. mRightScrollView.setPrimary(primary);
  54. mMenuList.setOnItemClickListener(new OnItemClickListener() {
  55. public void onItemClick(AdapterView<?> parent, View view, 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. mRightScrollView.hideMenu();
  74. }
  75. });
  76. }
  77. }

RightScrollView.java

  1. package com.example.testrefreshview;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.view.MotionEvent;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import android.widget.Scroller;
  8. /**
  9. * 具有右滑菜单的ViewGroup,相似于Facebook的主界面
  10. * @author Lqh
  11. */
  12. public class RightScrollView extends ViewGroup {
  13. private Context mContext;
  14. private Scroller mScroller;
  15. private View mMenuView;
  16. private View mPriView;
  17. private int mWidth;
  18. private int mHeight;
  19. private boolean mIsShowMenu;
  20. private float mMenuWeight = 3.0f / 5;
  21. private OnMenuChangedListener mListener;
  22. public RightScrollView(Context context) {
  23. this(context, null);
  24. }
  25. public RightScrollView(Context context, AttributeSet attrs) {
  26. super(context, attrs);
  27. mContext = context;
  28. mScroller = new Scroller(mContext);
  29. }
  30. /**设置右滑的菜单View*/
  31. public void setMenu(View menu){
  32. mMenuView = menu;
  33. addView(mMenuView);
  34. }
  35. /**
  36. * 设置主界面View
  37. */
  38. public void setPrimary(View primary){
  39. mPriView = primary;
  40. addView(mPriView);
  41. }
  42. public boolean isShowMenu(){
  43. return mIsShowMenu;
  44. }
  45. public void setOnMenuChangedListener(OnMenuChangedListener listener){
  46. mListener = listener;
  47. }
  48. public void showMenu(){
  49. if(mIsShowMenu){
  50. return;
  51. }
  52. mIsShowMenu = true;
  53. int dx = (int)(mWidth * mMenuWeight);
  54. mScroller.startScroll(getScrollX(), 0, -dx, 0, 500);
  55. if(mListener != null){
  56. mListener.onChanged(mIsShowMenu);
  57. }
  58. invalidate();
  59. }
  60. public void hideMenu(){
  61. if(!mIsShowMenu){
  62. return;
  63. }
  64. mIsShowMenu = false;
  65. int dx = (int)(mWidth * mMenuWeight);
  66. mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
  67. if(mListener != null){
  68. mListener.onChanged(mIsShowMenu);
  69. }
  70. invalidate();
  71. }
  72. private float mDownX;
  73. @Override
  74. public boolean onTouchEvent(MotionEvent event) {
  75. float x = event.getX();
  76. switch(event.getAction()){
  77. case MotionEvent.ACTION_DOWN:
  78. mDownX = x;
  79. break;
  80. case MotionEvent.ACTION_UP:
  81. int dis = (int) (x - mDownX);
  82. if(Math.abs(dis) > (mWidth * mMenuWeight / 2)){
  83. if(dis > 0){
  84. showMenu();
  85. }else{
  86. hideMenu();
  87. }
  88. }
  89. break;
  90. }
  91. return true;
  92. }
  93. @Override
  94. public void computeScroll() {
  95. super.computeScroll();
  96. if(mScroller.computeScrollOffset()){
  97. scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  98. postInvalidate();
  99. }
  100. }
  101. @Override
  102. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  103. mWidth = MeasureSpec.getSize(widthMeasureSpec);
  104. mHeight = MeasureSpec.getSize(heightMeasureSpec);
  105. setMeasuredDimension(mWidth, mHeight);
  106. int widthSpec = MeasureSpec.makeMeasureSpec((int)(mWidth * mMenuWeight), MeasureSpec.EXACTLY);
  107. int heightSpec = MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY);
  108. mMenuView.measure(widthSpec, heightSpec);
  109. widthSpec = MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY);
  110. mPriView.measure(widthSpec, heightSpec);
  111. }
  112. @Override
  113. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  114. mMenuView.layout(-(int)(mWidth * mMenuWeight), 0, 0, mHeight);
  115. mPriView.layout(0, 0, mWidth, mHeight);
  116. }
  117. public interface OnMenuChangedListener{
  118. public void onChanged(boolean isShow);
  119. }
  120. }

运行效果:

源码下载:http://download.csdn.net/detail/lxq_xsyu/7231677

Android自己定义组件系列【3】——自己定义ViewGroup实现側滑的更多相关文章

  1. Android自己定义组件系列【7】——进阶实践(4)

    上一篇<Android自己定义组件系列[6]--进阶实践(3)>中补充了关于Android中事件分发的过程知识.这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpa ...

  2. Android自己定义组件系列【6】——进阶实践(3)

    上一篇<Android自己定义组件系列[5]--进阶实践(2)>继续对任老师的<可下拉的PinnedHeaderExpandableListView的实现>进行了分析,这一篇计 ...

  3. Android自己定义组件系列【5】——进阶实践(2)

    上一篇<Android自己定义组件系列[5]--进阶实践(1)>中对任老师的<可下拉的PinnedHeaderExpandableListView的实现>前一部分进行了实现,这 ...

  4. Android自己定义组件系列【4】——自己定义ViewGroup实现双側滑动

    在上一篇文章<Android自己定义组件系列[3]--自己定义ViewGroup实现側滑>中实现了仿Facebook和人人网的側滑效果,这一篇我们将接着上一篇来实现双面滑动的效果. 1.布 ...

  5. Android自己定义组件系列【2】——Scroller类

    在上一篇中介绍了View类的scrollTo和scrollBy两个方法,对这两个方法不太了解的朋友能够先看<自己定义View及ViewGroup> scrollTo和scrollBy尽管实 ...

  6. Android自己定义组件系列【1】——自己定义View及ViewGroup

    View类是ViewGroup的父类,ViewGroup具有View的全部特性.ViewGroup主要用来充当View的容器.将当中的View作为自己孩子,并对其进行管理.当然孩子也能够是ViewGr ...

  7. Android自己定义组件系列【9】——Canvas绘制折线图

    有时候我们在项目中会遇到使用折线图等图形,Android的开源项目中为我们提供了非常多插件,可是非常多时候我们须要依据详细项目自己定义这些图表,这一篇文章我们一起来看看怎样在Android中使用Can ...

  8. Android自己定义组件系列【5】——高级实践(1)

    在接下来的几篇文章将任老师的博文<您可以下拉PinnedHeaderExpandableListView实现>骤来具体实现.来学习一下大神的代码并记录一下. 原文出处:http://blo ...

  9. Android自己定义组件系列【8】——面膜文字动画

    我们掩盖文字动画Flash中非经货共同体共同,由于Android应用程序开发人员做你想要做这个动画在应用程序中去?本文中,我们看的是如何自己的定义ImageView来实现让一张文字图片实现文字的遮罩闪 ...

随机推荐

  1. 分享一个自己写的vue多语言插件smart-vue-i18n

    前言 目前有比较成熟的方案(vue-i18n)了解了下,并且实用了一下感觉对于我在使用的项目来说略显臃肿,功能比较多,所以压缩的会比较大,在移动端不太适合所以自己花一天时间撸了一个vue多语言插件,压 ...

  2. java基础32 List集合下的ArrayList集合

    单例集合体系: ---------| collection  单例集合的根接口--------------| List  如果实现了list接口的集合类,具备的特点:有序,可重复       注:集合 ...

  3. 3.Springboot之修改启动时的默认图案Banner

    一.SpringBoot的默认启动图案 在SpringBoot启动的时候,默认的会展示出一个spring的logo,这个图案我们用户是可以自定义的 二.自定义启动图案 方法一: Application ...

  4. HDU 3374 String Problem(KMP+最大(最小)表示)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3374 题目大意:给出一个字符串,依次左移一个单位形成一堆字符串,求其字典序最小和最大的字符串需要左移多 ...

  5. **PHP错误Cannot use object of type stdClass as array in错误的

    错误:将PHP对象类型当做了PHP数组  解决方法:用对象操作符-> 今天在PHP输出一个二维数组的时候,出现了“Fatal error: Cannot use object of type s ...

  6. INNODB表快速迁移

    本实验在一台server上启动了2个mysql实例端口分别是3307   3308,目的是将3307的表aaa迁移到3308中去,并打开3308的slave 1.在3308上 mysql> dr ...

  7. day7 面向对象进阶

    面向对象高级语法部分 通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例 ...

  8. Codeforces Round #239 (Div. 1) 二项式差分

    C - Curious Array 思路:对于区间[l, r]每个数加上C(i - l + k, k), 可以在l处+1, 在r+1处-1, 然后做k+1次求前缀和操作,然后就可以写啦. 然后逐层求前 ...

  9. ubuntu 安装 theano

    参考博客: http://www.cnblogs.com/anyview/p/5025704.html 1. 安装gfortran, numpy, scipy, sklearn, blas, atla ...

  10. Python实现代码行数统计工具

    我们经常想要统计项目的代码行数,但是如果想统计功能比较完善可能就不是那么简单了, 今天我们来看一下如何用python来实现一个代码行统计工具. 思路:首先获取所有文件,然后统计每个文件中代码的行数,最 ...