自定义控件的详解

本篇文章转载自:https://blog.csdn.net/Jsagacity/article/details/78580270  原作者:LayneYao

自定义控件的学习流程:

View的测量->View的绘制->ViewGroup的测量->ViewGroup的绘制->自定义ViewGroup->自定义控件的三种方式说明->事件拦截机制说明

View的测量:
Android系统绘制View是需要我们精确地告诉它该如何去画,它才能绘制出你想要的图形。
那么Android在绘制View之前,我们必须对View进行测量,即告诉系统该画一个多大的View。这个过程是在onMeasure()方法中进行的。
Android系统有一个功能强大的类---MeasureSpec类,通过它来帮我们测量View,测量模式有以下三种:
EXACTLY:精确值模式,当我们将控件的layout_width属性或layout_height属性指定为具体数字时,例如:android:layout_width="100dp",或指定为match_parent属性(填充父窗体)时,系统就会使用EXACTLY模式。

AT_MOST:最大值模式,当控件的layout_width属性或layout_height属性指定为wrap_content时,控件大小一般随着子控件或内容变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。

UNSPECIFIED:这个属性比较奇怪---它不指定其大小测量模式,View想多大就多大,通常情况下在绘制自定义View时才会使用。

如果不重写onMeasure()方法来指定属性的大小,那么View默认的就只支持EXACTLY模式。
View的测量方式:

[java]  view plain  copy
  1. /** 
  2.  * Created by Layne_Yao on 2017-11-17 上午11:38:38. 
  3.  * CSDN:http://blog.csdn.net/Jsagacity 
  4.  */  
  5. public class DragView extends View {  
  6.   
  7.     public DragView(Context context) {  
  8.         super(context);  
  9.         ininView();  
  10.     }  
  11.   
  12.     public DragView(Context context, AttributeSet attrs, int defStyleAttr) {  
  13.         super(context, attrs, defStyleAttr);  
  14.         ininView();  
  15.     }  
  16.   
  17.     public DragView(Context context, AttributeSet attrs) {  
  18.         super(context, attrs);  
  19.         ininView();  
  20.     }  
  21.   
  22.     private void ininView() {  
  23.         setBackgroundColor(Color.BLUE);  
  24.     }  
  25.   
  26.     // View的测量  
  27.     @Override  
  28.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  29.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  30.         setMeasuredDimension(measuredWidth(widthMeasureSpec),  
  31.                 measuredHeight(heightMeasureSpec));  
  32.     }  
  33.   
  34.     private int measuredHeight(int heightMeasureSpec) {  
  35.         int result = 0;  
  36.         // 从MeasureSpec对象中提取出具体的测量模式和大小  
  37.         int specMode = MeasureSpec.getMode(heightMeasureSpec);  
  38.         int specSize = MeasureSpec.getSize(heightMeasureSpec);  
  39.   
  40.         if (specMode == MeasureSpec.EXACTLY) {// 如果精确值模式,直接就是指定值  
  41.             result = specSize;  
  42.         } else {  
  43.             // 最大值模式给定一个不大的值  
  44.             result = 200;  
  45.             if (specMode == MeasureSpec.AT_MOST) {  
  46.                 result = Math.min(result, specSize);  
  47.             }  
  48.         }  
  49.         return result;  
  50.     }  
  51.   
  52.     private int measuredWidth(int widthMeasureSpec) {  
  53.         int result = 0;  
  54.         int specMode = MeasureSpec.getMode(widthMeasureSpec);  
  55.         int specSize = MeasureSpec.getSize(widthMeasureSpec);  
  56.   
  57.         if (specMode == MeasureSpec.EXACTLY) {  
  58.             result = specSize;  
  59.         } else {  
  60.             result = 200;  
  61.             if (specMode == MeasureSpec.AT_MOST) {  
  62.                 result = Math.min(result, specSize);  
  63.             }  
  64.         }  
  65.         return result;  
  66.     }  
  67.   
  68. }  

布局情况:

[java]  view plain  copy
  1. <com.itman.customviewdemo.DragView  
  2.     android:layout_width="wrap_content"  
  3.     android:layout_height="wrap_content" />  

如果layout_width属性或layout_height属性指定为wrap_content时那么就是最大值模式,我们给定了一个值。如果layout_width属性或layout_height属性指定为100dp,或者是match_parent属性时,就是精确值模式,那么就指定的显示大小。

