【Android成长之路】创建自定义控件

来源:互联网 发布:js数组删除元素的代码 编辑:程序博客网 时间:2024/05/21 04:41

前言:我们所用的所有控件都是直接或间接继承自View 的,所用的所有布局都是直接或间接继承自ViewGroup 的View 是Android 中一种最基本的UI 组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View 的基础之上又添加了各自特有的功能。而ViewGroup 则是一种特殊的View,它可以包含很多的子View 和子ViewGroup,是一个用于放置控件和布局的容器
这里写图片描述

看到这里,是不是在担心如果系统自带的控件并不能满足我们的需求时,可不可以利用上面的继承结构来创建自定义控件呢?没有做不到,只有想不到,作为站在计算机科技前沿的程序猿,怎么会说No呢?

接下来,就来尝试一下创建自定义控件的两种简单方法。先将准备工作做好,创建一个UICustomViews 项目。

一.引入布局
如果你用过iPhone 应该会知道,几乎每一个iPhone 应用的界面顶部都会有一个标题栏,标题栏上会有一到两个按钮可用于返回或其他操作(iPhone 没有实体返回键,这就是小编我不喜欢用苹果的原因-_-#)。现在很多的Android 程序也都喜欢模仿iPhone 的风格,在界面的顶部放置一个标题栏。虽然Android 系统已经给每个活动提供了标题栏功能,但这里我们仍然决定不使用它,而是创建一个自定义的标题栏。
经过前面两节的学习,我想创建一个标题栏布局对你来说已经不是什么困难的事情了,只需要加入两个Button 和一个TextView,然后在布局中摆放好就可以了。可是这样做却存在着一个问题,一般我们的程序中可能有很多个活动都需要这样的标题栏,如果在每个活动的布局中都编写一遍同样的标题栏代码,明显就会导致代码的大量重复。
duang、duang、duang!这个时候我们就可以使用引入布局的方式来解决这个问题,新建一个布局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"    android:background="@drawable/title_bg" >    <Button        android:id="@+id/title_back"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:layout_margin="5dip"        android:background="@drawable/back_bg"        android:text="Back"        android:textColor="#fff" />    <TextView        android:id="@+id/title_text"        android:layout_width="0dip"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:layout_weight="1"        android:gravity="center"        android:text="This is Title"        android:textColor="#fff"        android:textSize="22sp" />    <Button        android:id="@+id/title_edit"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:layout_margin="5dip"        android:background="@drawable/edit_bg"        android:text="Edit"        android:textColor="#fff" /></LinearLayout>

可以看到,我们在LinearLayout 中分别加入了两个Button 和一个TextView,左边的Button可用于返回,右边的Button 可用于编辑,中间的TextView 则可以显示一段标题文本。android:background 用于为布局或控件指定一个背景,可以使用颜色或图片来进行填充,这里我提前准备好了三张图片,title_bg.png、back_bg.png 和edit_bg.png,分别用于作为标题栏、返回按钮和编辑按钮的背景。另外在两个Button 中我们都使用了android:layout_margin 这个属性,它可以指定控件在上下左右方向上偏移的距离,当然也可以使用android:layout_marginLeft或android:layout_marginTop 等属性来单独指定控件在某个方向上偏移的距离。
现在标题栏布局已经编写完成了,剩下的就是如何在程序中使用这个标题栏了,修改activity_main.xml 中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <include layout="@layout/title" /></LinearLayout>

没错!我们只需要通过一行include 语句将标题栏布局引入进来就可以了。最后别忘了在MainActivity 中将系统自带的标题栏隐藏掉,代码如下所示

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        requestWindowFeature(Window.FEATURE_NO_TITLE);        setContentView(R.layout.activity_main);    }}

使用这种方式,不管有多少布局需要添加标题栏,只需一行include 语句就可以了。

运行效果:这里写图片描述


有木有感觉上面的技能有点炫?咳咳,不过,我们是不是还忘记了点什么?Bingo,貌似上面的东东只能看,不能点,TʌT。接下来,我们就去解决这个问题吧!

二.创建自定义控件
引入布局的技巧确实解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每个活动中为这些控件单独编写一次事件注册的代码。比如说标题栏中的返回按钮,其实不管是在哪一个活动中,这个按钮的功能都是相同的,即销毁掉当前活动。而如果在每一个活动中都需要重新注册一遍返回按钮的点击事件,无疑又是增加了很多重复代码,这种情况最好是使用自定义控件的方式来解决。

新建TitleLayout 继承自LinearLayout,让它成为我们自定义的标题栏控件,代码如下所示:
这里不单单可以继承LinearLayout布局,还可以继承RelativeLayout布局,程序依旧可以运行,说明inflate()的第二个参数this指的是TitleLayout类的实例自身,而不是title.xml中的根布局Linearayout

public class TitleLayout extends LinearLayout {    public TitleLayout(Context context, AttributeSet attrs) {        super(context, attrs);        LayoutInflater.from(context).inflate(R.layout.title, this);         //在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于 findViewById(),        //不同点是LayoutInflater是用来找layout下xml“布局文件”,并且实例化!而findViewById()是找具体xml下的具体 widget控件.        //from():从给定的context获得LayoutInflater        //inflate方法的主要作用就是将xml转换成一个View对象,用于动态的创建布局        //inflate()与setContentView()的区别:        //setContentView()一旦调用, layout就会立刻显示UI;而inflate只会把Layout形成一个以view类实现成的对象,有需要时再用setContentView(view)显示出来。        //一般在activity中通过setContentView()将界面显示出来,但是如果在非activity中如何对控件布局设置操作了,这就需要LayoutInflater动态加载。        /*public View inflate(int Resourece,ViewGroup root)        作用:填充一个新的视图层次结构从指定的XML资源文件中        reSource:View的layout的ID        root: 生成的层次结构的根视图        return 填充的层次结构的根视图。如果参数root提供了,那么root就是根视图;否则填充的XML文件的根(在这里即title.xml中的LinearLayout)就是根视图。*/    }}

首先我们重写了LinearLayout 中的带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数。然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater 来实现了。通过LayoutInflater 的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法接收两个参数,第一个参数是要加载的布局文件的id,这里我们传入R.layout.title,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this

现在自定义控件已经创建好了,然后我们需要在布局文件中添加这个自定义控件,修改activity_main.xml 中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <com.example.uicustomviews.TitleLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        ></com.example.uicustomviews.TitleLayout></LinearLayout>

添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候我们需要指明控件的完整类名,包名在这里是不可以省略的。注意!注意!注意!重要的事情要说三遍!
重新运行程序,你会发现此时效果和使用引入布局方式的效果是一样的。

然后我们来尝试为标题栏中的按钮注册点击事件,修改TitleLayout 中的代码,如下所示:

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() {            @Override            public void onClick(View v) {                ((Activity) getContext()).finish();                //getContext():返回正在运行的上下文,在这里即正在返回栈栈顶的活动并且要包括了该布局            }        });        titleEdit.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                Toast.makeText(getContext(), "You clicked Edit button", Toast.LENGTH_SHORT).show();            }        });    }}

首先还是通过findViewById()方法得到按钮的实例,然后分别调用setOnClickListener()方法给两个按钮注册了点击事件,当点击返回按钮时销毁掉当前的活动,当点击编辑按钮时弹出一段文本。重新运行程序,点击一下编辑按钮,效果如图所示:
这里写图片描述

这样的话,每当我们在一个布局中引入TitleLayout,返回按钮和编辑按钮的点击事件就已经自动实现好了,也是省去了很多编写重复代码的工作。

最后在啰嗦一些,不知道大家有没有想过构造函数TitleLayout(Context context, AttributeSet attrs)中的contex为什么会有值?(因为在上面的代码中我们用到了contxet)我大概的了解了一下,因为最近事情比较多,我就没有详细的去深究了,在这里分享一下,可能会有不妥,欢迎指正:
因为直接在布局中用的这种自定义view会调用你上面写的那种构造方法,也就是从布局里面直接调用的,所以context自然会有值,总布局是和代码关联的,总布局里面的对象都是有应用上下文的。

0 0