用户界面开发基础
Activity是Adnroid中唯一可視化的應用程序組件。
代碼托管 Github
Activity的使用方法
Activity是Android中最核心的應用程序組件,也是大多數程序必須使用的用于顯示界面的組件。
創建Activity
實際上一個類只要繼承Activity類就可以當成一個Activity來使用了,只是沒有任何控件,只有屏幕頂端默認的標題欄。
想要在Activity中添加控件,最直接的方法就是在onCreate中裝載xml布局文件或者使用Java代碼添加控件。
如果要重寫onCreate方法,必須要調用Activity類的onCreate()方法,也就是super.onCreate(savedInstanceState)
,否則顯示Activity時會拋出異常。
這是因為Activity類中沒有不帶參數的onCreate().如果不顯示的調用super.onCreate(savedInstanceState),系統會試圖調用super.onCreate()方法,然而在Activity類中并沒有此方法。
配置Activity
在AndroidManifest.xml中配置Activity。
<activity android:name=".MainActivity_"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>每一個Activity都會對應AndroidManifest.xml問價中的一個<activity>標簽。
必選屬性:android:name ,用于指定Activity類名。
指定android:name屬性值有三種方式:
package=”com.turing.base” >
其他一些常用的屬性
android:label
AndroidMainfest.xml中在application和activity中都可以設置android:lable。
android:label用來設置應用名和標題名。
應用名:
當主activity和application中都設置此值后,應用名會優先使用主activity中的值。
標題名:
當application和activity中都設置android:label時,標題名會優先使用各個activity中的值。當存在activity中沒有設置值時,會使用application中的值。
android:icon
android:icon 必須指定一個圖像資源ID,用來作為應用程序列表中的程序圖標。 如果沒有在activity標簽中指定,系統這會使用application標簽中的android:icon屬性值來代替。
intent-filter
intent-filter標簽的作用就是對Activity進行分類,
intent-filter標簽內部的
action標簽表示Activity可以接收的動作
category表示Activity所屬的種類
實際上,action和category標簽中的android:name屬性值只是一個普通的字符串。 在Android系統中預定了一些表中的動作和種類。
例如:
android.intent.action.Main 和android.intent.action.category.LAUNCHER
其中android.intent.action.Main需要定義在Main Activity類的activity標簽中。
當Android系統運行時,會首先啟動包含android.intent.action.Main的Activity。 作為MainActivity必須使用android.intent.category.LAUNCHER 作為其類別,表示該Activity顯示在最頂層。
顯示其他的Activity(Intent與Activity)
想要創建和顯示Activity,必須使用android.content.Intent作為中間的代理,并使用startActivity或startActivityForResult方法創建并顯示Activity。
顯示調用Intent
Intent intent = new Intent(this,MyActivity.class); startActivity(intent);第一個參數指定了Context類型的對象,第二個參數指定需要顯示的Activity類的class對象。
如果不想再Intent類的構造方法中指定這兩個參數,也可以通過Intent類的setClass方法來指定,代碼如下:
Intent intent = new Intent(); intent.setClass(this,MyActivity.class); startActivity(intent);隱式調用Intent
隱式調用仍然需要使用Intent,但是并不需要指定要調用的Activity,而只是要指定一個Action和相應的Category即可。 action和category這兩個標簽,不光是提供Android系統使用,我們也可以將他們應用到自定義的Activity中。
如果是自定義的種類(category),category標簽的屬性值至少要有一個android.intent.category.DEFAULT.
action標簽的android:name屬性,可以是任意字符串,但建議使用有意義的字符串,并要在程序中通過常量來引用。
一個intent-filter標簽可以包含多個action和category標簽。
一個Activity中可以包含多個intent-filter標簽。
當intent-filter標簽中,只有一個值為android.intent.action.category.DEFAULT的category時,并不需要在Intent對象中指定這個category。如果包含了其他的category,必須要使用intent.addCategory方法添加相應的category.
實例如下:
<!-- 該Activity未設置任何intent-filter,用顯式的方式調用這個Activity --><activity android:name=".activity.intentAct.XianSiDiaoyongAct"android:label="XianSiDiaoyongAct" /><!-- 隱式調用Activity --><activity android:name=".activity.intentAct.YinSiDiaoyngAct"android:icon="@drawable/flag_mark_red"android:label="YinSiDiaoyngAct"><intent-filter><action android:name="myAction1" /><category android:name="android.intent.category.DEFAULT" /></intent-filter><!--這個intent-filter和YinSiSelectAct的intent的相同--><intent-filter><action android:name="myAction2" /><category android:name="android.intent.category.DEFAULT" /><category android:name="mycategory"/></intent-filter></activity><activity android:name=".activity.intentAct.YinSiSelectAct"android:icon="@drawable/flag_mark_yellow"android:label="YinSiSelectAct"><!--這個intent-filter和YinSiDiaoyngAct的第二個intent-filter相同,使用這個intent-filter,屏幕會彈出一個列表,供用戶選擇--><intent-filter><action android:name="myAction2" /><category android:name="android.intent.category.DEFAULT" /><category android:name="mycategory"/></intent-filter></activity> switch (position){case 0: // 顯示調用ActivityToast.makeText(UI_Base.this,String.valueOf(position),Toast.LENGTH_SHORT).show();// 第一種方式Intent intent = new Intent(UI_Base.this, XianSiDiaoyongAct.class);startActivity(intent);// 第二種方式// Intent intent1 = new Intent();// intent1.setClass(UI_Base.this,XianSiDiaoyongAct.class);// startActivity(intent1);break;case 1:// 隱式調用ActivityIntent intent2 = new Intent("myAction1");startActivity(intent2);break;case 2: // 隱式調用兩個符合過濾條件的ActivityIntent intent3 = new Intent("myAction2");intent3.addCategory("mycategory");startActivity(intent3);break;如果在intent-filter標簽中使用了默認的category(android.intent.category.DEFAULT),在隱式調用中并不需要在Intent對象中使用addCategory方法指定。 如果非要指定,使用定義在Intent類中的常量 Intent.CATEGORY_DEFAULT.
intent.addCategory(Intent.CATEGORY_DEFAULT);代碼說明:
第一個顯示調用,會根據指定的class來動態創建Activity對象實例。
第二個隱式調用,系統會查找包含myaction1的Activity,如果找到,顯示。否則拋出異常。
第三個隱式調用符合過濾條件的Activity,由于有兩個Activity都包含了名為myaction2的動作,并且都屬于名為mycategory的種類,系統會彈出選擇界面,用戶可以選擇其中一個運行,如果勾選了”Use by default for this action”復選框,下次運行會直接運行上次選擇的Activity。
Activity的生命周期
整體描述
從Activity創建到銷毀的過程中需要在不同的階段調用7個生命周期方法。
procted void onCreate(Bundle savedInstanceState) procted void onStart() procted void onResume() procted void onPause() procted void onStop() procted void onRestart() procted void onDestory()上述7個生命周期方法分別在4個階段按照一定的順序進行調用,4個階段分別如下
onCreate—onStart—onResume
onPause—onStop
onRestart—onStart—onResume
onStop—onDestory
如果在這4個階段執行生命周期方法的過程中不發生狀態的改變,系統會按照上面的藐視依次執行這4個階段的生命周期方法,但是如果執行過程中改變了狀態,系統會按更加復雜的方法調用生命周期方法。
從上圖中我們可以看出,
在執行的過程中可以改變系統的執行軌跡的生命周期方法是onPause和onStop。
如果在onPause的過程中Activity重新獲得了焦點,然后又失去了焦點,系統將不會執行onStop方法,而是按照如下順序執行相應的生命周期方法:
onPause—onResume—onPause
如果在執行onStop方法的過程中Activity重新獲得了焦點,然后又失去了焦點,系統將不會執行onDestory方法,而是按照如下的順序執行相應的生命周期方法:
onStop—onRestart—onStart—onResume—onPause—onStop
從官方給出的Activity生命周期圖中不難看出,這個圖中包含兩層循環,第一層是:onPause—onResume—onPause。
第二層是onStop—onRestart—onStart—onResume—onPause—onStop.
第一層稱為焦點生命周期
第二層稱為可視生命周期
也就是說第一層循環在Activity焦點的獲得與失去中循環,這一過程中Activity始終可見。
第二層循環在Activity可見與不可見的過程中循環,在這個過程中伴隨著Activity焦點的獲得與失去。也就是說Activity首先被顯示,然后會獲得焦點,接著失去焦點,最后由于彈出其他的Activity,使當前的Activity變得不可見。
因此Activity有如下三種生命周期:
- 整體生命周期:onCreate……..onDestory
- 可視生命周期:onStart…….onStop
- 焦點生命周期:onResume—onPause
演示
public class LifeCircleActivity extends Activity {private static final String TAG = LifeCircleActivity.class.getSimpleName();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_life_circle);Log.e(TAG, "onCreate");}@Overrideprotected void onStart() {super.onStart();Log.e(TAG, "onStart");}@Overrideprotected void onResume() {super.onResume();Log.e(TAG, "onResume");}@Overrideprotected void onPause() {super.onPause();Log.e(TAG, "onPause");}@Overrideprotected void onStop() {super.onStop();Log.e(TAG, "onStop");}@Overrideprotected void onRestart() {super.onRestart();Log.e(TAG, "onRestart");}@Overrideprotected void onDestroy() {super.onDestroy();Log.e(TAG, "onDestroy");} }啟動應用程序: onCreate—onStart—onResume
按home鍵:(失去焦點) onPause—onStop
按home鍵后重新進入:(重新獲得焦點)onRestart—onStart—onResume
按返回鍵:(退出) onPause–onStop—onDestory
在不同Activity之間傳遞數據
Activity之間切換時,不可避免的要進行數據傳遞,例如在單擊列表中的某個列表項時,小需要編輯與這個列表項相關的數據,這個時候就需要在顯示一個Activity,然后將原始數據傳遞個這個Activity,這就是一個典型的數據傳遞的過程。
在Android中傳遞數據的方法很多,介紹4中比較常用的數據傳遞方法
- 通過Intent傳遞數據
- 通過靜態(static)變量傳遞數據
- 通過剪切板(Clipboard)傳遞數據
- 通過全局變量傳遞數據
使用Intent傳遞數據
這是最常用的一種數據傳遞方法。
通過Intent類的putExtra方法可以將簡單類型的數據或者可序列化的對象保存在Intent對象中,然后在目標Activity中使用getXXX(getInt,getString。。。。)方法獲得這些數據。
關鍵代碼如下:
Intent intent5 = new Intent(UI_Base.this,GetIntentActivity.class);//簡單類型intent5.putExtra("intent_string","通過Intent傳遞的字符串");intent5.putExtra("intent_int", 20);// 可序列化的對象Data data = new Data();data.setId(99);data.setName("ZTE");intent5.putExtra("intent_object", data);startActivity(intent5); // 獲取StringString msg = getIntent().getStringExtra("intent_string");LogUtils.d("String:" + msg);// 獲取Intint value = getIntent().getExtras().getInt("intent_int");LogUtils.d("int:" + value);int vaule2 = getIntent().getIntExtra("intent_int" , 0);LogUtils.d("第二種獲取方式:" + vaule2 );// 獲取可序列化對象Data data = (Data) getIntent().getSerializableExtra("intent_object");LogUtils.d("Data: name" + data.getName() + ",id:" + data.getId());StringBuffer sb = new StringBuffer();sb.append("intent_string:" + msg);sb.append("\n");sb.append("intent_int:" + value);sb.append("\n");sb.append("intent_object: name-"+data.getName());sb.append("\n");sb.append("intent_object: id-"+data.getId());tv_getIntent.setText(sb.toString());Data implements Serializable
public class Data implements Serializable {public int id;public String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;} }Data類是可序列化的,也就是實現了java.io.Serializable接口。
使用靜態變量傳遞數據
雖然intent可以很方便的在Activity中間傳遞數據,這也是官方推薦的數據傳遞方式。
但是Intent也有其局限性,Intent無法傳遞不能序列化的對象,也即是沒有實現java.io.Serializable接口的類的對象。
比如Canvas對象就無法通過Intent對象傳遞,如果傳遞自定義類的對象,也必須實現java.io.Serializable接口才可以。 如果沒有源代碼,而且還沒有實現Serializable接口,使用Intent對象就無能為力了。
步驟:
1.startActivity之前,為靜態變量賦值
2.目標類定義靜態變量接收(也可以在其他類中定義)
使用剪切板傳遞變量(String類型和復雜對象)
在Activity之間傳遞對象還可以利用一些技巧。無論是windows 還是Linux,都會支持一種叫做剪切板的技術。
String類型
Intent intent7 = new Intent(UI_Base.this, ClipBoardTransActivity.class);ClipboardManager clipboardManager = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); // api 11的方法 @TargetApi(Build.VERSION_CODES.HONEYCOMB) clipboardManager.setText("通過Clipboard傳遞數據"); startActivity(intent7); public class ClipBoardTransActivity extends AppCompatActivity {@TargetApi(Build.VERSION_CODES.HONEYCOMB)@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_clip_board_trans);ClipboardManager clipboardManager = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);String msg = clipboardManager.getText().toString();TextView textView = (TextView) findViewById(R.id.id_tv_clipboard);textView.setText(msg);} }在上述的代碼中使用了getSystemService方法獲得了一個系統服務對象,也就是ClipboardManager對象,該對象用于管理系統剪切板,并使用ClipboardManager.setText方法向剪切板中保存了一個字符串,通過getText可以獲取。
但是ClipboardManager對象只支持向剪切板讀寫字符串,并不支持其他的類型,更別提復雜的對象了。
當然了,如果是其他類型的數據,比如int ,可以將起轉換成字符串。
復雜對象
如果是對象類型呢,比如之前的Data對象能否通過剪切板傳遞呢?答案是肯定的,只是需要通過Base64進行編碼解碼轉換。
由于Data是可序列化的對象,因此完全可以將Data抓換成byte[]類型的數據,然后將byte[]類型的數據再進行Base4編碼(通過Email發送附件就是將附件轉換成為Base64格式的字符串發送的)轉換成字符串。
代碼演示,通過剪切板傳遞Data對象。
解碼
public class ClipboardTransObjectDataAct extends AppCompatActivity {@TargetApi(Build.VERSION_CODES.HONEYCOMB)@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_clipboard_trans_object_data);ClipboardManager cbm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);// 從剪切板中獲取Base64編碼格式的字符串String base64Str = cbm.getText().toString();// 將Base64格式的字符串還原為byte[]格式的數據byte[] buffer = Base64.decode(base64Str,Base64.DEFAULT);ByteArrayInputStream bais = new ByteArrayInputStream(buffer);try {ObjectInputStream ois = new ObjectInputStream(bais);// 將byte[]數據還原為Data對象Data data = (Data)ois.readObject();// 輸出TextView tv = (TextView)findViewById(R.id.id_tv_clipboard_trans_object);tv.setText(base64Str + "\n\n data.id:" + data.getId() + "\ndata.name:"+data.getName());} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}} }說明:
Base64類是從Android2.2開始支持的,2.1及其以下版本無法通過Android SDK API 進行Base64編碼和解碼,因此需要借助第三方的類庫如common httpclient才可以。
使用全局對象傳遞變量
雖然使用靜態變量可以傳遞任意類型的數據,但是官方并不建議這樣做。如果在類中大量使用靜態變量(尤其是很占用資源的變量,如Bitmap對象)可能會造成內存溢出異常,而且可能因為靜態變量在很多類中出現而造成代碼難以維護和混亂。
我們可以使用一種更優雅的數據傳遞方式–全局對象。這種方式可以完全取代靜態變量。
步驟:
Manifest.xml指定全局類
<applicationandroid:name=".AppContext"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme"> AppContext context = (AppContext)getApplication();context.appName = "ANDROID BASE";context.data.setId(0000);context.data.setName("通過全局變量來傳遞數據");Intent intent9 = new Intent(UI_Base.this, ApplicationTransActivity.class);startActivity(intent9); public class ApplicationTransActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_application_trans);// 獲取全局變量AppContext context = (AppContext)getApplication();String name = context.appName;Data data = context.data;StringBuffer sb = new StringBuffer();sb.append("AppContext appname:" + name );sb.append("\n");sb.append("AppContext data.id:" + data.getId());sb.append("\n");sb.append("AppContext data.name:" + data.getName());TextView textView = (TextView) findViewById(R.id.id_tv_app_trans);textView.setText(sb.toString());} }全局對象所對應的類必須是android.app.Application的子類。
全局類中不需要定義靜態變量,只需要定義成員變量即可,
而且全局類中必須要有一個無參的構造方法,或者不編寫任何代碼的構造方法(系統會自動的建立一個無參數的構造方法)。
這個類和Activity一樣,由系統自動創建,因此,必須要有一個無參的構造方法。
只編寫一個全局類是不會自動創建全局對象的,因為Android系統并不知道哪個是全局類,因此需要在AndroidManifest.xml中的application標簽的android:name屬性來執行這個類。
指定全局類后,在程序運行后,全局對象會被自動創建,而且會一直在內存中駐留,直到應用程序徹底退出內存。
四種方式比較
雖然上述始終方法在某些情況下可以相互取代,但是根據具體情況使用不同的數據傳遞方法會使程序更加便于維護。
對于向其他Activity中傳遞簡單類型(int 、String、short、bool等)或者可序列化的對象時,建議使用Intent。
如果傳遞不可序列化的對象,可以采用靜態變量或者全局對象的方式,不過按照官方的建議,最好是采用全局對象的方式。
另外如果想要使某些數據長時間駐留內存,以便程序隨時的取用,最好采用全局對象的方式。當然如果數據不復雜,也可以采用靜態變量的方式
至于剪切板,如果不是特殊情況,并不建議使用,因為這可能會影響到其他的程序(其他程序也可能使用剪切板)
返回數據到前一個Activity
在應用程序中,不僅要向Activity傳遞數據,同時也要從Activity中返回數據,一般建議采用Intent這種方式來返回數據,需要使用startActivityForResult方法來顯示Activity。
代碼如下
其中startActivityForResult方法有2個參數,第二個參數是一個int類型的請求碼,可以是任意的整數,只是為了區分請求的來源,以便處理返回結果。
大致步驟如下:
啟動一個ForResult的意圖:
Intent intent = new Intent(MainAcitvity.this,RequestActivity.class);
//發送意圖標示為REQUSET=1
startActivityForResult(intent, REQUSET);
B Activity處理數據:
Intent intent=new Intent();
intent.putExtra(KEY_USER_ID, et01.getText().toString());
setResult(RESULT_OK, intent);
finish();
代碼演示如下:
A類
Intent intent10 = new Intent(UI_Base.this, StarActivityForResultAct.class);startActivityForResult(intent10, 1); // 請求碼1 一定要>=0 @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode){ // 請求碼1case 1:switch (resultCode){ // 響應碼case 2:Toast.makeText(UI_Base.this,data.getStringExtra("value"),Toast.LENGTH_SHORT).show();break;default:break;}break;default:break;}}B類
public class StarActivityForResultAct extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_star_activity_for_result);}/*** android:onClick="doSomethingThenReturn"* 對應xml中的屬性,記得方法里面的參數,否則報錯* @param view*/public void doSomethingThenReturn(View view){Intent intent = new Intent();intent.putExtra("value","返回給前個Act的值");// 通過intent對象返回結果,setResult的第一個參數是響應碼setResult(2, intent);// 關閉當前Activityfinish();} } <?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="match_parent"><Button android:id="@+id/id_btn_doSomethingThenReturn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="doSomethingThenReturn"android:text="doSomethingThenReturn"/></RelativeLayout>在Button標簽中有一個屬性android:click,可以直接指定按鈕單擊事件的方法名。這樣在Activity中就不用創建按鈕對象而處理按鈕單擊事件了。 如果在程序中只處理單擊事件,而不直接引用相應的對象,可以采用這種方法。
視圖(View)
在Android系統中任何可視化控件都必須從android.view.View類繼承。
兩種方式創建View對象:
視圖簡介
Android中視圖類可以分為三種
- 布局類(Layout)
- 視圖容器(View Container)
- 視圖類 (例如TextView…)
使用xml布局文件定義視圖
注意事項:
- XML布局文件的擴展名必須是xml
- XML布局文件名必須符合Java變量的命名規則(以為在R類中會自動生成一個變量),例如不能以數字開頭
- XML布局文件的根節點可以是任意的控件標簽,比如<LinearLayout> <TextView>
- XML布局文件的根節點必須包含android命名控件,且必須是http://schemas.android.com/apk/res/android
- 為XML布局文件中的標簽指定ID時需要使用這樣的格式:@+id/value ,其中@+ 語法標識如果ID在R.id類中不出在,這產生一個與ID同名的變量,如果存在,則直接使用。 value表示ID的值。
- 視圖ID的值也要符合java變量的命名規則
在獲得XML布局文件中的視圖對象需要注意如下幾點
- finddViewById需要在setContentView之后使用。
- findViewById只能獲得已經裝載的XML布局文件中的視圖對象。
- 在不同的XML布局文件中可以存在相同ID的視圖,但是在同一個XML文件中,雖然也可以有相同ID的視圖,但是通過ID獲取視圖時,只能夠獲取按照定義的順序的第一個視圖對象,其他相同ID值的視圖對象將無法回去,因此在同一個XML布局文件中盡量使視圖ID唯一。
在代碼中控制視圖
舉個例子:
TextView tv = (TextView)findViewById(R.id.textView1); tv.setText("UUUUU");還可以使用字符串資源對TextView進行文本修改
tv.setText(R.string.hello);注意:當seText方法的參數是int型時,會被認為這個參數值是一個字符串資源ID,因此,如果要將TextView的文本設置為一個整數,需要將這個整數抓換位String類型。例如 tv.setText(String.valueOf(200));將TextView的文本設置為200
在更高級的Android應用中,往往需要動態的添加視圖,要實現這個功能,最重要的是要獲得被添加的視圖所在的容器對象,這個容器對象所對應的類需要繼承ViewGroup類。
將其他的視圖添加到當前的容器視圖中的步驟如下:
場景:
假設有兩個xml布局文件:test1.xml test2.xml
這兩個xml的根節點都是<LinearLayout>, 目的獲取test2.xml中的LinearLayout對象,并將該對象作為test1.xml文件中的<LinearLayout>標簽的子節點添加到test1.xml的LinearLayout對象中。
第一種方式:
// 獲得test1.xml中的LinearLayout對象 LinearLayout l1 = (LinearLayout)getLayoutInflater().inflate(R.layout.test,null); // 將test1.xml中的LinearLayout對象設置為當前容器視圖 setContentView(l1); // 獲取test2.xml中的LinearLayout對象,并將該對象添加到test1.xml中的LinearLayout中 LinearLayout l2 = (LinearLayout)getInflater().inflater(R.layout.test2,l1);參數解釋: inflate()方法第一個參數標識XML布局資源文件的ID,
第二個參數標識獲得容器對象后,要將該對象添加到哪個視圖對象中。
如果不想添加到任何其他的容器中,設置為null即可。
第二種方式:
// 獲得test1.xml中的LinearLayout對象 LinearLayout l1 = (LinearLayout)getLayoutInflater().inflate(R.layout.test,null); // 將test1.xml中的LinearLayout對象設置為當前容器視圖 setContentView(l1); // 獲取test2.xml中的LinearLayout對象,并將該對象添加到test1.xml中的LinearLayout中 LinearLayout l2 = (LinearLayout)getInflater().inflater(R.layout.test2,null);l1.addView(l2);inflate方法第二個參數設置為null, 通過addView方法添加
第三種方式
完全使用Java代碼創建一個視圖對象,并將該對象添加到容器視圖中
TextView tv = new TextView(this); l1.addView(tv)注意事項:
- 如果使用setContentView方法將試圖容器設置為當前視圖后,還想要向試圖容器中添加新的視圖或者進行其他操作,setContentView方法的參數值應直接使用容器視圖對象,因為這樣可以向容器視圖對象中添加新的視圖。
- 一個視圖只能有一個父視圖。也就是說一個視圖只能被包含在一個容器視圖中。因此,在向容器視圖中添加其他視圖時,不能將XML布局文件中非根節點的視圖對象添加到其他的容器視圖中。
布局(Layout)
框架布局FrameLayout
最簡單的布局方式,FrameLayout 以層疊放方式顯示,第一個添加到框架布局中的視圖顯示在最底層,最后一個放在最頂層。
上一層視圖會覆蓋下一層視圖,類似于堆棧,因此也被稱為堆棧布局。
線性布局LinearLayout
最常用的布局方式。
線性布局可以分為水平線性布局和垂直先行布局。
android:orientation ,兩個字 horizontal 、vertical。 默認horizontal。
一個非常重要的屬性 gravity,用于控制布局中視圖的位置。
設置多個屬性,需要使用“|”分隔,在屬性值和“|”之間不能有其他符號(例如空格和制表符等)。
| top | 將視圖放到屏幕頂端 |
| bottom | 將視圖放到屏幕底端 |
| left | 將視圖放到屏幕左側 |
| right | 將視圖放到屏幕右側 |
| center_vertical | 將視圖按垂直方向居中顯示 |
| center_horizontal | 將視圖按水平方向居中顯示 |
| center | 將視圖按垂直和水平方向居中顯示 |
LinearLayout標簽中的子標簽還可以使用layout_gravity和layout_weight屬性來設置每一個視圖的位置
layout_gravity 屬性的取值和gravity的取值相同,表示當前視圖在布局中的位置。
layout_weight屬性是一個非負整數,如果該屬性值大于0,線性布局會根據水平或者垂直方向以及不同視圖的layout_weight屬性值占所有視圖的layout_weight屬性值之和的比例為這些視圖分配自己說占用的區域,視圖將按相應比例拉伸。
相對布局RelativeLayout
設置某一個視圖相對于其他視圖的位置。
表格布局TableLayout
一個表格布局由一個<TableLayout>標簽和若干個<TableRow>標簽組成
絕對布局AbsoluteLayout
android:layout_x 和android:layout_y設置橫縱坐標。
重用XML布局
布局重用
<inclued>??include標簽可以實現在一個layout中引用另一個layout的布局,這通常適合于界面布局復雜、不同界面有共用布局的APP中,比如一個APP的頂部布局、側邊欄布局、底部Tab欄布局、ListView和GridView每一項的布局等,將這些同一個APP中有多個界面用到的布局抽取出來再通過include標簽引用,既可以降低layout的復雜度,又可以做到布局重用(布局有改動時只需要修改一個地方就可以了)。
inclued標簽,首字母要小寫,只有layout屬性是必選的。
?include標簽的使用很簡單,只需要在布局文件中需要引用其它布局的地方,使用layout=”@layout/child_layout”就可以了:
<include layout="@layout/titlebar"/>如果要覆蓋布局的尺寸,必須同時覆蓋android:layout_weight和android:layout_height . 不能只覆蓋一個,否則無效
建議將給include標簽調用布局設置寬高、位置、ID等工作放在調用布局的根標簽中,這樣可以避免給include標簽設置屬性不當造成的各種問題(之前遇到過給include標簽設置android:id屬性后,程序實例化子布局中組件失敗的現象):
?應該這樣:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/bottomBarLayoutId"android:layout_width="match_parent"android:layout_height="61dp"android:orientation="horizontal"android:layout_alignParentBottom="true">。。。 </LinearLayout><include layout="@layout/include_voice_ctrl_bar_layout" />而不是這樣:<?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"android:orientation="horizontal">。。。 </LinearLayout><include android:id="@+id/bottomBarLayoutId"android:layout_width="match_parent"android:layout_height="61dp"android:layout_alignParentBottom="true"layout="@layout/include_voice_ctrl_bar_layout"/>優化XML布局
減少視圖層級
<merge />
<merge />標簽在UI的結構優化中起著非常重要的作用,它可以刪減多余的層級,優化UI。<merge />多用于替換FrameLayout或者當一個布局包含另一個時,<merge />標簽消除視圖層次結構中多余的視圖組。例如你的主布局文件是垂直布局,引入了一個垂直布局的include,這是如果include布局使用的LinearLayout就沒意義了,使用的話反而減慢你的UI表現。這時可以使用<merge />標簽優化
<merge xmlns:android="http://schemas.android.com/apk/res/android"><Button android:layout_width="fill_parent" android:layout_height="wrap_content"android:text="@string/add"/><Button android:layout_width="fill_parent" android:layout_height="wrap_content"android:text="@string/delete"/></merge>現在,當你添加該布局文件時(使用<include />標簽),系統忽略<merge />節點并且直接添加兩個Button。更多<merge />介紹可以參考《Android Layout Tricks #3: Optimize by merging》
需要時使用<ViewStub />
<ViewStub />標簽最大的優點是當你需要時才會加載,使用他并不會影響UI初始化時的性能。各種不常用的布局想進度條、顯示錯誤消息等可以使用<ViewStub />標簽,以減少內存使用量,加快渲染速度。<ViewStub />是一個不可見的,大小為0的View。<ViewStub />標簽使用如下:
<ViewStub android:id="@+id/stub_import"android:inflatedId="@+id/panel_import"android:layout="@layout/progress_overlay"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_gravity="bottom" />當你想加載布局時,可以使用下面其中一種方法:
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE); // or View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();當調用inflate()函數的時候,ViewStub被引用的資源替代,并且返回引用的view。 這樣程序可以直接得到引用的view而不用再次調用函數findViewById()來查找了。
注:ViewStub目前有個缺陷就是還不支持 <merge /> 標簽。
更多<ViewStub />標簽介紹可以參考《Android Layout Tricks #3: Optimize with stubs》
查看APK文件中的布局
AXMLPrinter2工具
總結
- 上一篇: Android程序设计基础
- 下一篇: Oracle查询锁表以及杀会话或系统进程