常用控件和布局的繼承結構,如下圖:
(待續。。。。)
所有的控件都是直接或間接繼承自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()方法給兩個按鈕注冊了點擊事件,當點擊返回按鈕時,銷毀當前的活動,當點擊編輯按鈕時彈出一段文本。
效果如下圖: