常用控件和布局的继承结构,如下图:
(待续。。。。)
所有的控件都是直接或间接继承自View的,所用的所有布局都是直接或间接继承自ViewGroup的,View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,ViewGroup是一中特殊的View,它可以包含很多的子View和子ViewGroup,是一个用于放置控件和布局的容器。
我们可以利用上面的继承结构来创建自定义控件,创建自定义控件的两种简单方法如下所示。
我们先创建一个UICustomViews项目。
1、引入布局
Android系统已经给每个活动提供了标题栏功能,但我们决定先不使用它,而是创建一个自定义的标题栏。
只需要两个Button和一个TextView,然后在布局中摆好就可以了,但是一般我们的程序中可能有很多个活动都需要这样的标题栏,如果在每个活动的布局中都编写一遍同样的标题栏代码,明显的就会导致代码的大量重复,这个时候,我们可以引入布局的方式来解决这个问题。我们在layout目录下新建一个布局文件title.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:id="@+id/title_back"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="5dp"android:background="#000"android:text="Back"android:textColor="#f00"/><TextViewandroid:id="@+id/title_text"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_weight="1"android:gravity="center"android:background="#000"android:text="Title Text"android:textColor="#f5f"android:textSize="24sp"/><Buttonandroid:id="@+id/title_edit"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="5dp"android:background="#000"android:text="Edit"android:textColor="#f00"/>
</LinearLayout>
我们在LinearLayout中分别加入了两个Batton和一个TextView,左边的Button可用于返回,右边的Button可用于编辑,中间的TextView则可以用来显示一段标题文本。熟悉一下属性,其中android:backgroud用于为布局或控件指定一个背景,可以使用颜色或者是照片来进行填充。
另外在两个Button中我们都使用了android:layout_margin这个属性,它可以指定控件在上下左右方向上偏移的距离,当然也可以使用android:layout_marginLeft或者android:layout_marginTop等属性来单独指定控件在某个方向上偏移的距离。
那么标题栏文件title.xml我们已经编写完成,那我们如何在程序中使用这个标题栏呢,修改acticity_main.xml中的代码,如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.uicustomviews.MainActivity"> <include layout="@layout/title"/></LinearLayout>
我们只需要通过一行include语句将标题栏布局引进来就可以了。
但是最后,不要忘了在MainActivity中将系统自带的标题栏隐藏掉,代码如下:
package com.example.uicustomviews;import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); ActionBar actionBar = getSupportActionBar();if(actionBar != null){actionBar.hide();}}
}
我们调用了getSupportActionBar()方法来获得ActionBar的实例,然后再调用ActionBar的hide()方法将标题栏隐藏起来。
使用这种方式,不管有多少布局需要标题栏,只需要一行include语句就可以了。
测试用例如下:
2、创建自定义组件
引入布局的技巧确实解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每个活动中为这些控件单独编写一次事件注册的代码。比如说,标题栏的返回按钮,不管在哪一个活动中,都是用来销毁当前活动。为了不增加重复代码,我们使用自定义控件的方式来解决。
新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件,代码如下:
package com.example.uicustomviews;import android.content.Context;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ActionBar actionBar = getSupportActionBar();if(actionBar != null){actionBar.hide();}}public class TitleLayout extends LinearLayout {public TitleLayout(Context context, AttributeSet attrs) {super(context, attrs);LayoutInflater.from(context).inflate(R.layout.title, this);}}
}
我们重写了LinearLayout中带有两个参数的构造函数,在布局文件中引入TitleLayout控件就会调用这个构造函数,然后在构造函数中需要对标题进行动态加载,这就是要LayoutInflator来实现了。通过LayoutInflator的from()方法可以构建出一个LayoutInflater对象,然后调用inflator()方法就可以动态加载一个布局文件,inflate()接收两个参数,第一个参数是要加载的布局文件的id,我们传入的是R.layout.title,第二个参数是给加载好的布局再添加一个父布局,我们指定为TitleLayout,于是直接传入this。
现在自定义控件已经建好,3、我们还需要在布局文件中添加这个自定义控件,修改activity_main.xml中的代码,如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" ><com.example.uicustomviews.TitleLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>
重新运行程序!
为标题栏的按钮注册点击事件,修改TitleLayout中的代码,如下:
package com.example.uicustomviews;import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;public class TitleLayout extends LinearLayout {public TitleLayout(Context context, AttributeSet attrs) {super(context, attrs);LayoutInflater.from(context).inflate(R.layout.title, this); Button titleBack = (Button) findViewById(R.id.title_back);Button titleEdit = (Button) findViewById(R.id.title_edit);titleBack.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {((Activity) getContext()).finish();}});titleEdit.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(getContext(), "You clicked Edit button",Toast.LENGTH_SHORT).show();}});}}
首先通过findViewById()方法得到按钮的实例,然后分别调用setOnClickListener()方法给两个按钮注册了点击事件,当点击返回按钮时,销毁当前的活动,当点击编辑按钮时弹出一段文本。
效果如下图: