本文通過開發一個應用來學習Android基本概念及構成應用的UI組件。 開發的應用名叫GeoQuiz,它能給出一道道地理知識問題。用戶點擊true或false按鈕回答問題,應用即時做出反饋 第一步請先自行創建一個新項目,目錄如下 1. 用戶界面設計 在XML文件(activity_quiz.xml) ...
本文通過開發一個應用來學習Android基本概念及構成應用的UI組件。
開發的應用名叫GeoQuiz,它能給出一道道地理知識問題。用戶點擊true或false按鈕回答問題,應用即時做出反饋
第一步請先自行創建一個新項目,目錄如下
1. 用戶界面設計
- 在XML文件(activity_quiz.xml)中定義組件
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/question_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/true_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/true_button"/>
<Button
android:id="@+id/false_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/false_button"/>
</LinearLayout>
<Button
android:id="@+id/cheat_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cheat_button"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/pre_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pre_button"
android:drawableLeft="@drawable/arrow_left"/>
<ImageButton
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/arrow_right"
android:contentDescription="@string/next_button"/>
</LinearLayout>
</LinearLayout>
activity_quiz.xml 效果圖
- 創建字元串資源
最好不要硬編碼設置組件的文本信息,如:android:text="True"。較好的做法是將文字內容放置在獨立的字元串資源XML文件中,然後引用它們,如:android:text="@string/true_button"。
找到app/res/values目錄,打開string.xml文件
添加字元串資源
<resources>
<string name="app_name">GeoQuiz</string>
<string name="true_button">True</string>
<string name="false_button">False</string>
<string name="pre_button">Pre</string>
<string name="next_button">Next</string>
<string name="correct_toast">Correct!</string>
<string name="incorrect_toast">Incorrect!</string>
<string name="question_oceans">The Pacific Ocean is larger than
the Atlantic Ocean.</string>
<string name="question_mideast">The Suez Canal connects the Red Sea
and the Indian Ocean.</string>
<string name="question_africa">The source of the Nile River is in Egypt.</string>
<string name="question_americas">The Amazon River is the longest river in the Americas.</string>
<string name="question_asia">Lake Baikal is the world\'s oldest and deepest
freshwater lake.</string>
<string name="warning_text">Are you sure you want to do this?</string>
<string name="show_answer_button">SHOW ANSWER</string>
<string name="cheat_button">CHEAT!</string>
<string name="judgment_toast">Cheating is wrong.</string>
<string name="fist_page">This is the first page!</string>
</resources>
2. 從佈局XML到視圖對象
- activity子類的實例創建後,onCreate(Bundle)方法會被調用,同時需要獲取並管理用戶界面,可再調用setContentView(int layoutResID),根據傳入的佈局資源ID參數,生成指定佈局視圖並將其放在屏幕上,佈局文件包含的組件也隨之以各自的屬性定義完成實例化。
public class QuizActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
}
}
- 資源和資源ID
- 使用資源ID在代碼中獲取相應的資源。activity_quiz.xml佈局的資源ID為R.layout.activity_quiz。
- 應用當前所有的資源放置在R.java文件中。切換至Project視圖,展開目錄app/build/generated/source/r.debug即可看到。R.java文件在Android項目編譯過程中自動生成,修改佈局或字元串等資源後,需再次運行應用,才會得到更新。
- 為需要的組件添加資源ID。如:android:id="@+id/idName"。
- 組件的應用
private Button mTrueButton;//在activity_quiz.java添加成員變數
@Override
protected void onCreate(Bundle savedInstanceState) {
......
mTrueButton=(Button)findViewById(R.id.true_button);//引用組件
mTrueButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
//設置監聽事件
}
});
}
3. 創建提示消息
Android的toast是用來通知用戶的簡短彈出消息。調用Toast類的以下方法可創建toast:
public static Toast makeText(Context context,int resId,int durattion)
- Context參數通常是Activity的一個實例(Activity本身就是Context的子類)。
- 第二個參數是toast要顯示字元串消息的資源ID。
- 第三個參數用來指定toast消息的停留時間。通常是Toast常量中的一個。
//舉個例子來說
Toast.makeText(QuizActivity.this,R.string.incorrect_toast,Toast.LENGTH_SHORT).show();
4. Android與MVC設計模式
- 應用對象按模型、控制器和視圖的類別分為三部分。Android應用基於模型-
控制器-視圖(Model-View-Controller,MVC)的架構模式進行設計。MVC設計模式表明,應用的任何對象,歸根結底都屬於模型對象、視圖對象以及控制對象中的一種。
- 我們使用 QuizActivity 創建 Question 數組對象。繼而通過與 TextView 以及三個 Button 的交互,在屏幕上顯示地理知識問題,並根據用戶的回答作出反饋,如圖2-4所示。
- 模型層 Question類代碼
public class Question {
private int mTextResId;//保存地理知識問題字元串的資源ID。資源ID總是int類型
private boolean mAnswerTrue;//問題答案
public Question(int textResId,boolean answerTrue){
mTextResId=textResId;
mAnswerTrue=answerTrue;
}
public int getTextResId() {
return mTextResId;
}
public void setTextResId(int textResId) {
mTextResId = textResId;
}
public boolean isAnswerTrue() {
return mAnswerTrue;
}
public void setAnswerTrue(boolean answerTrue) {
mAnswerTrue = answerTrue;
}
}
對於有首碼m的成員變數生成getter與setter方法
首先,配置Android Studio識別成員變數的 m 首碼。
打開Android Studio首選項對話框(Mac用戶選擇Android Studio菜單,Windows用戶選擇File →
Settings菜單)。分別展開Editor和Code Style選項,在Java選項下選擇CodeGeneration選項頁。在Naming表單中,選擇Fields行,添加m作為fields的首碼。若要添加靜態變數首碼s,則添加 s 作為Static Fields的首碼。如下圖。
- 控制器層QuizActivity.java
public class QuizActivity extends AppCompatActivity {
private ImageButton mNextButton;
private TextView mQuestionTextView;
private Question[] mQuestionBank=new Question[]{
new Question(R.string.question_oceans,true),
new Question(R.string.question_mideast,false),
new Question(R.string.question_africa,false),
new Question(R.string.question_americas,true),
new Question(R.string.question_asia,true)
};
private int mCurrentIndex=0;
}
5. 添加圖片資源
1.將圖片添加到drawable對應目錄中,尾碼名為.png、.jpg、.gif的文件都會自動獲得資源ID
- mdpi:中等像素密度屏幕(約160dpi)
- hdpi:高等像素密度屏幕(約240dpi)
- xhdpi:超高像素密度屏幕(約320dpi)
- xxdpi:超超高像素密度屏幕(約480dpi)
2. 在XML文件中引用資源
- 為next按鈕增加圖片(activity_quiz.xml)
<Button android:id="@+id/next_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/next_button" android:drawableLeft="@drawable/arrow_righ android:drawablePadding="4dp"/>
*以@drawable/開頭的定義是引用drawable資源*
ImageButton組件繼承自ImageView。Button組件則繼承自TextView。ImageView和TextView繼承自View
也可以ImageButton組件替換Button組件。刪除next按鈕的text以及drawable屬性定義,並添加ImageView屬性。
<ImageButton android:id="@+id/next_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/arrow_right" android:contentDescription="@string/next_button"/>
6. activity的生命周期
設備旋轉時,系統會銷毀當前QuizActivity實例,然後創建一個新的QuizActivity實例。所以每次旋轉設備用戶每次都會從第一題開始,現在來修正這個缺陷。
- 創建水平模式佈局
右鍵單擊res目錄選擇New->Android resource directory。資源類型選擇layout,保持Source set的main選項不變,選擇待選資源列表中的Orientation,然後單擊>>按鈕將其移動到已選資源特征區域。
最後,確認選中Screen orientation下拉列表中的Landscape選項,並確保目錄名顯示為layout-land
這裡的-land尾碼名是配置修飾符的另一個使用例子。Android依靠res子目錄的配置修飾符定位最佳資源以匹配當前設備配置。設備處於水平方向時,Android會找到並使用res/layout-land目錄下的佈局資源。其它情況下,它會預設使用res/layout目錄下的佈局資源。
- 將res/layout目錄下的activity_quiz.xml文件複製到res/layout-land目錄。
註意:兩個佈局文件的文件名必須相同,這樣它們才能以同一個資源ID被引用
- 水平模式佈局修改(layout-land/activity_quiz.xml)
FrameLayout替換了最上層的LinearLayout。FrameLayout是最簡單的ViewGroup組件,它一概不管如何安排其子視圖的位置。FrameLayout子視圖的位置排列取決於它們各自的android:layout_gravity屬性。
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/question_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="24dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/true_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/true_button"/>
<Button
android:id="@+id/false_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/false_button"/>
</LinearLayout>
<Button
android:id="@+id/cheat_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:text="@string/cheat_button"/>
<Button
android:id="@+id/pre_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:text="@string/pre_button"
android:drawableLeft="@drawable/arrow_left"
android:drawablePadding="4dp"/>
<ImageButton
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:src="@drawable/arrow_right"
android:contentDescription="@string/next_button"/>
</FrameLayout>
- 保存數據以應對設備旋轉
覆蓋以下Activity方法
protected void onSaveInstanceState(Bundle outState)
- 該方法通常在onStop()方法之前由系統調用,除非用戶按後退鍵。
- 該方法的預設實現要求所有activity視圖將自身數據狀態保存在Bundle對象中。Bundle是存儲字元串鍵與限定類型值之間映射關係(鍵值對)的一種結構。
public class QuizActivity extends AppCompatActivity {
......
private int mCurrentIndex=0;
private static final String KEY_INDEX="index";
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt(KEY_INDEX,mCurrentIndex);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
if(savedInstanceState!=null){
mCurrentIndex=savedInstanceState.getInt(KEY_INDEX,0);
}
......
}
......
}
7. 日誌
public static int d(String tag,String msg)//輸出日誌信息d:debug
方法的第一個參數通常是以類名為值的TAG常量傳入
public class QuizActivity extends AppCompatActivity {
private static final String TAG="QuizActivity";
......
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG,"onCreate(Bundle) called");
setContentView(R.layout.activity_quiz);
......
}
}
日誌級別 | 方法 | 說明 |
---|---|---|
ERROR | Log.e(...) | 錯誤 |
WARNING | Log.w(...) | 警告 |
INFO | Log.i(...) | 信息型消息 |
DEBUG | Log.w(...) | 調試輸出 |
VERBOSE | Log.v(...) | 僅用於開發 |
所有的日誌記錄方法都有兩種參數簽名:string類型的tag參數和msg參數;除tag和msg參數外再加上Throwable實例參數
9. 第二個activity
新activity將帶來第二個用戶界面,方便用戶偷看問題的答案
第二個activity的佈局組件的定義(activity_cheat.xml)
<LinearLayout
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"
android:gravity="center"
android:orientation="vertical"
tools:context="com.example.mdx.studyworks.CheatActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="@string/warning_text"/>
<TextView
android:id="@+id/answer_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
tools:text="Answer"/>
<Button
android:id="@+id/show_answer_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show_answer_button"/>
</LinearLayout>
activity_cheat.xml 效果圖
- 註意用於顯示答案的TextView組件,它的tools和tools:text屬性的命名空間比較特別。該命名空間可以覆蓋某個組件的任何屬性。這樣,可在預覽中看到效果,而在運行時Answer文字不會顯現出來。
- 應用的所有activity都必須在manifest配置文件中聲明,這樣操作系統才能找到它們。
//在manifest配置文件中聲明CheatActivity
<activity android:name=".CheatActivity">
</activity>
啟動activity
public void startActivity(Intent intent)
activity調用startActivity(Intent)方法時,調用請求實際發給了操作系統的ActivityManager。ActivityManager負責創建Activity實例並調用其onCreate(Bundle)方法
public Intent(Context pageContext,Class<?> cls)
傳入該方法的Class類型參數告訴ActivityManager應該啟動哪個activity
Context參數告訴ActivityManager在哪裡可以找到它
mCheatButton=(Button)findViewById(R.id.cheat_button);
mCheatButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//從QuizActivity啟動CheatActivity
Intent i=new Intent(QuizActivity.this,CheatActivity.class);
startActivity(i);
}
});
activity間數據傳遞
- 使用 intent extra
將extra數據信息添加給intent,調用Intent.putExtra(...)方法
public Intent putExtra(String name,boolean value)
Intent i=new Intent(QuizActivity.this,CheatActivity.class);
i.putExtra(EXTRA_ANSWER_IS_TRUE,answerIsTrue);
startActivity(i)
從extra獲取數據
public boolean getBooleanExtra(String name,boolean defaultValue)
mAnswerIsTrue=getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE,false);
Activity.getIntent()方法返回了由startActivity(Intent)方法轉發的Intent對象
- 從子activity獲取返回結果
//父activity
/*第二個參數是請求碼,*/
public void startActivityForResult(Intent intent,int requestCode)
//子activity發送返回信息給父activity,有2種方法
public final void setResult(int resultCode)
public final void setResult(int resultCode,Intent data)
resultCode可以是以下任意一個預定義常量
- Activity.RESULT_OK ,即1
- Activity.RESULT_CANCELED ,即0
如需自定義結果代碼,還可使用另一個常量:RESULT_FIRST_USER
//父,QuizActivity
private static final int REQUEST_CODE_CHEAT=0;
mCheatButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean answerIsTrue=mQuestionBank[mCurrentIndex].isAnswerTrue();
Intent i=new Intent(QuizActivity.this,CheatActivity.class);
i.putExtra(EXTRA_ANSWER_IS_TRUE,answerIsTrue);
startActivityForResult(i,REQUEST_CODE_CHEAT);
}
});
//處理返回結果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//super.onActivityResult(requestCode, resultCode, data);
//結果碼不一致
if (resultCode!= Activity.RESULT_OK){
return;
}
//結果碼一致
if (requestCode==REQUEST_CODE_CHEAT){
if (data==null){
return;
}
//解析結果intent
mIsCheater=data.getBooleanExtra(EXTRA_ANSWER_SHOW,false);
}
}
//子,CheatActivity
Intent data=new Intent();
data.putExtra(EXTRA_ANSWER_SHOW,isAnswerShown);
setResult(RESULT_OK,data);//設置返回結果
10. activity的使用與管理
被指定為應用的第一個activity
<!--指定第一個activity是QuizActivity-->
<activity android:name=".QuizActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>