显示结果:




View的绘制:
当测量好了一个View之后,我们就可以简单地重写onDraw()方法,并在Canvas对象上来绘制所需要的图形。
要想在Android的界面中绘制相应的图像,就必须在Canvas上进行绘制。Canvas就像是一个画板,使用Paint就可以在上面作画了。通常需要通过继承View并重写onDraw()方法来完成绘图。

ViewGroup的测量:
ViewGroup会去管理其子View,其中一个管理项目就是负责子View 的显示大小,ViewGroup大小为wrap_content时,就需要获取View的大小来决定自己的大小。其他情况就是指定值。

ViewGroup测量子View时,就是调用子View的onMeasure()来测量子View自身。
测量完了,就会调用子View的onLayout()来决定View的显示位置。


ViewGroup的绘制:
ViewGroup其实没有需要绘制的东西,onDraw()也只是修改一下背景。
但是ViewGroup使用dispatchDraw()方法可以绘制其子View,其过程同样是通过遍历所有子View,并调用View的绘制方法来完成绘制工作。


自定义View:
在自定义View时,我们通常会去重写onDraw()方法来绘制View的显示内容。
自定义View中,通常有以下一些比较重要的View的回调方法
1.onFinishInflate():从XML加载组建后回调
2.onSizeChanged():组件大小改变后回调
3.onMeasure():回调该方法来进行测量
4.onLayout():回调该方法来确定显示的位置
5.onTouchEvent():监听触摸事件时回调

自定义控件的三种方式:
1.对现有控件进行拓展
2.通过组合来实现新的控件
3.重写View来实现全新的控件


1、对现有控件进行拓展
一般来说,可以通过onDraw()方法对原生控件行为进行拓展,增加新的功能,修改显示的UI等。

先来个简单的:

[java]  view plain  copy
  1. /** 
  2.  * Created by Layne_Yao on 2017-11-17 上午11:50:38. 
  3.  * CSDN:http://blog.csdn.net/Jsagacity 
  4.  */  
  5. public class CustomTextView extends TextView {  
  6.   
  7.     private Paint paint1, paint2;  
  8.   
  9.     public CustomTextView(Context context) {  
  10.         super(context);  
  11.         // TODO Auto-generated constructor stub  
  12.          initPaint();  
  13.     }  
  14.       
  15.     public CustomTextView(Context context, AttributeSet attrs) {  
  16.         super(context, attrs);  
  17.         initPaint();  
  18.     }  
  19.      
  20.     public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {  
  21.         super(context, attrs, defStyleAttr);  
  22.         // TODO Auto-generated constructor stub  
  23.          initPaint();  
  24.     }  
  25.   
  26.     /** 
  27.      * 初始化画笔 
  28.      */  
  29.     private void initPaint() {  
  30.         paint1 = new Paint();  
  31.         paint1.setColor(Color.RED);  
  32.         paint1.setStyle(Paint.Style.FILL);  
  33.         paint2 = new Paint();  
  34.         paint2.setColor(Color.YELLOW);  
  35.         paint2.setStyle(Paint.Style.FILL);  
  36.     }  
  37.   
  38.     @Override  
  39.     protected void onDraw(Canvas canvas) {  
  40.         //绘制外层矩形  
  41.         canvas.drawRect(00, getMeasuredWidth(), getMeasuredHeight(), paint1);  
  42.         //绘制内层矩形  
  43.         canvas.drawRect(1010, getMeasuredWidth() - 10, getMeasuredHeight() - 10, paint2);  
  44.         canvas.save();  
  45.         //绘制文字前平移10像素  
  46.         canvas.translate(100);  
  47.         //父类完成的方法,即绘制文本  
  48.         super.onDraw(canvas);  
  49.         canvas.restore();  
  50.     }  
  51. }  

来个复杂点的:

[java]  view plain  copy
  1. /** 
  2.  * Created by Layne_Yao on 2017-11-18 上午9:47:25. 
  3.  * CSDN:http://blog.csdn.net/Jsagacity 
  4.  */  
  5. public class DynamicsTextView extends TextView{  
  6.   
  7.      int mViewWidth = 0;  
  8.         private Paint mPaint;  
  9.         private LinearGradient mLinearGradient;  
  10.         private Matrix matrix;  
  11.         private int mTranslate;  
  12.           
  13.     public DynamicsTextView(Context context) {  
  14.         super(context);  
  15.           
  16.     }  
  17.       
  18.     public DynamicsTextView(Context context, AttributeSet attrs,  
  19.             int defStyleAttr) {  
  20.         super(context, attrs, defStyleAttr);  
  21.         // TODO Auto-generated constructor stub  
  22.     }  
  23.   
  24.     public DynamicsTextView(Context context, AttributeSet attrs) {  
  25.         super(context, attrs);  
  26.         // TODO Auto-generated constructor stub  
  27.     }  
  28.   
  29.     @Override  
  30.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  31.         // TODO Auto-generated method stub  
  32.         super.onSizeChanged(w, h, oldw, oldh);  
  33.         if (mViewWidth == 0) {  
  34.             mViewWidth = getMeasuredWidth();  
  35.             if (mViewWidth > 0) {  
  36.                 mPaint = getPaint();  
  37.                 mLinearGradient = new LinearGradient(00, mViewWidth, 0new int[]{Color.BLUE, 0xffffffff, Color.BLUE},  
  38.                         null, Shader.TileMode.CLAMP);  
  39.                 mPaint.setShader(mLinearGradient);  
  40.                 matrix = new Matrix();  
  41.             }  
  42.         }  
  43.           
  44.     }  
  45.       
  46.      @Override  
  47.         protected void onDraw(Canvas canvas) {  
  48.             super.onDraw(canvas);  
  49.             if (matrix != null) {  
  50.                 mTranslate += mViewWidth + 5;  
  51.                 if (mTranslate > 2 * mViewWidth / 5) {  
  52.                     mTranslate = -mViewWidth;  
  53.                 }  
  54.                 matrix.setTranslate(mTranslate, 0);  
  55.                 mLinearGradient.setLocalMatrix(matrix);  
  56.                 postInvalidateDelayed(500);  
  57.             }  
  58.         }  
  59. }  

运行结果:



2、创建复合控件
创建复合控件可以很好地创建出具有重用功能的控件集合。这种方式通常需要继承一个合适的ViewGroup,再添加指定功能的控件,从而组合成新的复合控件。通过这种方式创建的控件,我们一般会给它指定一些可配置的属性,让她具有更强的拓展性。

复合控件:

[java]  view plain  copy
  1. /** 
  2.  * Created by Layne_Yao on 2017-11-18 上午10:36:12. 
  3.  * CSDN:http://blog.csdn.net/Jsagacity 
  4.  */  
  5. public class ToolBar extends RelativeLayout {  
  6.       // 包含topbar上的元素:左按钮、右按钮、标题  
  7.     private Button mLeftButton, mRightButton;  
  8.     private TextView mTitleView;  
  9.   
  10.     // 布局属性,用来控制组件元素在ViewGroup中的位置  
  11.     private LayoutParams mLeftParams, mTitlepParams, mRightParams;  
  12.   
  13.     // 左按钮的属性值,即我们在atts.xml文件中定义的属性  
  14.     private int mLeftTextColor;  
  15.     private Drawable mLeftBackground;  
  16.     private String mLeftText;  
  17.     // 右按钮的属性值,即我们在atts.xml文件中定义的属性  
  18.     private int mRightTextColor;  
  19.     private Drawable mRightBackground;  
  20.     private String mRightText;  
  21.     // 标题的属性值,即我们在atts.xml文件中定义的属性  
  22.     private float mTitleTextSize;  
  23.     private int mTitleTextColor;  
  24.     private String mTitle;  
  25.   
  26.     // 映射传入的接口对象  
  27.     private topbarClickListener mListener;  
  28.   
  29.     public ToolBar(Context context, AttributeSet attrs, int defStyle) {  
  30.         super(context, attrs, defStyle);  
  31.     }  
  32.   
  33.     public ToolBar(Context context) {  
  34.         super(context);  
  35.     }  
  36.   
  37.     public ToolBar(Context context, AttributeSet attrs) {  
  38.         super(context, attrs);  
  39.         // 设置topbar的背景  
  40.         setBackgroundColor(0xFFF59563);  
  41.         // 通过这个方法,将你在atts.xml中定义的declare-styleable  
  42.         // 的所有属性的值存储到TypedArray中  
  43.         TypedArray ta = context.obtainStyledAttributes(attrs,  
  44.                 R.styleable.TopBar);  
  45.         // 从TypedArray中取出对应的值来为要设置的属性赋值  
  46.         mLeftTextColor = ta.getColor(  
  47.                 R.styleable.TopBar_leftTextColor, 0);  
  48.         mLeftBackground = ta.getDrawable(  
  49.                 R.styleable.TopBar_leftBackground);  
  50.         mLeftText = ta.getString(R.styleable.TopBar_leftText);  
  51.   
  52.         mRightTextColor = ta.getColor(  
  53.                 R.styleable.TopBar_rightTextColor, 0);  
  54.         mRightBackground = ta.getDrawable(  
  55.                 R.styleable.TopBar_rightBackground);  
  56.         mRightText = ta.getString(R.styleable.TopBar_rightText);  
  57.   
  58.         mTitleTextSize = ta.getDimension(  
  59.                 R.styleable.TopBar_titleTextSize, 10);  
  60.         mTitleTextColor = ta.getColor(  
  61.                 R.styleable.TopBar_titleTextColor, 0);  
  62.         mTitle = ta.getString(R.styleable.TopBar_title_text);  
  63.   
  64.         // 获取完TypedArray的值后,一般要调用  
  65.         // recyle方法来避免重新创建的时候的错误  
  66.         ta.recycle();  
  67.   
  68.         mLeftButton = new Button(context);  
  69.         mRightButton = new Button(context);  
  70.         mTitleView = new TextView(context);  
  71.   
  72.         // 为创建的组件元素赋值  
  73.         // 值就来源于我们在引用的xml文件中给对应属性的赋值  
  74.         mLeftButton.setTextColor(mLeftTextColor);  
  75.         mLeftButton.setBackgroundDrawable(mLeftBackground);  
  76.         mLeftButton.setText(mLeftText);  
  77.   
  78.         mRightButton.setTextColor(mRightTextColor);  
  79.         mRightButton.setBackgroundDrawable(mRightBackground);  
  80.         mRightButton.setText(mRightText);  
  81.   
  82.         mTitleView.setText(mTitle);  
  83.         mTitleView.setTextColor(mTitleTextColor);  
  84.         mTitleView.setTextSize(mTitleTextSize);  
  85.         mTitleView.setGravity(Gravity.CENTER);  
  86.   
  87.         // 为组件元素设置相应的布局元素  
  88.         mLeftParams = new LayoutParams(  
  89.                 LayoutParams.WRAP_CONTENT,  
  90.                 LayoutParams.MATCH_PARENT);  
  91.         mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);  
  92.         // 添加到ViewGroup  
  93.         addView(mLeftButton, mLeftParams);  
  94.   
  95.         mRightParams = new LayoutParams(  
  96.                 LayoutParams.WRAP_CONTENT,  
  97.                 LayoutParams.MATCH_PARENT);  
  98.         mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);  
  99.         addView(mRightButton, mRightParams);  
  100.   
  101.         mTitlepParams = new LayoutParams(  
  102.                 LayoutParams.WRAP_CONTENT,  
  103.                 LayoutParams.MATCH_PARENT);  
  104.         mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);  
  105.         addView(mTitleView, mTitlepParams);  
  106. }  

运行结果:



3、重写View来实现全新的控件

自定义一个View时通常需要继承View类,并重写它的onDraw(),onMeasure()等方法来实现绘制逻辑,同时通过重写onTouchEvent()等触控时间来实现交互逻辑。

案例一:

[java]  view plain  copy
  1. /** 
  2.  * Created by Layne_Yao on 2017-11-18 上午11:08:23. 
  3.  * CSDN:http://blog.csdn.net/Jsagacity 
  4.  */  
  5. public class CircleProgressView extends View {  
  6.   
  7.     private int mCircleXY;  
  8.     private int length;  
  9.     private float mRadius;  
  10.   
  11.     private Paint mCirclePaint;  
  12.     private Paint mArcPaint;  
  13.     private Paint mTextPaint;  
  14.     private String mShowText = "Android skill";  
  15.   
  16.     private int mTextSize = 25;  
  17.     private float mSweepValue = 270;  
  18.   
  19.     public CircleProgressView(Context context, AttributeSet attrs) {  
  20.         super(context, attrs);  
  21.         // TODO Auto-generated constructor stub  
  22.         // 获取屏幕高宽  
  23.         WindowManager wm = (WindowManager) getContext().getSystemService(  
  24.                 Context.WINDOW_SERVICE);  
  25.         length = wm.getDefaultDisplay().getWidth();  
  26.         init();  
  27.     }  
  28.   
  29.     private void init() {  
  30.         mCircleXY = length / 2;  
  31.         mRadius = (float) (length * 0.5 / 2);  
  32.   
  33.         mCirclePaint = new Paint();  
  34.         mCirclePaint.setColor(Color.BLUE);  
  35.   
  36.         mArcPaint = new Paint();  
  37.         mArcPaint.setStrokeWidth(50);  
  38.         mArcPaint.setStyle(Paint.Style.STROKE);  
  39.         mArcPaint.setColor(Color.BLUE);  
  40.   
  41.         mTextPaint = new Paint();  
  42.         mTextPaint.setColor(Color.WHITE);  
  43.         mTextPaint.setTextSize(mTextSize);  
  44.     }  
  45.   
  46.     @Override  
  47.     protected void onDraw(Canvas canvas) {  
  48.         super.onDraw(canvas);  
  49.         // 矩形  
  50.         RectF mArcRectF = new RectF((float) (length * 0.1),  
  51.                 (float) (length * 0.1), (float) (length * 0.9),  
  52.                 (float) (length * 0.9));  
  53.         // 绘制圆  
  54.         canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);  
  55.         // 绘制弧线  
  56.         canvas.drawArc(mArcRectF, 270, mSweepValue, false, mArcPaint);  
  57.         // 绘制文字  
  58.         canvas.drawText(mShowText, 0, mShowText.length(), mCircleXY, mCircleXY  
  59.                 + (mTextSize / 4), mTextPaint);  
  60.     }  
  61.   
  62.     public void setSweepValue(float sweepValue) {  
  63.         if (sweepValue != 0) {  
  64.             mSweepValue = sweepValue;  
  65.         } else {  
  66.             mSweepValue = 25;  
  67.         }  
  68.         invalidate();  
  69.     }  
  70.   
  71. }  

案例二:

[java]  view plain  copy
  1. /** 
  2.  * Created by Layne_Yao on 2017-11-20 上午11:29:02. 
  3.  * CSDN:http://blog.csdn.net/Jsagacity 
  4.  */  
  5. public class VolumeView extends View {  
  6.     private int mWidth;  
  7.     private int mRectWidth;  
  8.     private int mRectHeight;  
  9.     private Paint mPaint;  
  10.     private int mRectCount;  
  11.     private int offset = 5;  
  12.     private double mRandom;  
  13.     private LinearGradient mLinearGradient;  
  14.   
  15.     public VolumeView(Context context) {  
  16.         super(context);  
  17.         initView();  
  18.     }  
  19.   
  20.     public VolumeView(Context context, AttributeSet attrs) {  
  21.         super(context, attrs);  
  22.         initView();  
  23.     }  
  24.   
  25.     public VolumeView(Context context, AttributeSet attrs, int defStyleAttr) {  
  26.         super(context, attrs, defStyleAttr);  
  27.         initView();  
  28.     }  
  29.   
  30.     private void initView() {  
  31.         mPaint = new Paint();  
  32.         mPaint.setColor(Color.BLUE);  
  33.         mPaint.setStyle(Paint.Style.FILL);  
  34.         mRectCount = 12;  
  35.     }  
  36.   
  37.     @Override  
  38.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  39.         super.onSizeChanged(w, h, oldw, oldh);  
  40.         mWidth = getWidth();  
  41.         mRectHeight = getHeight();  
  42.         mRectWidth = (int) (mWidth * 0.6 / mRectCount);  
  43.         mLinearGradient = new LinearGradient(00, mRectWidth, mRectHeight,  
  44.                 Color.YELLOW, Color.BLUE, Shader.TileMode.CLAMP);  
  45.         mPaint.setShader(mLinearGradient);  
  46.     }  
  47.   
  48.     @Override  
  49.     protected void onDraw(Canvas canvas) {  
  50.         super.onDraw(canvas);  
  51.         for (int i = 0; i < mRectCount; i++) {  
  52.             mRandom = Math.random();  
  53.             float currentHeight = (float) (mRectHeight * mRandom);  
  54.             canvas.drawRect(  
  55.                     (float) (mWidth * 0.4 / 2 + mRectWidth * i + offset),  
  56.                     currentHeight, (float) (mWidth * 0.4 / 2 + mRectWidth  
  57.                             * (i + 1)), mRectHeight, mPaint);  
  58.         }  
  59.         postInvalidateDelayed(300);  
  60.     }  
  61. }  


