Android群英传之Android控件架构与自定义控件
2016-07-23 00:17
513 查看
1、Android控件架构
ViewGroup可以包含多个View,形成控件树。上层控件负责下层子控件的测量与绘制。findViewById是在控件树中进行深度优先搜索。ViewGroup实现了ViewParent接口,Viewparent定义了一个控件作为父控件的职责,负责子布局与父布局的交互,例如requestLayout。UI界面架构图
每个Activity都有一个Window对象,一般由PhoneWindow实现,各种监听事件都通过WindowManagerService接收,PhoneWindow将DecorView作为整个应用窗口的根View。DecorView分为两部分:TitleView和ContentView。
为什么requestWindowFeature方法一定要在setContentView之前才能生效?
因为一般的视图树会默认基本分为两部分,上面TitleBar下面Content,在requestWindowFeature(Window.Feature_NO_TITLE)后,DecorView只剩Content。而在setContentView后,WindowManagerService会回调onResume,把整个DecorView添加到PhoneWindow,让其显示。若在setContentView之后再设置requestWindowFeature,因为DecorView已经被添加了,所以再改变也没有用了。
2、View的测量
即使我们不看Android的View源码,对于一个手机上的视图,我们也应该想到,它应该是先确定大小,再确定它的位置,最后进行绘制。核心类:MeasureSpec
核心方法:MeasureSpec.getMode、MeasureSpec.getSize
MeasureSpec是一个32位的int值,高2位是测量模式,低30位是测量大小。
测量模式有三种
1. EXACTLY:精确值模式,属性设置为具体数值或match_parent时,使用此模式
2. AT_MOST:最大值模式,属性设置为wrap_content时,使用此模式
3. UNSPECIFIED:不指定大小测量模式,通常在绘制自定义View时才会用到
View类默认的onMeasure方法只支持EXACTLY模式,想让自定义View支持wrap_content属性,必须重写onMeasure方法来指定wrap_content时的大小
重写onMeasure后,最终的的工作就是把测量后的宽高值作为参数设置给setMeasuredDimension方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //计算宽和高 //模板代码... widthMeasureSpec = measureWidth(widthMeasureSpec); heightMeasureSpec = measureHeight(heightMeasureSpec); setMeasuredDimension(widthMeasureSpec,heightMeasureSpec); }
计算宽度值的模板代码(计算高度同理)
private int measureWidth(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; }
3、View的绘制
重写View的onDraw(Canvas canvas),在画布上绘图即可通常情况下,为啥Canvas对象的创建要传入参数Bitmap?(好吧,通常情况?我为啥不知道)
穿进去的Bitmap与通过Bitmap创建的Canvas是紧紧联系在一起的,这个Bitmap用来存储所有绘制在Canvas上的像素信息,当使用Bitmap创建Canvas后,后面调用的所有Canvas.drawXXX方法都发生在这个Bitmap上。
还是不太明白,绘制内容都放在bitmap上而不绘制在画布上,貌似有利于重用bitmap在多个画布上?
4、ViewGroup的测量
ViewGroup在测量时遍历所有的子View,调用子View的measure方法来返回每一个子view的大小。测量完毕后,就把子View放到合适的位置,就是Layout的过程。
5、ViewGroup的绘制
除非要指定ViewGroup的背景,否则ViewGroup的onDraw不会被调用,但是ViewGroup会使用dispatchDraw来遍历子View,调用他们的绘制方法。6、自定义View
有三种自定义View的方式1)对现有控件进行拓展
对现有控件进行拓展的代码结构:@Override protected void onDraw(Canvas canvas) { //在回调父类方法之前实现自己的逻辑,对TextView来说就是在绘制文本之前 super.onDraw(canvas); //在回调父类方法之后实现自己的逻辑,对TextView来说就是在绘制文本之后 }
书中对TextView进行拓展的例子
private void initView() { mPaint1 = new Paint(); mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light)); mPaint1.setStyle(Paint.Style.FILL); mPaint2 = new Paint(); mPaint2.setColor(Color.YELLOW); mPaint2.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { // 绘制外层矩形 canvas.drawRect( 0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint1); // 绘制内层矩形 canvas.drawRect( 10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, mPaint2); canvas.save(); // 绘制文字前平移10像素 canvas.translate(10, 0); // 父类完成的方法,即绘制文本 super.onDraw(canvas); canvas.restore(); }
2)创建复合控件
一般需要继承一个合适的ViewGroupa、定义属性
在res资源目录的value目录下创建一个attrs.xml,一般形如:<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="TitleBar"> <attr name="title" format="string" /> <attr name="titleTextColor" format="color" /> <attr name="titleTextSize" format="dimension" /> </declare-styleable> </resources>
自定义属性的使用
//获取自定义属性的对象 TypedArray TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTitle); //getString 获取String类型的值 mTitle = ta.getString(R.styleable.MyTitle_title); //getColor 获取Color类型的值 mTitleColor = ta.getColor(R.styleable.MyTitle_titleTextColors, 0); //getDimension 获取尺寸类型的值 mTitleSize = ta.getDimension(R.styleable.MyTitle_titleTextSize, 0); //一般到最后,调用recyle避免重新创建时的错误 ta.recycle();
b、组合控件
给自定义控件里面的基本控件设置前面获取的属性。定义接口,实现回调
c、引用UI模板
//引入命名空间,此命名空间为custom xmlns:custom="http://schemas.android.com/apk/res-auto" //自定义属性的定义 custom:title="标题" custom:titleTextColor="#123412" custom:titleTextSize="15sp"
3)重写View实现全新的控件
创建自定义View的难点在于绘制控件和实现交互,通常需要继承View类,并重写onDraw、onMeasure等方法来实现绘制逻辑,同时通过重写onTouchEvent等触控事方法来实现交互逻辑。7、自定义ViewGroup
一般需要重写onMeasure方法来对子View进行测量,onLayout来确定子View的位置,onTouchEvent来增加响应时间。8、事件拦截机制
核心类:MotionEvent核心方法:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
dispatchTouchEvent方法一般不管。ViewGroup比View多重写了一个onInterceptTouchEvent方法,也就是事件拦截机制的核心方法。
onInterceptTouchEvent方法返回值:true,拦截,false,不拦截
onTouchEvent方法返回值:true,代表已自行处理,不往上传递,未处理(其实可以偷偷处理,就告诉你没处理false),继续往上传递
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories