Android 开发实践 自定义控件 详解
来源:互联网 发布:物理数据模型转换为sql 编辑:程序博客网 时间:2024/06/16 21:50
转载请注明出处:http://blog.csdn.net/smartbetter/article/details/52194663
要想成为一名合格的 Android 开发者,掌握自定义控件是必不可少的,Android 系统中的控件是每个 Android App 都必不可少的部分,无论是使用系统控件,还是使用自定义控件,这些控件,为我们组成了每个精美的界面,本篇将从 Android 控件架构入手,然后到 View 和 ViewGroup 的测量与绘制,最后分析了一个很重要的知识点,事件的拦截机制,总体来说还是很丰富的。
1.Android控件架构
Android 中控件大致被分为 ViewGroup 控件 (例如RelativeLayout、LinearLayout、FrameLayout等控件) 和 View 控件 (例如TextView、Button等控件),ViewGroup 作为父控件可以包含多个 View 控件,并管理其包含的 View 控件,通过 ViewGroup,整个界面上的控件就形成了一个树形结构,也就是我们常说的控件树,上层控件负责下层子控件的测量与绘制,并传递交互事件。我们在 Activity 中使用的 findViewById() 方法就是控件树中以树的深度优先遍历来查找对应元素。
2.View的测量与绘制
1.View的测量
Android 在绘制 View 之前必须对 View 进行测量,这个过程由 View 类中的 onMeasure() 方法来执行。
/** * onMeasure()方法默认只支持EXACTLY模式,如果想让支持wrap_content,则需要重写该方法 * * @param widthMeasureSpec * @param heightMeasureSpec */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 此时宽高测量条件相同,如不相同则分开写自定义测量值方法 setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));}/** * 自定义测量值 * 通过MeasureSpec类帮助我们测量View * 测量的模式有三种: EXACTLY:精确值模式; AT_MOST:wrap_content; UNSPECIFIED:不指定测量模式,想多大就多大. * * @param measureSpec * @return */private int measure(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); // if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { result = 200; if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result;}
之后就可以在布局文件中引用该View,并通过 android:layout_width 属性和 android:layout_height 属性来控制大小了。
2.View的绘制
当测量好之后,就可以重写View 类中的 onDraw() 方法,并在 Canvas (系统2D绘图API所必须使用到的对象,就像一个画板,使用 Paint 就可以在上面作画了) 对象上来绘制所需要的图形了。
@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas);}
onDraw() 方法中有一个参数就是 Canvas 对象,使用这个 Canvas 对象就可以进行绘图了,而在其他地方,通常需要使用代码创建一个 Canvas 对象:
Canvas canvas = new Canvas(bitmap);
传入的 bitmap 与通过这个 bitmap 创建的 Canvas 画布是紧紧关联在一起的,这个过程我们称之为 装载画布,这个 bitmap 用来存储所有绘制在 Canvas 上的像素信息。所以当你通过这种方式创建了 Canvas 对象后,后面调用的 Canvas.drawXXX() 方法都发生在这个 bitmap 上。
Canvas 的一些常用绘制方法:
3.View的重绘
View 中的 invalidate() 方法是用来重绘 View 的,必须是在 UI 线程中进行工作。比如在修改某个 View 的显示时,invalidate() 的调用是把之前的旧的 View 从 UI 线程队列中pop掉。
有时为了防止使用 invalidate() 刷新过快反而影响效果,可以使用 postInvalidateDelayed(300); 进行 View 的延迟重绘。
3.ViewGroup的测量与绘制
1.ViewGroup的测量
ViewGroup 会管理其子 View,其中一个管理项目就是负责子 View 的显示大小,当 ViewGroup 的大小为 wrap_content 时,ViewGroup 就会对子 View 进行遍历,调用子 View 的 Measure 方法来获取每一个子 View 的测量结果 (前面说的对View的测量,就是在这里进行的),以便获得所有子 View 的大小,从而确定自己的大小,而其他模式下则会通过具体的指定值来设置自身的大小。
在子 View 测量完毕后,就需要将子 View 放到合适的位置,这个过程就是 Layout 过程,ViewGroup 在执行 Layout 过程时,同样是使用遍历来调用子 View 的 Layout 方法,并指定其具体的位置,从而确定其布局位置。
在自定义 ViewGroup 时,通常会重写 onLayout() 方法来控制其子 View 显示位置的逻辑,同样,如果需要支持 wrap_content 属性,那么它还必须重写 onMeasure 方法,这点与 View 是相同的。
2.ViewGroup的绘制
ViewGroup 通常情况下不需要绘制,因为它本身就没有需要绘制的东西,如果不是指定了 ViewGroup 的背景颜色,那么 ViewGroup 的 onDraw() 方法都不会被调用。
4.自定义View
在自定义 View 时,我们通常会去重写 onDraw() 方法来绘制 View 的显示内容,如果该 View 还需要使用 wrap_content 属性,那么还需要重写 onMeasure() 方法。另外,通过自定义 attrs 属性还可以设置新的属性配置项。
在 View 中 有以下一些比较重要的回调方法:
通常有三种方法实现自定义控件:对现有控件进行拓展;通过组合来实现新的控件;重写 View 来实现全新的控件。
1.对现有控件进行拓展
以 TextView 为例,在构造方法初始化画笔并重写其 onDraw() 方法:
mPaint = new Paint();mPaint.setColor(Color.BLUE);mPaint.setStyle(Paint.Style.FILL);
重写其 onDraw() 方法:
@Overrideprotected void onDraw(Canvas canvas) { // 在回调父类方法前实现逻辑,对TextView来说即是在绘制文本内容前 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); canvas.save(); canvas.translate(10, 0); // 平移10像素 super.onDraw(canvas); // 在回调父类方法后实现逻辑,对TextView来说即是在绘制文本内容后 canvas.restore();}
2.重写View来实现全新的控件
创建一个自定义View难点在于绘制控件和实现交互。通常需要继承 View 类,并重写它的 onDraw() 、onMeasure() 等方法来实现绘制逻辑,同时通过重写 onTouchEvent() 等触摸事件来实现交互逻辑,当然还可以像实现组合控件的方式,通过引入自定义属性,丰富自定义 View 的可定制性。
5.自定义ViewGroup
ViewGroup 存在的目的就是为了对其子 View 进行管理,为其子 View 添加显示、响应的规则。因此,自定义 ViewGroup 需要重写 onMeasure() 来对子 View进行测量,重写 onLayout() 方法来确定子 View 的位置,重写 onTouchEvent() 方法增加响应事件。
/** * 使用遍历的方式来通知子View对自身进行测量 */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); for (int i=0; i<count; ++i) { View childView = getChildAt(i); measureChild(childView, widthMeasureSpec, heightMeasureSpec); }}/** * 确定子View的位置 */@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); // 设置ViewGroup的高度 MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); mlp.height = mScreenHeight * childCount; setLayoutParams(mlp); for (int i=0; i<childCount; i++) { View child = getChildAt(i); if (child.getVisibility()!=View.GONE) { child.layout(l, i*mScreenHeight, r, (i+1)*mScreenHeight); } }}/** * 增加响应事件,MotionEvent是Android为触摸事件封装的类 */@Overridepublic boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); switch (event.getAction()) { // 初次触摸 case MotionEvent.ACTION_DOWN: // 记录触摸起点 mLastY = y; mStart = getScaleY(); // 添加其他逻辑 break; // 滑动 case MotionEvent.ACTION_MOVE: break; // 抬起 case MotionEvent.ACTION_UP: // 记录触摸终点 mEnd = getScaleY(); int dScrollY = mEnd - mStart; // 添加其他逻辑 break; } postInvalidate(); return true;}
下来我们对比一下 自定义View 和 自定义ViewGroup 的区别:
6.事件的拦截机制分析
1.组成事件传递机制的三个方法
对于 ViewGroup 来说,可重写如下三个方法:
// 拦截事件,事件拦截的核心方法@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev);}// 处理事件@Overridepublic boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(ev);}// 分发事件@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev);}
对于 View 来说,可重写如下两个方法:
// 处理事件@Overridepublic boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(ev);}// 分发事件@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev);}
初始情况下,返回值都是 false,返回 true 则拦截。
2.深入事件传递机制的逻辑
首先有我们的一个 viewgroupA,在它里面有 viewgroupB,在 viewgroupB 里面有 viewC,当我们触摸 viewC 的时候就会触发事件,其事件传递机制的流程我们用如下表示:
最后送大家一句话:只有站在一个设计者的角度上,才可以更好地创建自定义View。
- Android 开发实践 自定义控件 详解
- Android 自定义控件详解
- Android 自定义控件 详解
- Android自定义控件详解
- Android开发之自定义控件(一)---onMeasure详解
- Android开发之自定义控件(二)---onLayout详解
- Android开发之自定义控件(一)---onMeasure详解
- Android开发之自定义控件(二)---onLayout详解
- Android开发之自定义控件(一)---onMeasure详解
- Android开发之自定义控件(二)---onLayout详解
- 【Android】Android自定义控件详解
- iOS自定义控件开发详解
- Android简易自定义日历控件实践
- android自定义控件属性详解
- Android自定义控件属性详解
- android自定义控件属性详解
- 关于Android自定义控件详解
- Android自定义控件属性详解
- hdu-4793-Collision
- [LeetCode]--25. Reverse Nodes in k-Group
- BZOJ2100: [Usaco2010 Dec]Apple Delivery Spfa+优化
- (二十六)、正则表达式
- 1的个数问题、数组最大最小值java---编程之美
- Android 开发实践 自定义控件 详解
- 【51node】-1006- 最长公共子序列Lcs(LCS,输出序列,模板)
- 执行远程服务器上的脚本失败?(环境变量引起的问题)
- Java泛型
- 树-堆结构练习——合并果子之哈夫曼树
- 解决缺失libgtk-x11-2.0.so.0的问题
- UnicodeDecodeError
- 带武器的角色
- TP框架,邮箱发送