运行结果:

   

自定义ViewGroup:
ViewGroup存在的目的就是为了对其子View进行管理,为其子View添加显示、相应的规则。因此,自定义ViewGroup通常需要重写onMeasure()方法来对子View进行测量,重写onLayout()方法来确定子View的位置,重写onTouchEvent()方法增加相应事件,下面看看如何自定义ViewGroup。

[java]  view plain  copy
  1. /** 
  2.  * Created by Layne_Yao on 2017-11-18 下午3:59:32. 
  3.  * CSDN:http://blog.csdn.net/Jsagacity 
  4.  */  
  5. public class CustomScrollView extends ViewGroup {  
  6.     private int mScreenHeight;  
  7.     private Scroller mScroller;  
  8.     private int mLastY;  
  9.     private int mStart;  
  10.     private int mEnd;  
  11.   
  12.     public CustomScrollView(Context context, AttributeSet attrs) {  
  13.         super(context, attrs);  
  14.         // 获取屏幕高宽  
  15.         WindowManager wm = (WindowManager) getContext().getSystemService(  
  16.                 Context.WINDOW_SERVICE);  
  17.         mScreenHeight = wm.getDefaultDisplay().getHeight();  
  18.         mScroller = new Scroller(getContext());  
  19.     }  
  20.   
  21.     @Override  
  22.     protected void onLayout(boolean b, int i, int i1, int i2, int i3) {  
  23.         int childCount = getChildCount();  
  24.         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();  
  25.         mlp.height = mScreenHeight * childCount;  
  26.         setLayoutParams(mlp);  
  27.   
  28.         for (int j = 0; j < childCount; j++) {  
  29.             View child = getChildAt(j);  
  30.             if (child.getVisibility() != View.GONE) {  
  31.                 child.layout(i, j * mScreenHeight, i2, (j + 1) * mScreenHeight);  
  32.             }  
  33.         }  
  34.     }  
  35.   
  36.     @Override  
  37.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  38.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  39.         int count = getChildCount();  
  40.         for (int i = 0; i < count; ++i) {  
  41.             View childView = getChildAt(i);  
  42.             measureChild(childView, widthMeasureSpec, heightMeasureSpec);  
  43.         }  
  44.     }  
  45.   
  46.     @Override  
  47.     public boolean onTouchEvent(MotionEvent event) {  
  48.         int y = (int) event.getY();  
  49.         switch (event.getAction()) {  
  50.         case MotionEvent.ACTION_DOWN:  
  51.             mLastY = y;  
  52.             mStart = getScrollY();  
  53.             break;  
  54.         case MotionEvent.ACTION_MOVE:  
  55.             if (!mScroller.isFinished()) {  
  56.                 mScroller.abortAnimation();  
  57.             }  
  58.             int dy = mLastY - y;  
  59.             if (getScrollY() < 0) {  
  60.                 dy = 0;  
  61.             }  
  62.             if (getScrollY() > getHeight() - mScreenHeight) {  
  63.                 dy = 0;  
  64.             }  
  65.             scrollBy(0, dy);  
  66.             mLastY = y;  
  67.             break;  
  68.         case MotionEvent.ACTION_UP:  
  69.             mEnd = getScrollY();  
  70.             int dScrollY = mEnd - mStart;  
  71.             if (dScrollY > 0) {  
  72.                 if (dScrollY < mScreenHeight / 3) {  
  73.                     mScroller.startScroll(0, getScrollY(), 0, -dScrollY);  
  74.                 } else {  
  75.                     mScroller.startScroll(0, getScrollY(), 0, mScreenHeight  
  76.                             - dScrollY);  
  77.                 }  
  78.             } else {  
  79.                 if (-dScrollY < mScreenHeight / 3) {  
  80.                     mScroller.startScroll(0, getScrollY(), 0, -dScrollY);  
  81.                 } else {  
  82.                     mScroller.startScroll(0, getScrollY(), 0, -mScreenHeight  
  83.                             - dScrollY);  
  84.                 }  
  85.             }  
  86.             break;  
  87.         }  
  88.         postInvalidate();  
  89.         return true;  
  90.     }  
  91.   
  92.     @Override  
  93.     public void computeScroll() {  
  94.         super.computeScroll();  
  95.         if (mScroller.computeScrollOffset()) {  
  96.             scrollTo(0, mScroller.getCurrY());  
  97.         }  
  98.     }  
  99. }  

运行结果:



事件拦截机制分析
要了解事件拦截机制,首先要了解一下触摸事件。
触摸事件就是捕获触摸屏幕后产生的时间。当点击一个按钮时,通过会产生两个或者三个时间---按钮按下,这是事件一;如果不小心滑动了,这是事件二;当手抬起,这是事件三。
触摸事件其实就是一个动作类型加坐标而已,但是Android的View结构是属性结构的,也就是说,View可以放在ViewGroup里面,通过不同的组合来实现不同的样式。
那么问题来了,View放在一个ViewGroup里面,这个ViewGroup又放在另一个ViewGroup里面,甚至还有可能继续嵌套,一层层地叠起来。可是触摸事件就一个到底分给谁?同一个时间,子View和父ViewGroup都有可能想要进行处理。因此就产生了“事件拦截”这个“霸气”的称呼。
我们定义两个ViewGroup,两个都重写以下方法:

[java]  view plain  copy
  1. @Override  
  2.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.         Log.e("MyView""MyViewGroupA-dispatchTouchEvent:"+ev.getAction());  
  4.         return super.dispatchTouchEvent(ev);  
  5.     }  
  6.   
  7.     @Override  
  8.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  9.         Log.e("MyView""MyViewGroupA-onInterceptTouchEvent:"+ev.getAction());  
  10.         return super.onInterceptTouchEvent(ev);  
  11.     }  
  12.       
  13.       
  14.     @Override  
  15.     public boolean onTouchEvent(MotionEvent event) {  
  16.         Log.e("MyView""MyViewGroupA-onTouchEvent:"+event.getAction());  
  17.         return super.onTouchEvent(event);  
  18.     }  

定义一个View,重写以下方法

[java]  view plain  copy
  1. @Override  
  2. public boolean onTouchEvent(MotionEvent event) {  
  3.     Log.e("MyView""MyView-onTouchEvent:"+event.getAction());  
  4.     return super.onTouchEvent(event);  
  5. }  
  6.   
  7.   
  8. @Override  
  9. public boolean dispatchTouchEvent(MotionEvent event) {  
  10.     Log.e("MyView""MyView-dispatchTouchEvent:"+event.getAction());  
  11.     return super.dispatchTouchEvent(event);  
  12. }  

布局情况:

[java]  view plain  copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context="com.itman.customviewdemo.intercept.MyViewActivity" >  
  6.   
  7.     <com.itman.customviewdemo.intercept.MyViewGroupA  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="match_parent" >  
  10.   
  11.         <com.itman.customviewdemo.intercept.MyViewGroupB  
  12.             android:layout_width="300dp"  
  13.             android:layout_height="300dp" >  
  14.   
  15.             <com.itman.customviewdemo.intercept.MyView  
  16.                 android:layout_width="100dp"  
  17.                 android:layout_height="100dp" />  
  18.         </com.itman.customviewdemo.intercept.MyViewGroupB>  
  19.     </com.itman.customviewdemo.intercept.MyViewGroupA>  
  20.   
  21. </RelativeLayout>  

正常的情况下,时间的传递顺序是:
ViewGroupA->ViewGroupB->View
事件传递的时候,先执行dispatchTouchEvent()方法,再执行onInterceptTouchEvent()方法。


事件的处理顺序是:
View->ViewGroupB->ViewGroupA
事件处理都是执行onTouchEvent()方法。


事件传递的返回值非常容易理解:true,拦截,不继续;false不拦截,继续流程。
时间处理的返回值也类似:true,处理了,不用审核;false,继续执行。
初始情况下,返回值都是false。

默认情况下运行,打印的Log如下:


把MyViewGroupA的onInterceptTouchEvent(MotionEvent ev)返回值改为true,打印的Log如下:


把MyViewGroupB的onTouchEvent(MotionEvent event)返回值修改为true,打印Log如下:


源码下载


猜你喜欢

转载自blog.csdn.net/sinat_34153600/article/details/79932411