本讲内容:View
View(视图)是所有UI组件的基类,任何一个视图都不可能凭空突然出现在屏幕上,每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()。
注意:
1、getMeasuredHeight()与getHeight的区别:当屏幕可以包裹内容的时候,他们的值相等,只有当view超出屏幕后,才能看出他们的区别:getMeasuredHeight()是实际View的大小,与屏幕无关,而getHeight的大小此时则是屏幕的大小。当超出屏幕后,getMeasuredHeight()等于getHeight()加上屏幕之外没有显示的大小
一、onMeasure() 用于测量视图的大小
每个View在屏幕上显示出来要先经过measure(计算)、layout(布局)和onDraw()(绘制).
1、什么时候调用onMeasure方法?
当父元素正要放置该控件时调用父元素会问子控件一个问题,“你想要用多大地方啊?”,然后传入两个参数——widthMeasureSpec和heightMeasureSpec.这两个值分别用于确定视图的宽度和高度的规格和大小。
更好的方法是你传递View的高度和宽度到setMeasuredDimension方法里,这样可以直接告诉父控件,需要多大地方放置子控件.
MeasureSpec是View的一个内部类,它封装了布局传递的参数specSize和specMode,其中specSize记录的是大小,specMode记录的是规格。widthMeasureSpec和heightMeasureSpec这两个参数使用MeasureSpec类的静态方法getMode和getSize来获取:
java代码:
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
依据specMode的值,(MeasureSpec有3种模式分别是UNSPECIFIED,
EXACTLY和AT_MOST)
如果是AT_MOST,specSize 代表的是最大可获得的空间;
如果是EXACTLY,specSize 代表的是精确的尺寸;
如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。
那么这些模式和我们平时设置的layout参数fill_parent,
wrap_content有什么关系呢?
经过代码测试就知道,当我们设置width或height为fill_parent时,容器在布局时调用子 view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。
而当设置为 wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY, 而MeasureSpec的UNSPECIFIED模式目前还没有发现在什么情况下使用。
View的onMeasure方法默认行为是当模式为UNSPECIFIED时,设置尺寸为mMinWidth(通常为0)或者背景drawable的最小尺寸,当模式为EXACTLY或者AT_MOST时,尺寸设置为传入的MeasureSpec的大小。
如果你不想使用系统默认的测量方式可以重写onMeasure()方法。譬如:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(200, 300);
}
把View默认的测量流程覆盖掉了,不管在布局文件中定义View这个视图的大小是多少,最终在界面上显示的大小都将会是200*300。需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
二、onLayout() 用于设置子View的布局(确定视图的位置)
三. onDraw() 用于绘制出子View
调用setEnable()方法来改变视图的可用状态,传入true表示可用,传入false表示不可用。它们之间最大的区别在于,不可用的视图是无法响应onTouch事件的。
2、focused 表示当前视图是否获得到焦点
调用requestFocus()办法来让视图获得焦点了。而requestFocus()方法也不能保证一定可以让视图获得焦点,它会有一个布尔值的返回值,如果返回true说明获得焦点成功,返回false说明获得焦点失败。一般只有视图在focusable和focusable in touch mode同时成立的情况下才能成功获取焦点,比如说EditText。
3、 window_focused
表示当前视图是否处于正在交互的窗口中,这个值由系统自动决定,应用程序不能进行改变。
4、selected 表示当前视图是否处于选中状态
一个界面当中可以有多个视图处于选中状态,调用setSelected()方法能够改变视图的选中状态,传入true表示选中,传入false表示未选中。
5、 pressed
表示当前视图是否处于按下状态。可以调用setPressed()方法来对这一状态进行改变,传入true表示按下,传入false表示未按下。通常情况下这个状态都是由系统自动赋值的,但开发者也可以自己调用这个方法来进行改变。
public class CustomView extends View implements OnClickListener{
private Paint mPaint;//画笔
private Rect mBounds;//边界
private int mCount;//计数
//当布局中引入CustomView自定义控件就会调用带有两个参数的构造函数
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);// 实例化画笔并打开抗锯齿
mBounds=new Rect();
this.setOnClickListener(this);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(Color.BLUE);
canvas.drawRect(0, 0,getWidth(),getHeight(),mPaint);//矩形左上角坐标(0,0)、右下角坐标(getWidth(),getHeight())
mPaint.setColor(Color.YELLOW);
mPaint.setTextSize(80);
String text=mCount+"";
mPaint.getTextBounds(text, 0, text.length(), mBounds);
int textWidth = mBounds.width();
int textHeight = mBounds.height();
//画出文字并且居中
canvas.drawText(text, getWidth()/2-textWidth/2,getHeight()/2+textHeight/2, mPaint);
}
public void onClick(View v) {
mCount++;
invalidate();
}
}
下面是res/layout/activity_main.xml布局文件:(自定义的View在使用要写出完整的包名,不然系统将无法找到)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.customviewdemo.CustomView
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_centerInParent="true"/>
</RelativeLayout>
不需要自己去绘制视图上显示的内容,而是用系统原生的控件,将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。
下面是res/layout/title.xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#ffcb89" >
<Button
android:id="@+id/id_title_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_centerVertical="true"
android:text="Back"
android:background="@drawable/back_bg"
android:textColor="#fff" />
<TextView
android:id="@+id/id_tetle_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="This is Title"
android:textColor="#fff"
android:textSize="25sp" />
</RelativeLayout>
public class CustomView extends LinearLayout{
private Button back;
private TextView title;
//当布局中引入CustomView自定义控件就会调用带有两个参数的构造函数
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater mInflater=LayoutInflater.from(context);
mInflater.inflate(R.layout.title, this);
back=(Button) findViewById(R.id.id_title_back);
title=(TextView) findViewById(R.id.id_tetle_text);
back.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
((Activity) getContext()).finish();
}
});
}
/**
* 设置标题栏上的文字
*/
public void setTitleText(String text){
title.setText(text);
}
}
去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能。
示例三:ListView进行扩展,加入在ListView上滑动就可以显示出一个删除按钮,点击按钮就会删除相应数据的功能。