用户界面View(二)

本文详细解析Android UI组件的基类View及其核心生命周期方法onMeasure、onLayout和onDraw,深入探讨视图状态如enabled、focused等,以及实现自定义视图的方法,包括自绘控件、组合控件和继承控件。并通过示例展示如何创建一个计数器、标题栏和扩展ListView功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

没有目标的人永远为有目标的人去努力。


本讲内容: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的布局(确定视图的位置

layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。


三. onDraw() 用于绘制出子View

接收一个Canvas参数,Canvas类相当一块画布,在上面绘制任意的东西


四、视图状态(譬如:按钮点击与未点击时的效果
1)、常用的几种视图状态
1、 enabled   表示当前视图是否可用
调用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表示未按下。通常情况下这个状态都是由系统自动赋值的,但开发者也可以自己调用这个方法来进行改变。


五、视图重绘(更新
Android中实现view的重绘有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。

调用视图的setVisibility()、setEnabled()、setSelected()等方法时都会导致视图重绘,而如果我们想要手动地强制让视图进行重绘,可以调用invalidate()方法来实现。当然了,setVisibility()、setEnabled()、setSelected()等方法的内部其实也是通过调用invalidate()方法来实现的。
注意:invalidate()方法是不会重新执行measure和layout流程的,如果你希望视图的绘制流程可以完完整整地重新走一遍,可以调用requestLayout()方法。


六、自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件

1)自绘控件
这个View上所展现的内容全部是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的

示例一:自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次

下面是CustomView.java自定义View
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>


2)组合控件
不需要自己去绘制视图上显示的内容,而是用系统原生的控件,将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。

示例二:标题栏就是个很常见的组合控件,标题栏上会有个返回按钮和标题,点击按钮后就可以返回到上一个界面


下面是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>

下面是CustomView.java自定义View
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);
	}
}


3)继承控件

去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能。


示例三:ListView进行扩展,加入在ListView上滑动就可以显示出一个删除按钮,点击按钮就会删除相应数据的功能。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值