②、onLayout(boolean changed, int left, int top, int right, int bottom) 在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;
(3)draw() 操作
draw() 函数利用前面两部分得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作,子类应该修改该方法,因为内部定义了绘图的基本操作:
①、绘制背景;
②、如果视图要绘制渐变框,这里会做些准备工作;
③、绘制视图本身,即调用 onDraw()函数。在View 中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。
注意:对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容的”,其中包含了多个子View,而子View已经实现了自己的绘制方法,因此只需要告诉View绘制自己的就可以,也就是下面的 dispatchDraw() 函数。
④、绘制子视图、即 dispatchDraw() 函数。在View中这事个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类不需实现该方法;
⑤、如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;
⑥、绘制滚动条;
从上面可以看出自定义View 需要最少覆 onMeasure() 和 onDraw()两个方法。
3 、View类的构造
创建一个自定义控件,一般要实现如下三个构造函数,有没有想过为什么要实现三种构造呢,接下来分析原因。
(1)、构造函数【1】 该构造函数,如果在代码中实例化一个View会调用该函数,也就是我们常用的代码中动态添加控件。
(2)、构造函数【2】该构造如果在 xml中引入了该自定义控件会调用此函数。
(3)、构造函数【3】该构造是定义的有默认样式的时候使用到此函数。
public MyCustomView(Context context) {
super(context);
}
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
4 、自定义View增加属性的两种方法:
(1)、在View中定义。通过构造函数中引入 AttributeSet 去查找XML布局的属性名称,然后找到它对应引用的资源ID去找值。
案例:实现一个带文字的图片(图片、文字是onDraw方法重绘实现)
public class MyView extends View {
private String mtext;
private int msrc;
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
int resourceId = 0;
/**
* attrs.getAttributeResourceValue(null, "Text", 0); 注释
*
* 返回“属性”作为资源标识符的值。
* 请注意,这是不同于 {@link #getAttributeNameResource}
* 在该属性中返回该属性所包含的值
* 资源标识符(例如,一个值的形式 “@package:type/resource"”)
* 另一方法返回资源标识属性名称的*标识符。
*
* @param namespace 属性的名称空间检索。
* @param attribute 检索的属性。
* @param defaultValue 如果属性没有找到返回的值,返回默认值。
*/
int textId = attrs.getAttributeResourceValue(null, "Text", 0);
int srcId = attrs.getAttributeResourceValue(null, "Src", 0);
mtext = context.getResources().getText(textId).toString();
msrc = srcId;
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
InputStream is = getResources().openRawResource(msrc);
Bitmap mBitmap = BitmapFactory.decodeStream(is);
//图片和文字的位置需要再定?还有问题
canvas.drawBitmap(mBitmap, 0, 0, paint);
int w = mBitmap.getWidth();
int h = mBitmap.getHeight();
//canvas.drawCircle(40, 90, 15, paint);
canvas.drawText(mtext, w / 2, 20, paint);
}
}
属性 Text, Src在自定义View类的构造方法中读取。
(2)、通过XML为View注册属性。与Android提供的标准属性写法一样。
案例:实现一个带文字说明的ImageView (ImageView+TextView 的组合)
public class MyImageView extends LinearLayout {
public MyImageView(Context context) {
super(context);
}
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
int resourceId = -1;
/**
* 4个参数的意思分别是:
* set:属性值的集合
* attrs:我们要获取的属性的资源ID的一个数组,如同ContextProvider中请求数据库时的Projection数组,就是从一堆属性中我们希望查询什么属性的值
* defStyleAttr:这个是当前Theme中的一个attribute,是指向style的一个引用,当在layout xml中和style中都没有为View指定属性时,会从Theme中这个attribute指向的Style中查找相应的属性值,这就是defStyle的意思,如果没有指定属性值,就用这个值,所以是默认值,但这个attribute要在Theme中指定,且是指向一个Style的引用,如果这个参数传入0表示不向Theme中搜索默认值
* defStyleRes:这个也是指向一个Style的资源ID,但是仅在defStyleAttr为0或defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用
*/
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.MyImageView);
ImageView iv = new ImageView(context);
TextView tv = new TextView(context);
int N = typedArray.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.MyImageView_Oriental:
resourceId = typedArray.getInt(
R.styleable.MyImageView_Oriental, 0);
this.setOrientation(resourceId == 1 ? LinearLayout.HORIZONTAL
: LinearLayout.VERTICAL);
break;
case R.styleable.MyImageView_Text:
resourceId = typedArray.getResourceId(
R.styleable.MyImageView_Text, 0);
tv.setText(resourceId > 0 ? typedArray.getResources().getText(
resourceId) : typedArray
.getString(R.styleable.MyImageView_Text));
break;
case R.styleable.MyImageView_Src:
resourceId = typedArray.getResourceId(
R.styleable.MyImageView_Src, 0);
iv.setImageResource(resourceId > 0 ? resourceId : R.mipmap.ic_launcher);
break;
}
}
addView(iv);
addView(tv);
typedArray.recycle();
}
}
attrs.xml 进行属性声明, 文件放在values目录下
注意:使用Android Studio开发时要引用如自定义属性声明,然后才可以像系统定义的属性一样引用
xmlns:custom="http://schemas.android.com/apk/res-auto"
5 、Android自定义控件还有很多需要学习的地方,下一篇我们继续深入了解,一下是在自定义中长用到的函数。
onFinishInflate() 回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法
onMeasure() 检测View组件及其子组件的大小
onLayout() 当该组件需要分配其子组件的位置、大小时
onSizeChange() 当该组件的大小被改变时
onDraw() 当组件将要绘制它的内容时
onKeyDown 当按下某个键盘时
onKeyUp 当松开某个键盘时
onTrackballEvent 当发生轨迹球事件时
onTouchEvent 当发生触屏事件时
onWindowFocusChanged(boolean) 当该组件得到、失去焦点时
onAtrrachedToWindow() 当把该组件放入到某个窗口时
onDetachedFromWindow() 当把该组件从某个窗口上分离时触发的方法
onWindowVisibilityChanged(int): 当包含该组件的窗口的可见性发生改变时触发的方法