第六堂(3)Material Design – Shared Element与自定动画效果
最后更新于:2022-04-01 00:53:29
第六堂(2)Material Design – RecylerView
最后更新于:2022-04-01 00:53:27
第六堂(1)Material Design – Theme与Transition
最后更新于:2022-04-01 00:53:24
第六堂
最后更新于:2022-04-01 00:53:22
第五堂(3)设计小工具元件 – AppWidget
最后更新于:2022-04-01 00:53:20
第五堂(2)系统通知服务 – Notification
最后更新于:2022-04-01 00:53:18
第五堂(1)建立广播接收元件 – BroadcastReceiver
最后更新于:2022-04-01 00:53:15
第五堂
最后更新于:2022-04-01 00:53:13
第四堂(3)读取装置目前的位置 – Google Services Location
最后更新于:2022-04-01 00:53:11
目前的行動裝置大部份都有衛星定位的設備,在戶外適當的環境下,可以從衛星接收到精確度很高的位置資訊。在室內或遮閉物比較多的環境,Android系統也可以從網路或電信服務,讀取誤差比較大一些的位置資訊。應用程式可以儲存使用這些位置資訊,例如記錄與儲存目前的位置,在地圖元件中查詢與規劃路徑。
這一章說明最新的Google Services Location API,跟傳統的作法比較,這是一種比較省電與方便的技術,應用程式可以根據自己的需求,讀取需要的位置資訊。目前已經為記事應用程式完成地圖元件,現在為應用程式加入讀取與儲存目前位置資訊的功能,如果記事資料已經儲存位置資訊,可以在地圖檢視與管理:
[![AndroidTutorial5_04_03_01](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1bb919bf49.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1bb919bf49.png)
[![AndroidTutorial5_04_03_02](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1bb960e162.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1bb960e162.png)
開啟還沒有儲存位置資訊的記事資料,可以在地圖檢視與儲存目前的位置:
[![AndroidTutorial5_04_03_03](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1bb9af21e1.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1bb9af21e1.png)
[![AndroidTutorial5_04_03_04](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1bb9d494ed.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1bb9d494ed.png)
## 14-1 準備工作
依照下列的步驟,執行準備使用Google Services Location API的工作:
1. 啟動Android Studio並開啟MyAndroidTutorial應用程式。
2. 選擇Android Studio功能表「Tools -> Android -> SDK Manager」。
3. 在Android SDK Manager視窗,檢查「Extras -> Google Play services」是否已經安裝。如果還沒有安裝的話,勾選並執行安裝的工作:
4. 開啟「Gradle Scripts -> build.gradle(Module:app)」,參考下面的內容,檢查是否已經加入需要的設定:
~~~
dependencies {
...
compile 'com.google.android.gms:play-services:7.0.0'
}
~~~
5. 如果在上一步驟修改「build.gradle(Module: app)」檔案的內容,必須選擇「Sync Project」執行同步的工作。
6. 開啟「ManifestAndroid.xml」,參考下面的內容,檢查在application標籤下是否已經加入需要的設定:
~~~
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
~~~
7. 同樣在「ManifestAndroid.xml」,參考下面的內容,檢查在manifest標籤下是否已經加入需要的設定:
~~~
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
~~~
開啟「res/values/strings.xml」檔案,加入這一章需要的文字資源:
~~~
<string name="title_update_location">記事儲存的位置</string>
<string name="message_update_location">更新或清除儲存的位置資訊?</string>
<string name="update">更新</string>
<string name="clear">清除</string>
<string name="title_current_location">目前位置</string>
<string name="message_current_location">是否儲存目前位置?</string>
<string name="google_play_service_missing">裝置沒有安裝Google Play服務</string>
~~~
## 14-2 使用Google Services Location API
應用程式需要讀取位置資料,使用Google Services提供的Location API,是比較方便的作法。使用在「com.google.android.gms.common.api」套件下的「GoogleApiClient」,可以連線與使用Google Services提供的服務。使用在「com.google.android.gms.location」套件下的API,可以讀取裝置目前的位置資訊。
使用Google Services Location API讀取位置資訊,通常會採用整合在元件的作法,例如記事應用程式的地圖元件,讓它可以連線與讀取位置資訊。開啟「net.macdidi.myandroidtutorial」套件下的「MapsActivity」,加入下列需要的欄位變數:
~~~
// Google API用戶端物件
private GoogleApiClient googleApiClient;
// Location請求物件
private LocationRequest locationRequest;
// 記錄目前最新的位置
private Location currentLocation;
// 顯示目前與儲存位置的標記物件
private Marker currentMarker, itemMarker;
~~~
### 14-2-1 使用Google API用戶端
地圖元件需要連線到Google API用戶端,使用位置資訊的服務。開啟「MapsActivity」,參考下列的程式片段,讓地圖元件類別實作需要的介面:
~~~
public class MapsActivity extends FragmentActivity
implements ConnectionCallbacks,
OnConnectionFailedListener {
...
~~~
在元件加入介面需要實作的方法,後續會在方法中加入需要執行的工作:
~~~
// ConnectionCallbacks
@Override
public void onConnected(Bundle bundle) {
// 已經連線到Google Services
}
// ConnectionCallbacks
@Override
public void onConnectionSuspended(int i) {
// Google Services連線中斷
// int參數是連線中斷的代號
}
// OnConnectionFailedListener
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// Google Services連線失敗
// ConnectionResult參數是連線失敗的資訊
}
~~~
### 14-2-2 接收位置更新資訊
使用者需要為記事資料儲存位置的時候,需要在地圖顯示目前的位置讓使用者檢視與儲存,所以為地圖元件加入接收位置更新資訊的功能。開啟「MapsActivity」,參考下列的程式片段,讓地圖元件類別實作需要的介面:
~~~
public class MapsActivity extends FragmentActivity
implements ConnectionCallbacks,
OnConnectionFailedListener,
LocationListener {
...
~~~
在元件加入介面需要實作的方法,後續會在方法中加入需要執行的工作:
~~~
// LocationListener
@Override
public void onLocationChanged(Location location) {
// 位置改變
// Location參數是目前的位置
}
~~~
## 14-3 Google API用戶端連線與接收位置更新資訊
需要使用Google Services Location服務,需要建立好需要的Google API用戶端物件,在「MapsActivity」加入下列建立Google API用戶端物件的方法:
~~~
// 建立Google API用戶端物件
private synchronized void configGoogleApiClient() {
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
~~~
在「MapsActivity」的「onCreate」方法加入呼叫上列方法的敘述:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// 建立Google API用戶端物件
configGoogleApiClient();
}
~~~
應用程式啟動以後,就會建立好需要的Google API用戶端物件。在後續執行連線與運作的時候,應用程式會執行ConnectionCallbacks與OnConnectionFailedListener介面對應的方法。
應用程式需要接收最新的位置資訊,需要依照應用程式的需求,建立與啟動LocationRequest服務。在「MapsActivity」加入下列建立LocationRequest物件的方法:
~~~
// 建立Location請求物件
private void configLocationRequest() {
locationRequest = new LocationRequest();
// 設定讀取位置資訊的間隔時間為一秒(1000ms)
locationRequest.setInterval(1000);
// 設定讀取位置資訊最快的間隔時間為一秒(1000ms)
locationRequest.setFastestInterval(1000);
// 設定優先讀取高精確度的位置資訊(GPS)
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
~~~
在「MapsActivity」的「onCreate」方法加入呼叫上列方法的敘述:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// 建立Google API用戶端物件
configGoogleApiClient();
// 建立Location請求物件
configLocationRequest();
}
~~~
在「MapsActivity」的「onConnected」、「onConnectionFailed」與「onLocationChanged」方法,分別加入啟動位置更新服務與錯誤處理的敘述:
~~~
// ConnectionCallbacks
@Override
public void onConnected(Bundle bundle) {
// 已經連線到Google Services
// 啟動位置更新服務
// 位置資訊更新的時候,應用程式會自動呼叫LocationListener.onLocationChanged
LocationServices.FusedLocationApi.requestLocationUpdates(
googleApiClient, locationRequest, MapsActivity.this);
}
// OnConnectionFailedListener
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// Google Services連線失敗
// ConnectionResult參數是連線失敗的資訊
int errorCode = connectionResult.getErrorCode();
// 裝置沒有安裝Google Play服務
if (errorCode == ConnectionResult.SERVICE_MISSING) {
Toast.makeText(this, R.string.google_play_service_missing,
Toast.LENGTH_LONG).show();
}
}
// LocationListener
@Override
public void onLocationChanged(Location location) {
// 位置改變
// Location參數是目前的位置
currentLocation = location;
LatLng latLng = new LatLng(
location.getLatitude(), location.getLongitude());
// 設定目前位置的標記
if (currentMarker == null) {
currentMarker = mMap.addMarker(new MarkerOptions().position(latLng));
}
else {
currentMarker.setPosition(latLng);
}
// 移動地圖到目前的位置
moveMap(latLng);
}
~~~
Google API用戶端連線與接收位置更新資訊,是很耗用資源與電力的服務,所以會在元件的生命週期方法執行控制的工作。參考下列的程式片段,修改「MapsActivity」的「onResume」方法,還有加入「onPause」與「onStop」兩個方法:
@Override
protected void onResume() {
super.onResume();
setUpMapIfNeeded();
// 連線到Google API用戶端
if (!googleApiClient.isConnected() && currentMarker != null) {
googleApiClient.connect();
}
}
@Override
protected void onPause() {
super.onPause();
// 移除位置請求服務
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(
googleApiClient, this);
}
}
@Override
protected void onStop() {
super.onStop();
// 移除Google API用戶端連線
if (googleApiClient.isConnected()) {
googleApiClient.disconnect();
}
}
## 14-4 傳送、接收與儲存位置資訊
完成地圖元件基本的工作以後,現在為記事元件加入處理位置資訊的工作。開啟「ItemActivity」,在「clickFunction」方法修改啟動地圖元件的程式碼,傳送記事資料儲存的位置資訊給地圖元件:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
...
case R.id.set_location:
// 啟動地圖元件用的Intent物件
Intent intentMap = new Intent(this, MapsActivity.class);
// 設定儲存的座標
intentMap.putExtra("lat", item.getLatitude());
intentMap.putExtra("lng", item.getLongitude());
intentMap.putExtra("title", item.getTitle());
intentMap.putExtra("datetime", item.getLocaleDatetime());
// 啟動地圖元件
startActivityForResult(intentMap, START_LOCATION);
break;
...
}
}
~~~
同樣在「ItemActivity」,在「onActivityResult」方法加入接收位置資訊的程式碼:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
...
case START_LOCATION:
// 讀取與設定座標
double lat = data.getDoubleExtra("lat", 0.0);
double lng = data.getDoubleExtra("lng", 0.0);
item.setLatitude(lat);
item.setLongitude(lng);
break;
...
}
}
}
~~~
完成上面的工作以後,使用者在已經儲存位置資訊的記事資料開啟地圖元件,就會在地圖畫面上顯示儲存的位置。使用者在地圖選擇儲存位置後,也可以儲存在記事資料庫中。
## 14-5 地圖元件的操作功能
最後的工作是在地圖元件提供使用者操作的功能,包含檢視與儲存目前的位置,還有更新或清除記事資料已經儲存的位置。開啟「MapsActivity」,在「onCreate」方法加入需要的程式碼:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// 建立Google API用戶端物件
configGoogleApiClient();
// 建立Location請求物件
configLocationRequest();
// 讀取記事儲存的座標
Intent intent = getIntent();
double lat = intent.getDoubleExtra("lat", 0.0);
double lng = intent.getDoubleExtra("lng", 0.0);
// 如果記事已經儲存座標
if (lat != 0.0 && lng != 0.0) {
// 建立座標物件
LatLng itemPlace = new LatLng(lat, lng);
// 加入地圖標記
addMarker(itemPlace, intent.getStringExtra("title"),
intent.getStringExtra("datetime"));
// 移動地圖
moveMap(itemPlace);
}
else {
// 連線到Google API用戶端
if (!googleApiClient.isConnected()) {
googleApiClient.connect();
}
}
}
~~~
地圖元件需要提供使用者選擇標記與訊息框的操作功能,在「MapsActivity」加入下列的方法:
~~~
private void processController() {
// 對話框按鈕事件
final DialogInterface.OnClickListener listener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
// 更新位置資訊
case DialogInterface.BUTTON_POSITIVE:
// 連線到Google API用戶端
if (!googleApiClient.isConnected()) {
googleApiClient.connect();
}
break;
// 清除位置資訊
case DialogInterface.BUTTON_NEUTRAL:
Intent result = new Intent();
result.putExtra("lat", 0);
result.putExtra("lng", 0);
setResult(Activity.RESULT_OK, result);
finish();
break;
// 取消
case DialogInterface.BUTTON_NEGATIVE:
break;
}
}
};
// 標記訊息框點擊事件
mMap.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() {
@Override
public void onInfoWindowClick(Marker marker) {
// 如果是記事儲存的標記
if (marker.equals(itemMarker)) {
AlertDialog.Builder ab = new AlertDialog.Builder(MapsActivity.this);
ab.setTitle(R.string.title_update_location)
.setMessage(R.string.message_update_location)
.setCancelable(true);
ab.setPositiveButton(R.string.update, listener);
ab.setNeutralButton(R.string.clear, listener);
ab.setNegativeButton(android.R.string.cancel, listener);
ab.show();
}
}
});
// 標記點擊事件
mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
// 如果是目前位置標記
if (marker.equals(currentMarker)) {
AlertDialog.Builder ab = new AlertDialog.Builder(MapsActivity.this);
ab.setTitle(R.string.title_current_location)
.setMessage(R.string.message_current_location)
.setCancelable(true);
ab.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent();
result.putExtra("lat", currentLocation.getLatitude());
result.putExtra("lng", currentLocation.getLongitude());
setResult(Activity.RESULT_OK, result);
finish();
}
});
ab.setNegativeButton(android.R.string.cancel, null);
ab.show();
return true;
}
return false;
}
});
}
~~~
調整之前為了測試地圖元件加入的程式碼,包含「setUpMapIfNeeded」與「setUpMap」兩個方法:
~~~
private void setUpMapIfNeeded() {
if (mMap == null) {
mMap = ((SupportMapFragment) getSupportFragmentManager().
findFragmentById(R.id.map)).getMap();
if (mMap != null) {
// 移除地圖設定
//setUpMap();
processController();
}
}
}
// 移除地圖設定方法
private void setUpMap() {
// 建立位置的座標物件
LatLng place = new LatLng(25.033408, 121.564099);
// 移動地圖
moveMap(place);
// 加入地圖標記
addMarker(place, "Hello!", " Google Maps v2!");
}
~~~
參考下列的程式碼修改「addMarker」方法:
~~~
// 在地圖加入指定位置與標題的標記
private void addMarker(LatLng place, String title, String snippet) {
BitmapDescriptor icon =
BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher);
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(place)
.title(title)
.snippet(snippet)
.icon(icon);
// 加入並設定記事儲存的位置標記
itemMarker = mMap.addMarker(markerOptions);
}
~~~
完成所有工作了,在實體裝置執行應用程式,測試這一章完成的功能。
第四堂(2)设计地图应用程式 – Google Maps Android API v2
最后更新于:2022-04-01 00:53:09
地圖已經是非常普遍的使用在各種應用程式,Google在2013年2月,為Android平台推出Google Maps Android API v2,使用全新的設計方式,改良地圖元件的畫面與效率,增加3D建築物,還有簡化的繪圖API。Android已經在2013年4月停止舊的API,所以這裡只會說明Google Maps Android API v2的設計方式。
Google Maps Android API v2包含在「Google Play Service SDK」,目前不能在模擬裝置中測試地圖應用程式,所以必需準備好實體裝置,接下來的應用程式必須安裝在實體裝置執行測試。
這一章為記事資料加入地圖元件的應用,如果使用者在記事資料儲存座標的資料,就可以啟動地圖元件顯示儲存的地點:
[![AndroidTutorial5_04_02_01](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9e0f329b.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9e0f329b.png)
[![AndroidTutorial5_04_02_02](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9e296382.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9e296382.png)
## 13-1 建立地圖元件
地圖元件從網路下載所有的資料,包含地圖、接道名稱、衛星空照與地形。應用程式必須申請專用的「Google Maps API Key」,才可以正確的下載資料。使用Android Studio可以簡化地圖應用程式的開發,在開始設計地圖應用程式之前,先依照下列的的步驟,確認已經安裝好「Google Play services」SDK:
1. 啟動Android Studio並開啟記事應用程式。
2. 選擇功能表「Tools -> Android -> SDK Manager」。
3. 在Android SDK Manager視窗,檢查「Extras -> Google Play services」是否已經安裝。如果還沒有安裝的話,勾選並執行安裝的工作:[![AndroidTutorial5_04_02_03](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9eaf02ab.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9eaf02ab.png)
依照下列的的步驟,為記事應用程式新增一的地圖元件:
1. 啟動Android Studio並開啟記事應用程式。
2. 在應用程式目錄(App)按滑鼠右鍵,選擇「New -> Google -> Google Map Activity」。
3. 使用預設的設定,選擇「Finish」:[![AndroidTutorial5_04_02_04](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9f02f994.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9f02f994.png)
4. Android Studio建立好需要的元件以後,會自動開啟「google*maps*api.xml」,找到畫面中一個很長的網址,全部選擇以後按複製所有的內容:[![AndroidTutorial5_04_02_05](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9fd89a2d.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1b9fd89a2d.png)
5. 啟動瀏覽器在網址列貼上前一個步驟複製的網址,使用Google的帳號登入以後,勾選同意服務條款,選擇「同意並繼續」:[![AndroidTutorial5_04_02_06](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba05be886.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba05be886.png)
6. 網頁右下角顯示處理中的活動訊息:[![AndroidTutorial5_04_02_07](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba0cc1e71.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba0cc1e71.png)
7. 在出現的對話框選擇「建立」:[![AndroidTutorial5_04_02_08](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba138d83c.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba138d83c.png)
8. 建立完成後,在「API金鑰」後面的一串內容,就是為已經申請好的Google Maps API Key。選擇並複製API金鑰的內容:[![AndroidTutorial5_04_02_09](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba1d07a30.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba1d07a30.png)
9. 回到Android Studio,在google*maps*api.xml檔案,找到「YOUR*KEY*HERE」的位置:[![AndroidTutorial5_04_02_10](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba1f02bdd.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba1f02bdd.png)
10. 把第八個步驟複製的API金鑰貼上與覆蓋「YOUR*KEY*HERE」:[![AndroidTutorial5_04_02_11](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba2752c32.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba2752c32.png)
完成上面的步驟以後,就已經建立好一個預設的地圖元件。依照下列的說明,檢查Android Studio為應用程式加入的內容:
* google*maps*api.xml:儲存與設定Google Maps API Key的資源檔案。
* MapsActivity.java:地圖元件。
* activity_maps.xml:地圖元件使用的畫面資源檔。
* Gradle Scripts -> build.gradle(Module:app):在「dependencies」區塊加入Google Play Service SDK的設定。
~~~
dependencies {
...
compile 'com.google.android.gms:play-services:7.0.0'
}
~~~
* AndroidManifest.xml:在標籤下加入讀取位置的授權,目前還沒有使用讀取位置的功能。
~~~
<!-- 新增地圖元件的時候,自動加入的設定 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
~~~
* AndroidManifest.xml:在標籤下加入Google Service版本、Google Map API key與地圖元件的設定。
~~~
<!-- Google Service版本 -->
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<!-- Google Map API key -->
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="@string/google_maps_key" />
<!-- 地圖元件 -->
<activity
android:name=".MapsActivity"
android:label="@string/title_activity_maps" >
~~~
## 13-2 啟動地圖元件
建立好的地圖元件,可以使用一般的方式啟動它。開啟「ItemActivity.java」,參考下列的內容,加入啟動地圖元件的程式碼:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
...
case R.id.set_location:
// 啟動地圖元件用的Intent物件
Intent intentMap = new Intent(this, MapsActivity.class);
// 啟動地圖元件
startActivityForResult(intentMap, START_LOCATION);
break;
...
}
}
~~~
完成上面的程式碼,連接實體裝置到電腦,執行應用程式後選擇安裝到實體裝置。在應用程式的主畫面選擇任何一個記事資料,選擇位置按鈕元件:
[![AndroidTutorial5_04_02_12](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba3391de7.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba3391de7.png)
應用程式啟動地圖元件以後,如果出現下列的畫面,表示地圖元件可以正確的運作:
[![AndroidTutorial5_04_02_13](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba3552182.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba3552182.png)
## 13-3 管理與新增Google Maps API Key
每一個使用地圖元件的應用程式,都需要依照上列的方式建立Google Maps API Key,地圖才可以正確下載與顯示。如果開發其它使用地圖元件的應用程式,可以繼續使用同一個Google Maps API Key。參考下列的步驟,先瞭解執行設定的作法:
1. 在瀏覽器開啟這個網頁:[https://console.developers.google.com/](https://console.developers.google.com/)。
2. 選擇「My Project」:
[![AndroidTutorial5_04_02_14](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba367bf07.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba367bf07.png)
1. 選擇「API和憑證 -> 憑證」目錄後,找到剛才申請的Android 應用程式的金鑰,選擇「編輯允許使用的Android應用程式」:
[![AndroidTutorial5_04_02_15](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba37ca187.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba37ca187.png)
1. 參考網頁的說明,加入第二個應用程式。在同一台電腦開發的時候,在「;」前的設定不變,「;」後面是應用程式的套件名稱。完成後選擇「更新」就可以讓其它應用程式使用這個申請好的Google Maps API Key:
[![AndroidTutorial5_04_02_16](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba3a4be85.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba3a4be85.png)
## 13-4 設定地圖顯示的位置
目前地圖元件顯示的畫面是預設的位置,以記事應用程式來說,在啟動地圖元件以後,需要將位置移動到儲存的位置,或是根據裝置資訊顯示目前的位置。目前的記事資料還沒有儲存位置資訊,下一章就會說明讀取與儲存位置資訊的作法,現在先瞭解設定地圖顯示位置的作法。
開啟「MapsActivity.java」,加入提供移動地圖功能的方法:
~~~
// 移動地圖到參數指定的位置
private void moveMap(LatLng place) {
// 建立地圖攝影機的位置物件
CameraPosition cameraPosition =
new CameraPosition.Builder()
.target(place)
.zoom(17)
.build();
// 使用動畫的效果移動地圖
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}
~~~
同樣在「MapsActivity.java」,找到預設的「setUpMap」方法,參考下列的內容修改原有的程式碼:
~~~
private void setUpMap() {
// 刪除原來預設的內容
//mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
// 建立位置的座標物件
LatLng place = new LatLng(25.033408, 121.564099);
// 移動地圖
moveMap(place);
}
~~~
完成上面的程式碼,執行應用程式後選擇安裝到實體裝置。在應用程式的主畫面選擇任何一個記事資料,選擇位置按鈕元件。應用程式啟動地圖元件以後,地圖畫面就會移動到指定的位置:
[![AndroidTutorial5_04_02_17](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba56321d3.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba56321d3.png)
## 13-5 新增地圖標記
地圖元件可以依照應用程式的需求,在地圖指定的位置加入標記。在「com.google.android.gms.maps.model」套件提供下列的元件:
* Marker – 在地圖上的一個標記就是一個Marker物件。
* MarkerOptions – 在加入Marker到地圖前,先使用它設定好Marker的所有資訊,例如位置、圖示與標題。
* BitmapDescriptor與BitmapDescriptorFactory – 需要設定Marker圖示的時候會用到它們。
開啟「MapsActivity.java」,加入設定地圖標記的方法:
~~~
// 在地圖加入指定位置與標題的標記
private void addMarker(LatLng place, String title, String snippet) {
BitmapDescriptor icon =
BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher);
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(place)
.title(title)
.snippet(snippet)
.icon(icon);
mMap.addMarker(markerOptions);
}
~~~
同樣在「MapsActivity.java」,找到「setUpMap」方法,參考下列的內容修改原有的程式碼:
~~~
private void setUpMap() {
// 刪除原來預設的內容
//mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
// 建立位置的座標物件
LatLng place = new LatLng(25.033408, 121.564099);
// 移動地圖
moveMap(place);
// 加入地圖標記
addMarker(place, "Hello!", " Google Maps v2!");
}
~~~
完成上面的程式碼,執行應用程式後選擇安裝到實體裝置。在應用程式的主畫面選擇任何一個記事資料,選擇位置按鈕元件。應用程式啟動地圖元件以後,地圖畫面在指定的位置顯示標記:
[![AndroidTutorial5_04_02_18](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba5ac9f96.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba5ac9f96.png)
點選標記以後,標記上方就會顯示設定的說明:
[![AndroidTutorial5_04_02_19](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba62ec9f0.png)](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d1ba62ec9f0.png)
完成這一章關於地圖的應用,下一章加入位置資訊的讀取與儲存以後,就可以在地圖元件顯示記事儲存的位置,或是根據裝置資訊顯示目前的位置。
第四堂(1)使用照相机与麦克风
最后更新于:2022-04-01 00:53:06
**这一章在处理相片与录音档名称的时候有错误,建议在完成这一章的内容以后,再参考[错误修正:4-1 使用照相机与麦克风](https://github.com/macdidi5/AndroidTutorial/blob/master/AndroidTutorial4_1Update.md)修正错误。**
现在行动装置的硬件设备技术已经越来越好了,萤幕的尺寸不断的增加,提供使用者清楚又美观的画面。触控萤幕也几乎是目前行动装置的标准设备,使用触控的方式操作应用程式快速又方便。Android系统内建的音乐播放应用程式,也可以让行动装置成为随身的音乐播放设备。还有画素也越来越高的照像功能,一台行动装置几乎可以应付所有的需求。
行动装置提供高画质的摄影镜头,让使用者随时可以拍摄照片与录影,也几乎已经是行动装置基本的设备与功能了。使用Android系统内建的API与元件,可以在应用程式需要的时候,让使用者拍摄照片与录影,并且把照片或影片档案储存在指定的位置。例如在记事本应用程式中,可以加入照片与录影备忘的功能。
应用程式需要录音的时候,可以使用内建的API执行录音的工作,并且把录音完成的档案储存在指定的位置,例如在记事本应用程式中,可以加入录制语音备忘的功能,让使用者可以随时查询与播放这些录音资讯。
这一章为记事资料加入照相与录音的功能,让这个应用程式的功能可以更完整,使用者可以在新增或修改记事资料的时候,启动相机拍照,还有使用麦克风录制语音备忘。
## 12-1 使用相机拍摄照片
不论是移动电话或平板电脑,几乎都有高画质的摄录镜头设备,让使用者可以随时拍摄与录影。加入拍摄照片的功能可以让应用程式的功能更完整,例如在记事本应用程式加入拍照的功能,记录影像会比文字更清楚与方便。
应用程式需要执行拍照的功能,可以启动系统相机元件执行拍照的工作,它的系统Action名称变量是“MediaStore.ACTION_IMAGE_CAPTURE”,使用这个Action名称建立好的Intent物件,可以呼叫putExtra方法加入照片档案储存位置的设定资料,资料的名称是“MediaStore.EXTRA_OUTPUT”,如果没有指定的话,会使用系统默认的名称储存在默认的位置。
应用程式要执行拍照的功能,装置必须有摄录镜头的设备才可以正确的执行,所以需要在应用程式设定档中加入硬件设备需求的设定。如果需要储存照片档案到外部储存设备,例如记忆卡,需要在应用程式设定档中加入授权设定:
~~~
<!-- 需要摄录镜头设备 -->
<uses-feature android:name="android.hardware.camera" />
<!-- 写入外部储存设备 -->
<uses-permission android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"/>
~~~
Android模拟装置也可以测试相机的功能,不过要先确认模拟装置的设定,关闭已经启动的模拟装置,在Android Studio选择功能表“Tools -> Android -> AVD Manager”,选择模拟装置的编辑图示:
[![AndroidTutorial5_04_01_01](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_01-300x171.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_01.png)
在模拟装置编辑视窗选择“Show Advanced Settings”:
[![AndroidTutorial5_04_01_02](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_02-300x152.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_02.png)
如果你的电脑没有连接WebCam,在“Front”与“Back”选择“Emulated”。如果电已经连接WebCam,就可以选择“Webcam0”。完成设定后选择“Finish”:
[![AndroidTutorial5_04_01_03](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_03-300x152.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_03.png)
回到AVD Manager视窗后,选择模拟装置的启动图示:
[![AndroidTutorial5_04_01_04](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_04-300x171.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_04.png)
模拟装置启动以后,开启“照相”应用程式,就可以看到模拟照相机的画面:
[![AndroidTutorial5_04_01_05](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_05-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_05.png)
因为记事元件的画面加入照片以后,在萤幕比较小的装置运作时,画面会超过萤幕的范围,所以需要调整画面的设计。另外也要加入显示照片用的ImageView元件。开启“res/layout/activity_item.xml”,参考下列的内容修改这个画面配置档:
~~~
<?xml version="1.0" encoding="utf-8"?>
<!-- 使用ScrollView为最外层的元件 -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 删除xmlns:android的设定 -->
<TableLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="1"
tools:context="net.macdidi.myandroidtutorial.ItemActivity">
<TableRow>
...
</TableRow>
<TableRow>
...
</TableRow>
<!-- 显示图片 -->
<ImageView
android:id="@+id/picture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/retangle_drawable"
android:padding="6sp"
android:layout_margin="2sp"
android:visibility="invisible" />
<TableLayout ...>
<TableRow>
...
</TableRow>
</TableLayout>
<TableLayout ...>
<TableRow>
...
</TableRow>
</TableLayout>
</TableLayout>
<!-- ScrollView的结束标签 -->
</ScrollView>
~~~
因为需要储存照片与录音档案,所以撰写一个档案公用类别。在“net.macdidi.myandroidtutorial”套件按鼠标右键,选择“New -> Java CLass”,在Name输入“FileUtil”后选择“OK”。参考下列的内容完成这个程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;
import android.widget.ImageView;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FileUtil {
// 应用程式储存盘案的目录
public static final String APP_DIR = "androidtutorial";
// 外部储存设备是否可写入
public static boolean isExternalStorageWritable() {
// 取得目前外部储存设备的状态
String state = Environment.getExternalStorageState();
// 判断是否可写入
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
// 外部储存设备是否可读取
public static boolean isExternalStorageReadable() {
// 取得目前外部储存设备的状态
String state = Environment.getExternalStorageState();
// 判断是否可读取
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
// 建立并传回在公用相簿下参数指定的路径
public static File getPublicAlbumStorageDir(String albumName) {
// 取得公用的照片路径
File pictures = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
// 准备在照片路径下建立一个指定的路径
File file = new File(pictures, albumName);
// 如果建立路径不成功
if (!file.mkdirs()) {
Log.e("getAlbumStorageDir", "Directory not created");
}
return file;
}
// 建立并传回在应用程式专用相簿下参数指定的路径
public static File getAlbumStorageDir(Context context, String albumName) {
// 取得应用程式专用的照片路径
File pictures = context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES);
// 准备在照片路径下建立一个指定的路径
File file = new File(pictures, albumName);
// 如果建立路径不成功
if (!file.mkdirs()) {
Log.e("getAlbumStorageDir", "Directory not created");
}
return file;
}
// 建立并传回外部储存媒体参数指定的路径
public static File getExternalStorageDir(String dir) {
File result = new File(
Environment.getExternalStorageDirectory(), dir);
if (!isExternalStorageWritable()) {
return null;
}
if (!result.exists() && !result.mkdirs()) {
return null;
}
return result;
}
// 读取指定的照片档案名称设定给ImageView元件
public static void fileToImageView(String fileName, ImageView imageView) {
if (new File(fileName).exists()) {
Bitmap bitmap = BitmapFactory.decodeFile(fileName);
imageView.setImageBitmap(bitmap);
}
else {
Log.e("fileToImageView", fileName + " not found.");
}
}
// 产生唯一的档案名称
public static String getUniqueFileName() {
// 使用年月日_时分秒格式为档案名称
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
return sdf.format(new Date());
}
}
~~~
开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,加入照相功能需要的字段变量:
~~~
// 档案名称
private String fileName;
// 照片
private ImageView picture;
~~~
同样在“ItemActivity”类别,找到“processViews”方法,参考下列的程式码,加入取得显示照片ImageView元件的程式码:
~~~
private void processViews() {
title_text = (EditText) findViewById(R.id.title_text);
content_text = (EditText) findViewById(R.id.content_text);
// 取得显示照片的ImageView元件
picture = (ImageView) findViewById(R.id.picture);
}
~~~
同样在“ItemActivity”类别,找到“clickFunction”方法,参考下列的程式码,加入启动相机元件的程式码:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
case R.id.take_picture:
// 启动相机元件用的Intent物件
Intent intentCamera =
new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 照片档案名称
File pictureFile = configFileName("P", ".jpg");
Uri uri = Uri.fromFile(pictureFile);
// 设定档案名称
intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, uri);
// 启动相机元件
startActivityForResult(intentCamera, START_CAMERA);
break;
...
}
}
private File configFileName(String prefix, String extension) {
// 如果记事资料已经有档案名称
if (item.getFileName() != null && item.getFileName().length() > 0) {
fileName = item.getFileName();
}
// 产生档案名称
else {
fileName = FileUtil.getUniqueFileName();
}
return new File(FileUtil.getExternalStorageDir(FileUtil.APP_DIR),
prefix + fileName + extension);
}
~~~
同样在“ItemActivity”类别,找到“onActivityResult”方法,参考下列的程式码,处理完成照相工作后的程式码:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
// 照像
case START_CAMERA:
// 设定照片档案名称
item.setFileName(fileName);
break;
...
}
}
}
~~~
同样在“ItemActivity”类别,新增覆写“onResume”方法的程式码,执行显示照片的工作:
~~~
@Override
protected void onResume() {
super.onResume();
// 如果有档案名称
if (item.getFileName() != null && item.getFileName().length() > 0) {
// 照片档案物件
File file = configFileName("P", ".jpg");
// 如果照片档案存在
if (file.exists()) {
// 显示照片元件
picture.setVisibility(View.VISIBLE);
// 设定照片
FileUtil.fileToImageView(file.getAbsolutePath(), picture);
}
}
}
~~~
完成照相功能的工作了,执行应用程式,新增一个记事资料后选择照相功能:
[![AndroidTutorial5_04_01_06](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_06-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_06.png)
画面出现像这样的相机模拟画面,选择照像按钮:
[![AndroidTutorial5_04_01_07](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_07-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_07.png)
选择确定按钮:
[![AndroidTutorial5_04_01_08](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_08-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_08.png)
记事资料显示拍好的照片,储存记事资料后也会储存照片:
[![AndroidTutorial5_04_01_09](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_09-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_09.png)
## 12-2 录制语音备忘
在行动装置的应用程式使用录音功能,可以让很多工作变得更方便,例如语音备忘录的功能,可以省掉很多输入文字的时间。如果应用程式需要执行录音的工作,使用宣告在“android.media”套件下的“MediaRecorder”类别,应用程式可以设定录音的来源、输出格式、编码和储存盘案的位置。这些是执行设定与录音的方法,要特别注意在程式码中呼叫它们的顺序:
* setAudioSource(int) – 设定录音来源,必须在setOutputFormat方法之前呼叫。设定为“MediaRecorder.AudioSource.MIC”表示录音来源是麦克风。
* setOutputFormat(int) –设定输出格式,必须在setAudioSource方法之后。设定为“MediaRecorder.OutputFormat.THREE_GPP”表示输出为3GP压缩格式。
* setAudioEncoder(int) –设定编码方式,必须在setOutputFormat方法之后。一般设定为“MediaRecorder.AudioEncoder.AMR_NB”。
* setOutputFile(String) –设定输出的档案名称,必须在setOutputFormat方法之后。
* prepare() – 使用设定的内容准备录音。
* start() – 开始录音。
* stop() – 停止录音。
* release() – 清除录音资源。
如果应用程式需要使用装置的录音设备,必须在应用程式设定档“AndroidManifest.xml”加入授权的设定:
~~~
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
~~~
加入录音功能的记事应用程式,可以让使用者选择录音功能按钮,启动一个负责录音的Activity元件,按下录音按钮就可以开始录音:
[![AndroidTutorial5_04_01_10](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_10-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_10.png)
录音的时候,录音按钮会切换为红色的图示,录音的音量变化会在右侧显示:
[![AndroidTutorial5_04_01_11](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_11-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_11.png)
设计录音元件的画面配置档,使用的图形资源可以在GitHub中取得,需要“record_dard_icon.png”与“record_red_icon.png”两个图示档案。开启“res/values/strings.xml”,加入这个元件需要的文字资源:
~~~
<string name="title_record">语音备忘</string>
~~~
在“res/layout”目录按鼠标右键,选择“New -> Layout resrouce file”,在File name输入“activity_record”候选择“OK”。参考下列的内容,完成这个画面资源的设计:
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/retangle_drawable"
android:layout_margin="6sp"
android:padding="6sp">
<ImageButton
android:id="@+id/record_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/record_sound_icon"
android:onClick="clickRecord" />
<ProgressBar
android:id="@+id/record_volumn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:max="15"
style="@android:style/Widget.ProgressBar.Horizontal" />
</LinearLayout>
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="*">
<TableRow>
<Button
android:text="@android:string/cancel"
android:onClick="onSubmit" />
<Button
android:id="@+id/record_ok"
android:text="@android:string/ok"
android:onClick="onSubmit" />
</TableRow>
</TableLayout>
</LinearLayout>
~~~
在“net.macdidi.myandroidtutorial”套件按鼠标右键,选择“New -> Java CLass”,在Name输入“RecordActivity”后选择“OK”。参考下列的内容完成这个Activity元件的程式码:(这里提供的设计包含显示录音中的音量,你可以考虑移除这个部份的程式码,这个元件的设计就会比较简单一些)
~~~
package net.macdidi.myandroidtutorial;
import android.app.Activity;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import java.io.IOException;
public class RecordActivity extends Activity {
private ImageButton record_button;
private boolean isRecording = false;
private ProgressBar record_volumn;
private MyRecoder myRecoder;
private String fileName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record);
processViews();
// 读取档案名称
Intent intent = getIntent();
fileName = intent.getStringExtra("fileName");
}
public void onSubmit(View view) {
if (isRecording) {
// 停止录音
myRecoder.stop();
}
// 确定
if (view.getId() == R.id.record_ok) {
Intent result = getIntent();
setResult(Activity.RESULT_OK, result);
}
finish();
}
private void processViews() {
record_button = (ImageButton) findViewById(R.id.record_button);
record_volumn = (ProgressBar) findViewById(R.id.record_volumn);
// 隐藏状态列ProgressBar
setProgressBarIndeterminateVisibility(false);
}
public void clickRecord(View view) {
// 切换
isRecording = !isRecording;
// 开始录音
if (isRecording) {
// 设定按钮图示为录音中
record_button.setImageResource(R.drawable.record_red_icon);
// 建立录音物件
myRecoder = new MyRecoder(fileName);
// 开始录音
myRecoder.start();
// 建立并执行显示麦克风音量的AsyncTask物件
new MicLevelTask().execute();
}
// 停止录音
else {
// 设定按钮图示为停止录音
record_button.setImageResource(R.drawable.record_dark_icon);
// 麦克风音量归零
record_volumn.setProgress(0);
// 停止录音
myRecoder.stop();
}
}
// 在录音过程中显示麦克风音量
private class MicLevelTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... args) {
while (isRecording) {
publishProgress();
try {
Thread.sleep(200);
}
catch (InterruptedException e) {
Log.d("RecordActivity", e.toString());
}
}
return null;
}
@Override
protected void onProgressUpdate(Void... values) {
record_volumn.setProgress((int) myRecoder.getAmplitudeEMA());
}
}
// 执行录音并且可以取得麦克风音量的录音物件
private class MyRecoder {
private static final double EMA_FILTER = 0.6;
private MediaRecorder recorder = null;
private double mEMA = 0.0;
private String output;
// 建立录音物件,参数为录音储存的位置与档名
MyRecoder(String output) {
this.output = output;
}
// 开始录音
public void start() {
if (recorder == null) {
// 建立录音用的MediaRecorder物件
recorder = new MediaRecorder();
// 设定录音来源为麦克风,必须在setOutputFormat方法之前呼叫
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 设定输出格式为3GP压缩格式,必须在setAudioSource方法之后,
// 在prepare方法之前呼叫
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
// 设定录音的编码方式,必须在setOutputFormat方法之后,
// 在prepare方法之前呼叫
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
// 设定输出的档案名称,必须在setOutputFormat方法之后,
// 在prepare方法之前呼叫
recorder.setOutputFile(output);
try {
// 准备执行录音工作,必须在所有设定之后呼叫
recorder.prepare();
}
catch (IOException e) {
Log.d("RecordActivity", e.toString());
}
// 开始录音
recorder.start();
mEMA = 0.0;
}
}
// 停止录音
public void stop() {
if (recorder != null) {
// 停止录音
recorder.stop();
// 清除录音资源
recorder.release();
recorder = null;
}
}
public double getAmplitude() {
if (recorder != null)
return (recorder.getMaxAmplitude() / 2700.0);
else
return 0;
}
// 取得麦克风音量
public double getAmplitudeEMA() {
double amp = getAmplitude();
mEMA = EMA_FILTER * amp + (1.0 - EMA_FILTER) * mEMA;
return mEMA;
}
}
}
~~~
开启应用程式设定档“AndroidManifest.xml”,加入录音元件的设定:
~~~
<!-- 录音元件 -->
<activity
android:name="net.macdidi.myandroidtutorial.RecordActivity"
android:theme="@android:style/Theme.Dialog"
android:label="@string/title_record"/>
~~~
完成录音元件的设计后,开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,找到“clickFunction”方法,加入启动录音元件的程式码,还有负责录音的goToRecord方法:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
case R.id.take_picture:
...
case R.id.record_sound:
// 录音档案名称
final File recordFile = configFileName("R", ".mp3");
// 如果已经有录音档,询问播放或重新录制
if (recordFile.exists()) {
// 询问播放还是重新录制的对话框
AlertDialog.Builder d = new AlertDialog.Builder(this);
d.setTitle(R.string.title_record)
.setCancelable(false);
d.setPositiveButton(R.string.record_play,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 播放
// 在后面的说明才会处理
}
});
d.setNeutralButton(R.string.record_new,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
goToRecord(recordFile);
}
});
d.setNegativeButton(android.R.string.cancel, null);
// 显示对话框
d.show();
}
// 如果没有录音档,启动录音元件
else {
goToRecord(recordFile);
}
break;
...
}
}
private void goToRecord(File recordFile) {
// 录音
Intent recordIntent = new Intent(this, RecordActivity.class);
recordIntent.putExtra("fileName", recordFile.getAbsolutePath());
startActivityForResult(recordIntent, START_RECORD);
}
~~~
为记事资料完成录音的功能了,在完成后面的播放功能后,再一起执行测试的工作。
## 12-3 播放语音备忘
在前面已经完成的功能,如果使用者选择的记事资料已经录制过语音备忘,应用程式可以选择播放或是重新录制:
[![AndroidTutorial5_04_01_12](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_12-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_12.png)
使用者选择播放功能,应用程式启动播放语音备忘元件,这个元件提供播放、暂停与停止三个功能按钮:
[![AndroidTutorial5_04_01_13](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_13-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_04_01_13.png)
现在设计录音元件的画面配置档,使用的图形资源可以在GitHub中取得,需要“play_icon.png”、“pause_icon”与“stop_icon.png”三个图示档案。开启“res/values/strings.xml”,加入这个元件需要的文字资源:
~~~
<string name="title_play">播放语音备忘</string>
<string name="record_play">播放</string>
<string name="record_new">重新录制</string>
~~~
在“res/layout”目录按鼠标右键,选择“New -> Layout resrouce file”,在File name输入“activity_play”候选择“OK”。参考下列的内容,完成这个画面资源的设计:
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/retangle_drawable"
android:layout_margin="6sp"
android:padding="6sp" >
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/play_icon"
android:onClick="clickPlay" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pause_icon"
android:onClick="clickPause" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/stop_icon"
android:onClick="clickStop" />
<SeekBar
android:id="@+id/control"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="8sp" />
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/ok_add_teim"
android:text="@android:string/ok"
android:onClick="onSubmit" />
</LinearLayout>
~~~
在“net.macdidi.myandroidtutorial”套件按鼠标右键,选择“New -> Java CLass”,在Name输入“PlayActivity”后选择“OK”。参考下列的内容完成这个Activity元件的程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.app.Activity;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
public class PlayActivity extends Activity {
private MediaPlayer mediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
Intent intent = getIntent();
String fileName = intent.getStringExtra("fileName");
// 建立指定资源的MediaPlayer物件
Uri uri = Uri.parse(fileName);
mediaPlayer = MediaPlayer.create(this, uri);
}
@Override
protected void onStop() {
if (mediaPlayer.isPlaying()) {
// 停止播放
mediaPlayer.stop();
}
// 清除MediaPlayer物件
mediaPlayer.release();
super.onStop();
}
public void onSubmit(View view) {
// 结束Activity元件
finish();
}
public void clickPlay(View view) {
// 开始播放
mediaPlayer.start();
}
public void clickPause(View view) {
// 暂停播放
mediaPlayer.pause();
}
public void clickStop(View view) {
// 停止播放
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
// 回到开始的位置
mediaPlayer.seekTo(0);
}
}
~~~
开启应用程式设定档“AndroidManifest.xml”,加入这个Activity元件的设定:
~~~
<!-- 播放元件 -->
<activity
android:name="net.macdidi.myandroidtutorial.PlayActivity"
android:theme="@android:style/Theme.Dialog"
android:label="@string/title_play"/>
~~~
完成元件的设计后,开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,找到“clickFunction”方法,加入启动这个元件的程式码:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
case R.id.take_picture:
...
case R.id.record_sound:
// 录音档案名称
final File recordFile = configFileName("R", ".mp3");
// 如果已经有录音档,询问播放或重新录制
if (recordFile.exists()) {
// 询问播放还是重新录制的对话框
AlertDialog.Builder d = new AlertDialog.Builder(this);
d.setTitle(R.string.title_record)
.setCancelable(false);
d.setPositiveButton(R.string.record_play,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 播放
Intent playIntent = new Intent(
ItemActivity.this, PlayActivity.class);
playIntent.putExtra("fileName",
recordFile.getAbsolutePath());
startActivity(playIntent);
}
});
d.setNeutralButton(R.string.record_new,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
goToRecord(recordFile);
}
});
d.setNegativeButton(android.R.string.cancel, null);
// 显示对话框
d.show();
}
// 如果没有录音档,启动录音元件
else {
goToRecord(recordFile);
}
break;
...
}
}
~~~
同样在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,找到“onActivityResult”方法,加入设定档案名称的程式码:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
...
case START_RECORD:
// 设定录音档案名称
item.setFileName(fileName);
break;
...
}
}
}
~~~
完成这一章所有的工作了,录音与播放的功能比较建议在实体的装置上测试,试试看加入的功能是不是都可以正确的运作。
## 错误修正:4-1 使用照相机与麦克风
这一章加入的照相与录音功能,把照片与录音档案名称储存在同一个字段。在完成这一章的内容后依照下列的步骤修正错误:
1. 开启“Item.java”,加入下列的字段与方法宣告:
~~~
// 录音档案名称
private String recFileName;
public String getRecFileName() {
return recFileName;
}
public void setRecFileName(String recFileName) {
this.recFileName = recFileName;
}
~~~
2. 同样在“Item.java”,为建构子加入录音档案名称参数:
~~~
// 录音档案名称参数:String recFileName
public Item(long id, long datetime, Colors color, String title,
String content, String fileName, String recFileName,
double latitude, double longitude, long lastModify) {
this.id = id;
this.datetime = datetime;
this.color = color;
this.title = title;
this.content = content;
this.fileName = fileName;
// 录音档案名称
this.recFileName = recFileName;
this.latitude = latitude;
this.longitude = longitude;
this.lastModify = lastModify;
}
~~~
3. 开启“ItemDAO.java”,加入与修改下列的字段宣告:
~~~
...
// 录音档案名称
public static final String RECFILENAME_COLUMN = "recfilename";
...
// 在“FILENAME_COLUMN”下方加入录音档案名称字段
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME + " (" +
...
FILENAME_COLUMN + " TEXT, " +
RECFILENAME_COLUMN + " TEXT, " + // 增加录音档案名称
...";
~~~
4. 同样在“ItemDAO.java”,修改“insert”方法:
~~~
public Item insert(Item item) {
ContentValues cv = new ContentValues();
...
cv.put(FILENAME_COLUMN, item.getFileName());
// 录音档案名称
cv.put(RECFILENAME_COLUMN, item.getRecFileName());
...
}
~~~
5. 同样在“ItemDAO.java”,修改“update”方法:
~~~
public boolean update(Item item) {
ContentValues cv = new ContentValues();
...
cv.put(FILENAME_COLUMN, item.getFileName());
// 录音档案名称
cv.put(RECFILENAME_COLUMN, item.getRecFileName());
...
}
~~~
6. 同样在“ItemDAO.java”,修改“getRecord”方法:
~~~
public Item getRecord(Cursor cursor) {
...
result.setFileName(cursor.getString(5));
// 录音档案名称
result.setRecFileName(cursor.getString(6));
// 后续的编号都要加一
result.setLatitude(cursor.getDouble(7));
...
}
~~~
7. 同样在“ItemDAO.java”,修改“sample”方法:
~~~
public void sample() {
// 增加录音档案名称参数“""”
Item item = new Item(0, new Date().getTime(), Colors.RED, "关于Android Tutorial的事情.", "Hello content", "", "", 0, 0, 0);
Item item2 = new Item(0, new Date().getTime(), Colors.BLUE, "一只非常可爱的小狗狗!", "她的名字叫“大热狗”,又叫\n作“奶嘴”,是一只非常可爱\n的小狗。", "", "", 25.04719, 121.516981, 0);
Item item3 = new Item(0, new Date().getTime(), Colors.GREEN, "一首非常好听的音乐!", "Hello content", "", "", 0, 0, 0);
Item item4 = new Item(0, new Date().getTime(), Colors.ORANGE, "储存在数据库的资料", "Hello content", "", "", 0, 0, 0);
...
}
~~~
8. 开启“MyDBHelper.java”,增加数据库版本编号:
~~~
// 数据库版本,资料结构改变的时候要更改这个数字,通常是加一
public static final int VERSION = 2;
~~~
9. 开启“ItemActivity.java”,增加录音档案名称字段变量:
~~~
// 录音档案名称
private String recFileName;
~~~
10. 同样在“ItemActivity.java”,增加取得录音档案名称的方法:
~~~
private File configRecFileName(String prefix, String extension) {
// 如果记事资料已经有档案名称
if (item.getRecFileName() != null && item.getRecFileName().length() > 0) {
recFileName = item.getRecFileName();
}
// 产生档案名称
else {
recFileName = FileUtil.getUniqueFileName();
}
return new File(FileUtil.getExternalStorageDir(FileUtil.APP_DIR),
prefix + recFileName + extension);
}
~~~
11. 同样在“ItemActivity.java”,修改启动录音元件的方法:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
...
case R.id.record_sound:
// 修改呼叫方法的名称为“configRecFileName”
final File recordFile = configRecFileName("R", ".mp3");
if (recordFile.exists()) {
...
}
// 如果没有录音档,启动录音元件
else {
goToRecord(recordFile);
}
break;
...
}
}
~~~
12. 同样在“ItemActivity.java”,找到“onActivityResult”方法,修改设定录音档案名称呼叫的方法:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
...
case START_RECORD:
// 修改设定录音档案名称
item.setRecFileName(recFileName);
break;
...
}
}
}
~~~
完成全部的修改以后执行应用程式,测试同一个记事资料照相与录音的功能。
第四堂
最后更新于:2022-04-01 00:53:04
第三堂(3)Android 内建的 SQLite 数据库
最后更新于:2022-04-01 00:53:02
Android系统内建“SQLite”数据库,它是一个开放的小型数据库,它跟一般商用的大型数据库有类似的架构与用法,例如MySQL数据库。应用程式可以建立自己需要的数据库,在数据库中使用Android API执行资料的管理和查询的工作。储存资料的数量是根据装置的储存空间决定的,所以如果空间足够的话,应用程式可以储存比较大量的资料,在需要的时候随时可以执行数据库的管理和查询的工作。
一般商用的大型数据库,可以提供快速存取与储存非常大量的资料,也包含网络通讯和复杂的存取权限管理,不过它们都会使用一种共通的语言“SQL”,不同的数据库产品都可以使用SQL这种数据库语言,执行资料的管理和查询的工作。SQLite数据库虽然是一个小型数据库,不过它跟一般大型数据库的架构与用法也差不多,同样可以使用SQL执行需要的工作,Android另外提供许多数据库的API,让开发人员使用API执行数据库的工作。
这一章会从了解应用程式数据库的需求开始,介绍如何建立数据库与表格,在应用程式运作的过程中,如何执行数据库的新增、修改、删除与查询的工作。
## 11-1 设计数据库表格
在数据库的技术中,一个数据库(Database)表示应用程式储存与管理资料的单位,应用程式可能需要储存很多不同的资料,例如一个购物网站的数据库,就需要储存与管理会员、商品和订单资料。每一种在数据库中的资料称为表格(Table),例如会员表格可以储存所有的会员资料。
SQLite 数据库的架构也跟一般数据库的概念类似,所以应用程式需要先建立好需要的数据库与表格后,才可以执行储存与管理资料的工作。建立表格是在Android应用程式中,唯一需要使用SQL执行的工作。其它执行数据库管理与查询的工作,Android都提供执行各种功能的API,使用这些API就不需要了解太多SQL这种数据库语言。
建立数据库表格使用SQL的“CREATE TABLE”指令,这个指令需要指定表格的名称,还有这个表格用来储存每一笔资料的字段(Column)。这些需要的表格字段可以对应到主要类别中的字段变量,不过SQLite数据库的资料型态只有下面这几种,使用它们来决定表格字段可以储存的资料型态:
* INTEGER – 整数,对应Java 的byte、short、int 和long。
* REAL – 小数,对应Java 的float 和double。
* TEXT – 字串,对应Java 的String。
在设计表格字段的时候,需要设定字段名称和型态,表格字段的名称建议就使用主要类别中的字段变量名称。表格字段的型态依照字段变量的型态,把它们转换为SQLite提供的资料型态。通常在表格字段中还会加入“NOT NULL”的指令,表示这个表格字段不允许空值,可以避免资料发生问题。
表格的名称可以使用主要类别的类别名称,一个SQLite表格建议一定要包含一个可以自动为资料编号的字段,字段名称固定为“_id”,型态为“INTEGER”,后面加上“PRIMARY KEY AUTOINCREMENT”的设定,就可以让SQLite自动为每一笔资料编号以后储存在这个字段。
## 11-2 建立SQLiteOpenHelper类别
Android 提供许多方便与简单的数据库API,可以简化应用程式处理数据库的工作。这些API都在“android.database.sqlite”套件,它们可以用来执行数据库的管理和查询的工作。在这个套件中的“SQLiteOpenHelper”类别,可以在应用程式中执行建立数据库与表格的工作,应用程式第一次在装置执行的时候,由它负责建立应用程式需要的数据库与表格,后续执行的时候开启已经建立好的数据库让应用程式使用。还有应用程式在运作一段时间以后,如果增加或修改功能,数据库的表格也增加或修改了,它也可以为应用程式执行数据库的修改工作,让新的应用程式可以正常的运作。
接下来设计建立数据库与表格的类别,在“net.macdidi.myandroidtutorial”套件按鼠标右键,选择“New -> Java CLass”,在Name输入“MyDBHelper”后选择“OK”。参考下列的内容先完成部份的程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
public class MyDBHelper extends SQLiteOpenHelper {
// 数据库名称
public static final String DATABASE_NAME = "mydata.db";
// 数据库版本,资料结构改变的时候要更改这个数字,通常是加一
public static final int VERSION = 1;
// 数据库物件,固定的字段变量
private static SQLiteDatabase database;
// 建构子,在一般的应用都不需要修改
public MyDBHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
// 需要数据库的元件呼叫这个方法,这个方法在一般的应用都不需要修改
public static SQLiteDatabase getDatabase(Context context) {
if (database == null || !database.isOpen()) {
database = new MyDBHelper(context, DATABASE_NAME,
null, VERSION).getWritableDatabase();
}
return database;
}
@Override
public void onCreate(SQLiteDatabase db) {
// 建立应用程式需要的表格
// 待会再回来完成它
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 删除原有的表格
// 待会再回来完成它
// 呼叫onCreate建立新版的表格
onCreate(db);
}
}
~~~
## 11-3 数据库功能类别
在Android应用程式中使用数据库功能通常会有一种状况,就是Activity或其它元件的程式码,会因为加入处理数据库的工作,程式码变得又多、又复杂。一般程式设计的概念,一个元件中的程式码如果很多的话,在撰写或修改的时候,都会比较容易出错。所以这里说明的作法,会采用在一般应用程式中执行数据库工作的设计方式,把执行数据库工作的部份写在一个独立的Java类别中。
接下来设计应用程式需要的数据库功能类别,提供应用程式与数据库相关功能。在“net.macdidi.myandroidtutorial”套件按鼠标右键,选择“New -> Java CLass”,在Name输入“ItemDAO”后选择“OK”。参考下列的内容先完成部份的程式码:
~~~
package net.macdidi.myandroidtutorial;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
// 资料功能类别
public class ItemDAO {
// 表格名称
public static final String TABLE_NAME = "item";
// 编号表格字段名称,固定不变
public static final String KEY_ID = "_id";
// 其它表格字段名称
public static final String DATETIME_COLUMN = "datetime";
public static final String COLOR_COLUMN = "color";
public static final String TITLE_COLUMN = "title";
public static final String CONTENT_COLUMN = "content";
public static final String FILENAME_COLUMN = "filename";
public static final String LATITUDE_COLUMN = "latitude";
public static final String LONGITUDE_COLUMN = "longitude";
public static final String LASTMODIFY_COLUMN = "lastmodify";
// 使用上面宣告的变量建立表格的SQL指令
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME + " (" +
KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
DATETIME_COLUMN + " INTEGER NOT NULL, " +
COLOR_COLUMN + " INTEGER NOT NULL, " +
TITLE_COLUMN + " TEXT NOT NULL, " +
CONTENT_COLUMN + " TEXT NOT NULL, " +
FILENAME_COLUMN + " TEXT, " +
LATITUDE_COLUMN + " REAL, " +
LONGITUDE_COLUMN + " REAL, " +
LASTMODIFY_COLUMN + " INTEGER)";
// 数据库物件
private SQLiteDatabase db;
// 建构子,一般的应用都不需要修改
public ItemDAO(Context context) {
db = MyDBHelper.getDatabase(context);
}
// 关闭数据库,一般的应用都不需要修改
public void close() {
db.close();
}
// 新增参数指定的物件
public Item insert(Item item) {
// 建立准备新增资料的ContentValues物件
ContentValues cv = new ContentValues();
// 加入ContentValues物件包装的新增资料
// 第一个参数是字段名称, 第二个参数是字段的资料
cv.put(DATETIME_COLUMN, item.getDatetime());
cv.put(COLOR_COLUMN, item.getColor().parseColor());
cv.put(TITLE_COLUMN, item.getTitle());
cv.put(CONTENT_COLUMN, item.getContent());
cv.put(FILENAME_COLUMN, item.getFileName());
cv.put(LATITUDE_COLUMN, item.getLatitude());
cv.put(LONGITUDE_COLUMN, item.getLongitude());
cv.put(LASTMODIFY_COLUMN, item.getLastModify());
// 新增一笔资料并取得编号
// 第一个参数是表格名称
// 第二个参数是没有指定字段值的默认值
// 第三个参数是包装新增资料的ContentValues物件
long id = db.insert(TABLE_NAME, null, cv);
// 设定编号
item.setId(id);
// 回传结果
return item;
}
// 修改参数指定的物件
public boolean update(Item item) {
// 建立准备修改资料的ContentValues物件
ContentValues cv = new ContentValues();
// 加入ContentValues物件包装的修改资料
// 第一个参数是字段名称, 第二个参数是字段的资料
cv.put(DATETIME_COLUMN, item.getDatetime());
cv.put(COLOR_COLUMN, item.getColor().parseColor());
cv.put(TITLE_COLUMN, item.getTitle());
cv.put(CONTENT_COLUMN, item.getContent());
cv.put(FILENAME_COLUMN, item.getFileName());
cv.put(LATITUDE_COLUMN, item.getLatitude());
cv.put(LONGITUDE_COLUMN, item.getLongitude());
cv.put(LASTMODIFY_COLUMN, item.getLastModify());
// 设定修改资料的条件为编号
// 格式为“字段名称=资料”
String where = KEY_ID + "=" + item.getId();
// 执行修改资料并回传修改的资料数量是否成功
return db.update(TABLE_NAME, cv, where, null) > 0;
}
// 删除参数指定编号的资料
public boolean delete(long id){
// 设定条件为编号,格式为“字段名称=资料”
String where = KEY_ID + "=" + id;
// 删除指定编号资料并回传删除是否成功
return db.delete(TABLE_NAME, where , null) > 0;
}
// 读取所有记事资料
public List getAll() {
List result = new ArrayList<>();
Cursor cursor = db.query(
TABLE_NAME, null, null, null, null, null, null, null);
while (cursor.moveToNext()) {
result.add(getRecord(cursor));
}
cursor.close();
return result;
}
// 取得指定编号的资料物件
public Item get(long id) {
// 准备回传结果用的物件
Item item = null;
// 使用编号为查询条件
String where = KEY_ID + "=" + id;
// 执行查询
Cursor result = db.query(
TABLE_NAME, null, where, null, null, null, null, null);
// 如果有查询结果
if (result.moveToFirst()) {
// 读取包装一笔资料的物件
item = getRecord(result);
}
// 关闭Cursor物件
result.close();
// 回传结果
return item;
}
// 把Cursor目前的资料包装为物件
public Item getRecord(Cursor cursor) {
// 准备回传结果用的物件
Item result = new Item();
result.setId(cursor.getLong(0));
result.setDatetime(cursor.getLong(1));
result.setColor(ItemActivity.getColors(cursor.getInt(2)));
result.setTitle(cursor.getString(3));
result.setContent(cursor.getString(4));
result.setFileName(cursor.getString(5));
result.setLatitude(cursor.getDouble(6));
result.setLongitude(cursor.getDouble(7));
result.setLastModify(cursor.getLong(8));
// 回传结果
return result;
}
// 取得资料数量
public int getCount() {
int result = 0;
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM " + TABLE_NAME, null);
if (cursor.moveToNext()) {
result = cursor.getInt(0);
}
return result;
}
// 建立范例资料
public void sample() {
Item item = new Item(0, new Date().getTime(), Colors.RED, "关于Android Tutorial的事情.", "Hello content", "", 0, 0, 0);
Item item2 = new Item(0, new Date().getTime(), Colors.BLUE, "一只非常可爱的小狗狗!", "她的名字叫“大热狗”,又叫\n作“奶嘴”,是一只非常可爱\n的小狗。", "", 25.04719, 121.516981, 0);
Item item3 = new Item(0, new Date().getTime(), Colors.GREEN, "一首非常好听的音乐!", "Hello content", "", 0, 0, 0);
Item item4 = new Item(0, new Date().getTime(), Colors.ORANGE, "储存在数据库的资料", "Hello content", "", 0, 0, 0);
insert(item);
insert(item2);
insert(item3);
insert(item4);
}
}
~~~
完成数据库功能类别以后,里面也宣告了一些SQLiteOpenHelper类别会使用到的资料,开启“MyDBHelper”类别,完成之前还没有完成的工作:
~~~
@Override
public void onCreate(SQLiteDatabase db) {
// 建立应用程式需要的表格
db.execSQL(ItemDAO.CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 删除原有的表格
db.execSQL("DROP TABLE IF EXISTS " + ItemDAO.TABLE_NAME);
// 呼叫onCreate建立新版的表格
onCreate(db);
}
~~~
## 11-4 使用数据库中的记事资料
完成与数据库相关的类别以后,其它的部份就简单多了,Activity元件也可以保持比较简洁的程式架构。开启在“net.macdidi.myandroidtutorial”套件下的“MainActivity”类别,修改原来自己建立资料的作法,改由数据库提供记事资料并显示在画面。由于所有执行数据库工作的程式码都写在“ItemDAO”类别,所以要宣告一个ItemDAO的字段变量,“onCreate”方法也要执行相关的修改:
~~~
// 宣告数据库功能类别字段变量
private ItemDAO itemDAO;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
processViews();
processControllers();
// 建立数据库物件
itemDAO = new ItemDAO(getApplicationContext());
// 如果数据库是空的,就建立一些范例资料
// 这是为了方便测试用的,完成应用程式以后可以拿掉
if (itemDAO.getCount() == 0) {
itemDAO.sample();
}
// 取得所有记事资料
items = itemDAO.getAll();
itemAdapter = new ItemAdapter(this, R.layout.single_item, items);
item_list.setAdapter(itemAdapter);
}
~~~
完成这个部份的修改以后,执行应用程式,如果画面上显示像这样的画面,数据库的部份应该就没有问题了。
[![AndroidTutorial5_03_03_01](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_03_01-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_03_01.png)
接下来需要处理新增与修改的部份,同样在“MainActivity”类别,找到“onActivityResult”方法,参考下列的内容修改程式码:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
Item item = (Item) data.getExtras().getSerializable(
"net.macdidi.myandroidtutorial.Item");
if (requestCode == 0) {
// 新增记事资料到数据库
item = itemDAO.insert(item);
items.add(item);
itemAdapter.notifyDataSetChanged();
}
else if (requestCode == 1) {
int position = data.getIntExtra("position", -1);
if (position != -1) {
// 修改数据库中的记事资料
itemDAO.update(item);
items.set(position, item);
itemAdapter.notifyDataSetChanged();
}
}
}
}
~~~
最后是删除记事资料的部份,同样在“MainActivity”类别,找到“clickMenuItem”方法,参考下列的内容修改程式码:
~~~
public void clickMenuItem(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
...
case R.id.delete_item:
if (selectedCount == 0) {
break;
}
AlertDialog.Builder d = new AlertDialog.Builder(this);
String message = getString(R.string.delete_item);
d.setTitle(R.string.delete)
.setMessage(String.format(message, selectedCount));
d.setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 取得最后一个元素的编号
int index = itemAdapter.getCount() - 1;
while (index > -1) {
Item item = itemAdapter.get(index);
if (item.isSelected()) {
itemAdapter.remove(item);
// 删除数据库中的记事资料
itemDAO.delete(item.getId());
}
index--;
}
itemAdapter.notifyDataSetChanged();
}
});
d.setNegativeButton(android.R.string.no, null);
d.show();
break;
case R.id.googleplus_item:
break;
case R.id.facebook_item:
break;
}
}
~~~
完成这一章所有的工作了,执行应用程式,试试看新增、修改和删除记事资料的功能。因为记事资料都保存在数据库,完成测试以后,关闭应用程式再重新启动,记事资料还是会显示在画面。
第三堂(2)储存与读取应用程式资讯
最后更新于:2022-04-01 00:52:59
应用程式在运作的时候,可能需要储存与读取一些简单的资料,另外可能也需
要提供一个画面让使用者设定一些应用程式需要的资讯。例如一个游戏应用程
式,需要在使用者完成一个关卡后,储存分数或花费的时间。还有提供游戏效
果的设定画面,让使用者设定是否需要背景音乐、音效和震动的效果。应用程
式可以读取这些设定的资料,用来控制游戏进行的时候,是否需要执行这些效
果。
Android 系统提供一种“Preference”的架构,它可以在应用程式中储存一些“名称=值”这类简单的资料,这些资料可以用来储存应用程式的状态,或是储存使用者执行的设定。这些资料在应用程式中执行储存与读取的工作都非常容易,如果有这类的需求,使用它来处理是最方便的。
这一章介绍Android提供的设计元件“PreferenceActivity”,使用它提供的设计方式,可以简化设定元件。完成这一章的工作以后,使用者可以在应用程式的主画面选择设定功能项目:
[![AndroidTutorial5_03_02_01](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_01-189x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_01.png)
启动设定元件以后,使用者可以选择设定默认颜色或提醒时间:
[![AndroidTutorial5_03_02_02](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_02-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_02.png)
使用者选择设定默认颜色的项目,应用程式启动之前已经设计好的选择颜色元件:
[![AndroidTutorial5_03_02_03](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_03-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_03.png)
使用者选择设定默认提醒时间的项目,应用程式开启选择时间的对话框:
[![AndroidTutorial5_03_02_04](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_04-189x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_04.png)
## 10-1 系统Preference设计架构介绍
一般的应用程式,通常需要提供使用者执行设定的功能,这样可以让应用程式
比较方便一些,不会是固定的画面或操作的行为。Android 为应用程式提供一个专门用来设计应用程式设定功能的元件,它是一个比较特别的Activity 元件,规划与设计的作法也很不一样。要使用这种比较方便而且容易的设定元件,需要设计这些设定档与元件:
* 设定画面配置档 – 这种设定元件有它特有的画面配置档,也是一个XML 格
式的档案,放在专案的“res/xml”目录下,使用特别的标签设计画面。
* PreferenceActivity元件 – 设定元件专用的Activity元件,让你的元件类别继承自这个类别。
设定元件专用的画面配置档放在专案的“res/xml”目录下,这个设定档的最外层使用“PreferenceScreen”标签,根据应用程式需要的设定资料,在这里标签中加入这些需要的设定元件标签:
* EditTextPreference – 使用对话框让使用者输入文字资料。
* CheckBoxPreference – 勾选元件,储存boolean 资料。
* SwitchPreference – 在Android 4.0(API level 14)加入,提供开关式的元
件,储存boolean 资料。
* ListPreference – 使用对话框让使用者在列表中选择一个项目,储存字串资料。
* MultiSelectListPreference – 在Android 3.0(API level 11)加入,使用对话框让使用者在列表中选择多个项目,储存Set 资料。
* RingtonePreference – 开启系统内建选择来电铃声的对话框让使用者选择,储存文字资料。
* PreferenceCategory – 用来执行设定资料的分组。
* Preference – 启动其它元件执行设定的工作。
## 10-2 设计设定元件使用的画面配置档
你的应用程式可以依照需要保存的资讯,设计好设定画面让使用者使用,设定画面使用一组特定的元件标签。这个记事应用程式除了说明一个一般的元件外,也会说明启动元件的作法,其它的设定元件就会比较简单一些,你可以依照自己的需求加入与测试其它设定元件。
设定元件需要使用一些文字资源,开启“res/values/strings.xml”,加入下列的文字资源:
~~~
<string name="default_color">默认的颜色</string>
<string name="default_color_summary">新增记事的默认颜色</string>
<string name="default_notify">默认提醒时间</string>
<string name="default_notify_summary">在指定的时间之前通知</string>
<string-array name="notify_minutes_array">
<item>五分钟</item>
<item>十分钟</item>
<item>二十分钟</item>
<item>三十分钟</item>
<item>六十分钟</item>
</string-array>
<string-array name="notify_minutes_value_array">
<item>5</item>
<item>10</item>
<item>20</item>
<item>30</item>
<item>60</item>
</string-array>
~~~
设定画面配置档必须放在专案的“res/xml”目录下,如果专案中还没有这个目录,在“res”目录按鼠标右键,选择“New -> Directory”,输入“xml”以后选择“OK”。在“res/xml”目录按鼠标右键,选择“New -> XML resource file”,在File name输入“mypreference”以后选择“OK”。把“mypreference.xml”修改为下列的内容:
~~~
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 默认颜色 -->
<!-- android:key 设定资料名称 -->
<!-- android:title 设定画面上显示的标题 -->
<!-- android:summary 设定画面上显示的说明 -->
<Preference
android:key="DEFAULT_COLOR"
android:title="@string/default_color"
android:summary="@string/default_color_summary">
</Preference>
<!-- 默认提醒时间 -->
<!-- android:entries 设定画面显示选项内容的阵列资源 -->
<!-- android:entriyValues 设定储存选项资料的阵列资源 -->
<!-- android:defaultValue 设定选项默认项目编号 -->
<ListPreference
android:key="NOTIFY_MINUTES"
android:title="@string/default_notify"
android:summary="@string/default_notify_summary"
android:entries="@array/notify_minutes_array"
android:entryValues="@array/notify_minutes_value_array"
android:defaultValue="5" />
</PreferenceScreen>
~~~
这个档案在设定画面中提供两个设定用的项目,一个用来设定新增记事的默认颜色,还有设定提醒的默认时间,这一章会先使用默认颜色的设定值,提醒的默认时间在后面才会用到。
## 10-2 设计设定元件
设定元件是一个比较特殊的Activity元件,它是继承自“PreferenceActivity”的子类别,最基本的作法只要在覆写的onCreate 方法中,呼叫“addPreferencesFromResource”方法指定元件使用的设定画面资源就可以了。在“net.macdidi.myandroidtutorial”目录按鼠标右键,选择“New -> Java Class”,在Name输入“PrefActivity”以后选择“OK”。把PrefActivity.java改为下列的内容:
~~~
package net.macdidi.myandroidtutorial;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class PrefActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 指定使用的设定画面配置资源
// 这行叙述从API Level 11开始会产生警告讯息
// 不过不会影响应用程式的运作
addPreferencesFromResource(R.xml.mypreference);
}
}
~~~
跟其它的Activity元件一样,设定元件同样需要在“AndroidManifest.xml”档案中加入必要的设定:
~~~
<!-- 设定元件 -->
<activity
android:name="net.macdidi.myandroidtutorial.PrefActivity" />
~~~
完成设定元件的设计以后,开启“res/menu/main_menu.xml”档案,为功能表加入启动设定元件的项目:
~~~
<!-- 设定 -->
<item
android:title="Setting"
app:showAsAction="always"
android:icon="@android:drawable/ic_menu_preferences"
android:onClick="clickPreferences" />
~~~
开启“net.macdidi.myandroidtutorial”套件下的“MainActivity”类别,加入启动设定元件的方法宣告:
~~~
// 设定
public void clickPreferences(MenuItem item) {
// 启动设定元件
startActivity(new Intent(this, PrefActivity.class));
}
~~~
完成这个阶段的工作以后,可以先执行应用程式,选择主功能表上的设定图示,看看可不可以正确的启动设定元件。
## 10-3 在设计设定元件中启动其它元件
选择记事分类颜色的元件在之前已经设计好了,所以需要让使用者选择默认颜色最好的作法,应该是使用原来设计好的元件,再稍微修改就可以了。选择记事分类颜色元件在这里需让设定元件使用,所以在“AndroidManifest.xml”档案修改它的设定:
~~~
<!-- 选择颜色 -->
<activity
android:name="net.macdidi.myandroidtutorial.ColorActivity"
android:theme="@android:style/Theme.Dialog"
android:label="@string/title_activity_color">
<!-- 加入设定元件启动用的Action名称 -->
<intent-filter>
<action android:name="net.macdidi.myandroidtutorial.CHOOSE_COLOR"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
~~~
开启“res/xml/mypreference.xml,加入启动选择颜色元件的设定:
~~~
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<Preference
android:key="DEFAULT_COLOR"
android:title="@string/default_color"
android:summary="@string/default_color_summary">
<!-- 启动选择颜色元件 -->
<intent
android:action="net.macdidi.myandroidtutorial.CHOOSE_COLOR"
android:targetPackage="net.macdidi.myandroidtutorial"
android:targetClass="net.macdidi.myandroidtutorial.ColorActivity"/>
</Preference>
...
</PreferenceScreen>
~~~
开启在“net.macdidi.myandroidtutorial”套件下的“ColorActivity”类别,找到“ColorListener”监听类别,依照下列的说明修改原来的程式码:
~~~
private class ColorListener implements OnClickListener {
@Override
public void onClick(View view) {
String action = ColorActivity.this.getIntent().getAction();
// 经由设定元件启动
if (action != null &&
action.equals("net.macdidi.myandroidtutorial.CHOOSE_COLOR")) {
// 建立SharedPreferences物件
SharedPreferences.Editor editor =
PreferenceManager.getDefaultSharedPreferences(
ColorActivity.this).edit();
// 储存默认颜色
editor.putInt("DEFAULT_COLOR", view.getId());
// 写入设定值
editor.commit();
finish();
}
// 经由新增或修改记事的元件启动
else {
Intent result = getIntent();
result.putExtra("colorId", view.getId());
setResult(Activity.RESULT_OK, result);
finish();
}
}
}
~~~
为了接下来设计读取颜色设定的功能,开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,找到“getColors”方法,参考下列的说明修改这个方法的宣告:
~~~
// 改为public static
public static Colors getColors(int color) {
...
}
~~~
使用者设定默认的颜色以后,通常会希望在设定元件的画面看到设定的结果,所以回到“PrefActivity”类别,依照下列的说明加入需要的程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
public class PrefActivity extends PreferenceActivity {
// 加入字段变量宣告
private SharedPreferences sharedPreferences;
private Preference defaultColor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.mypreference);
// 读取颜色设定元件
defaultColor = (Preference)findPreference("DEFAULT_COLOR");
// 建立SharedPreferences物件
sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this);
}
@Override
protected void onResume() {
super.onResume();
// 读取设定的默认颜色
int color = sharedPreferences.getInt("DEFAULT_COLOR", -1);
if (color != -1) {
// 设定颜色说明
defaultColor.setSummary(getString(R.string.default_color_summary) +
": " + ItemActivity.getColors(color));
}
}
}
~~~
完成这个阶段的工作以后,执行应用程式,选择设定默认颜色的项目,看看可不可以正确的启动选择颜色元件。选择颜色并回到设定元件以后,默认颜色设定项目的说明也会显示设定颜色的名称。
## 10-4 使用储存的设定值
完成设定元件与相关的设计后,使用者在新增的记事资料的时候,如果没有为它设定颜色,就应该采用已经在设定元件设定好的默认颜色。开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity”类别,找到“onSubmit”方法,依照下列的说明执行需要的修改:
~~~
public void onSubmit(View view) {
if (view.getId() == R.id.ok_teim) {
String titleText = title_text.getText().toString();
String contentText = content_text.getText().toString();
item.setTitle(titleText);
item.setContent(contentText);
if (getIntent().getAction().equals(
"net.macdidi.myandroidtutorial.EDIT_ITEM")) {
item.setLastModify(new Date().getTime());
}
// 新增记事
else {
item.setDatetime(new Date().getTime());
// 建立SharedPreferences物件
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this);
// 读取设定的默认颜色
int color = sharedPreferences.getInt("DEFAULT_COLOR", -1);
item.setColor(getColors(color));
}
Intent result = getIntent();
result.putExtra("net.macdidi.myandroidtutorial.Item", item);
setResult(Activity.RESULT_OK, result);
}
// 结束
finish();
}
~~~
完成这一章所有的工作了,执行应用程式,新增一个记事资料,看看会不会设定为默认的颜色。
[![AndroidTutorial5_03_02_05](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_05-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_02_05.png)
第三堂(1)为ListView元件建立自定画面
最后更新于:2022-04-01 00:52:57
ListView是Android应用程式很常使用的画面元件,它可以显示多笔资料项目让使用者浏览、选择与执行后续的操作。目前完成的记事应用程式,只有把简单的文字资料设定给ListView元件使用,其实这个元件有非常多不同的用途,它可以显示比较复杂的资料项目,让使用者勾选和执行后续的功能。
这一章会加强ListView元件的使用,为它设计专用的画面,让一个项目可以显示比较多的资料:
[![AndroidTutorial5_03_01_01](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_01-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_01.png)
为了让记事资料可以清楚的分类,所以在新增与修改记事加入设定颜色的功能:
[![AndroidTutorial5_03_01_02](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_02-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_02.png) [![AndroidTutorial5_03_01_03](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_03-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_03.png)
在浏览记事资料的主画面,提供使用者勾选项目的功能,在未选择与已选择项目的状态,需要显示不同的功能表项目:
[![AndroidTutorial5_03_01_04](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_04-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_04.png) [![AndroidTutorial5_03_01_05](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_05-190x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_05.png)
如果使用者选择记事项目,为应用程式加入删除记事的功能:
[![AndroidTutorial5_03_01_06](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_06-182x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_01_06.png)
## 9-1 记事资料的封装
不论是开发一般Java或Android应用程式,应用程式的功能越写越多,程式码也会更复杂,一般的物件封装作法可以让程式码比较简洁一些。这个应用程式需要管理所有的记事资料,所以应该为应用程式新增一个封装记事资料的类别。因为希望可以为每一个记事资料加入颜色设定的功能,所以先建立一个封装颜色资料的类别。在“net.macdidi.myandroidtutorial”套件上按鼠标右键,选择“New -> Java Class”,在Create New Class对话框的Name输入“Colors”,Kind选择“Enum”后选择“OK”。参考下面的内容完成这个程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.graphics.Color;
public enum Colors {
LIGHTGREY("#D3D3D3"), BLUE("#33B5E5"), PURPLE("#AA66CC"),
GREEN("#99CC00"), ORANGE("#FFBB33"), RED("#FF4444");
private String code;
private Colors(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public int parseColor() {
return Color.parseColor(code);
}
}
~~~
在“net.macdidi.myandroidtutorial”套件上按鼠标右键,选择“New -> Java Class”,在Create New Class对话框的Name输入“Item”后选择“OK”。参考下面的内容完成这个程式码:
~~~
package net.macdidi.myandroidtutorial;
import java.util.Date;
import java.util.Locale;
public class Item implements java.io.Serializable {
// 编号、日期时间、颜色、标题、内容、档案名称、经纬度、修改、已选择
private long id;
private long datetime;
private Colors color;
private String title;
private String content;
private String fileName;
private double latitude;
private double longitude;
private long lastModify;
private boolean selected;
public Item() {
title = "";
content = "";
color = Colors.LIGHTGREY;
}
public Item(long id, long datetime, Colors color, String title,
String content, String fileName, double latitude, double longitude,
long lastModify) {
this.id = id;
this.datetime = datetime;
this.color = color;
this.title = title;
this.content = content;
this.fileName = fileName;
this.latitude = latitude;
this.longitude = longitude;
this.lastModify = lastModify;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getDatetime() {
return datetime;
}
// 装置区域的日期时间
public String getLocaleDatetime() {
return String.format(Locale.getDefault(), "%tF %<tR", new Date(datetime));
}
// 装置区域的日期
public String getLocaleDate() {
return String.format(Locale.getDefault(), "%tF", new Date(datetime));
}
// 装置区域的时间
public String getLocaleTime() {
return String.format(Locale.getDefault(), "%tR", new Date(datetime));
}
public void setDatetime(long datetime) {
this.datetime = datetime;
}
public Colors getColor() {
return color;
}
public void setColor(Colors color) {
this.color = color;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public long getLastModify() {
return lastModify;
}
public void setLastModify(long lastModify) {
this.lastModify = lastModify;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
~~~
为了让记事资料项目可以使用不同的颜色分类,所以新增一个绘图资源。在“res/drawable”目录上按鼠标右键,选择“New -> Drawable resource file”。在“File name”输入“item_drawable”后选择“OK”。参考下面的内容完成这个绘图资源:
~~~
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<corners
android:topLeftRadius="20sp"
android:topRightRadius="20sp"
android:bottomLeftRadius="20sp"
android:bottomRightRadius="20sp" />
<solid android:color="#AAAAAA"/>
</shape>
~~~
为了让ListView元件的每一个项目可以显示比较多的资料,你可以为项目建立一个画面配置档。这个画面配置档需要使用一个额外的图示(selected_icon.png),用来显示使用者已经选择一个项目,你可以在GitHub这一章的范例程式专案找到这个图档,把它复制到“res/drawable”目录。现在准备新增一个给ListView元件项目使用的画面资源,在“res/layout”目录上按鼠标右键,选择“New -> Layout resource file”。在“File name”输入“single_item”后选择“OK”。参考下面的内容完成这个画面资源:
~~~
<?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" >
<!-- 颜色分类 -->
<RelativeLayout
android:id="@+id/type_color"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="3sp"
android:background="@drawable/item_drawable" >
<!-- 勾选 -->
<ImageView
android:id="@+id/selected_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:src="@drawable/selected_icon"
android:visibility="invisible" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="3sp"
android:gravity="center_vertical"
android:orientation="vertical" >
<!-- 标题 -->
<TextView
android:id="@+id/title_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_vertical" />
<!-- 日期时间 -->
<TextView
android:id="@+id/date_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_vertical" />
</LinearLayout>
</LinearLayout>
~~~
需要在ListView元件中显示比较复杂的画面,就不能使用一般的Adapter物件,你可以依照自己的需求,撰写一个自定的Adapter类别给ListView元件使用。在“net.macdidi.myandroidtutorial”套件上按鼠标右键,选择“New -> Java Class”,在Create New Class对话框的Name输入“ItemAdapter”后选择“OK”。参考下面的内容完成这个程式码:
~~~
package net.macdidi.myandroidtutorial;
import java.util.List;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class ItemAdapter extends ArrayAdapter {
// 画面资源编号
private int resource;
// 包装的记事资料
private List items;
public ItemAdapter(Context context, int resource, List items) {
super(context, resource, items);
this.resource = resource;
this.items = items;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout itemView;
// 读取目前位置的记事物件
final Item item = getItem(position);
if (convertView == null) {
// 建立项目画面元件
itemView = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater)
getContext().getSystemService(inflater);
li.inflate(resource, itemView, true);
}
else {
itemView = (LinearLayout) convertView;
}
// 读取记事颜色、已选择、标题与日期时间元件
RelativeLayout typeColor = (RelativeLayout) itemView.findViewById(R.id.type_color);
ImageView selectedItem = (ImageView) itemView.findViewById(R.id.selected_item);
TextView titleView = (TextView) itemView.findViewById(R.id.title_text);
TextView dateView = (TextView) itemView.findViewById(R.id.date_text);
// 设定记事颜色
GradientDrawable background = (GradientDrawable)typeColor.getBackground();
background.setColor(item.getColor().parseColor());
// 设定标题与日期时间
titleView.setText(item.getTitle());
dateView.setText(item.getLocaleDatetime());
// 设定是否已选择
selectedItem.setVisibility(item.isSelected() ? View.VISIBLE : View.INVISIBLE);
return itemView;
}
// 设定指定编号的记事资料
public void set(int index, Item item) {
if (index >= 0 && index < items.size()) {
items.set(index, item);
notifyDataSetChanged();
}
}
// 读取指定编号的记事资料
public Item get(int index) {
return items.get(index);
}
}
~~~
完成这些程式码与画面配置档以后,就完成基本的准备工作了。
## 9-2 使用自定画面的ListView元件
为了让ListView元件使用已经准备好的程式码与资源,之前已经写好的主画面元件,就要执行比较大幅度的修改。开启“net.macdidi.myandroidtutorial”套件下的“MainActivity.java”,修改字段变量的宣告:
~~~
private ListView item_list;
private TextView show_app_name;
// 删除原来的宣告
//private ArrayList data = new ArrayList<>();
//private ArrayAdapter adapter;
// ListView使用的自定Adapter物件
private ItemAdapter itemAdapter;
// 储存所有记事本的List物件
private List items;
// 选单项目物件
private MenuItem add_item, search_item, revert_item, share_item, delete_item;
// 已选择项目数量
private int selectedCount = 0;
~~~
同样在“MainActivity.java”,参考下列的说明,修改“onCreate”方法的程式码:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
processViews();
processControllers();
// 删除原来的程式码
//data.add("关于Android Tutorial的事情");
//data.add("一只非常可爱的小狗狗!");
//data.add("一首非常好听的音乐!");
//int layoutId = android.R.layout.simple_list_item_1;
//adapter = new ArrayAdapter(this, layoutId, data);
//item_list.setAdapter(adapter);
// 加入范例资料
items = new ArrayList();
items.add(new Item(1, new Date().getTime(), Colors.RED, "关于Android Tutorial的事情.", "Hello content", "", 0, 0, 0));
items.add(new Item(2, new Date().getTime(), Colors.BLUE, "一只非常可爱的小狗狗!", "她的名字叫“大热狗”,又叫\n作“奶嘴”,是一只非常可爱\n的小狗。", "", 0, 0, 0));
items.add(new Item(3, new Date().getTime(), Colors.GREEN, "一首非常好听的音乐!", "Hello content", "", 0, 0, 0));
// 建立自定Adapter物件
itemAdapter = new ItemAdapter(this, R.layout.single_item, items);
item_list.setAdapter(itemAdapter);
}
~~~
执行上面的修改以后,会发现这个程式码出现一些错误,这些错误会在“onActivityResult”与“processControllers”这两个方法里面,你可以参考下列的作法,先把这两的方法的所有程式码加上注解,后面再慢慢修改它们:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
/*
...
*/
}
private void processControllers() {
/*
...
*/
}
~~~
使用上面介绍的方法处理程式码以后,错误的情况就会消失了,先执行这个应用程式,看看是否可以正常的显示应用程式画面。
## 9-3 新增记事的资料传送与接收
改用目前的方式处理记事资料以后,新增记事的作法就要执行一些必要的修改。开启“net.macdidi.myandroidtutorial”套件下的“ItemActivity.java”,加入这些新的字段变量宣告:
~~~
// 启动功能用的请求代码
private static final int START_CAMERA = 0;
private static final int START_RECORD = 1;
private static final int START_LOCATION = 2;
private static final int START_ALARM = 3;
private static final int START_COLOR = 4;
// 记事物件
private Item item;
~~~
同样在“ItemActivity.java”,参考下列的说明,修改“onCreate”方法的程式码:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item);
processViews();
// 取得Intent物件
Intent intent = getIntent();
// 读取Action名称
String action = intent.getAction();
// 如果是修改记事
if (action.equals("net.macdidi.myandroidtutorial.EDIT_ITEM")) {
// 接收与设定记事标题
String titleText = intent.getStringExtra("titleText");
title_text.setText(titleText);
}
// 新增记事
else {
item = new Item();
}
}
~~~
同样在“ItemActivity.java”,参考下列的说明,修改“onSubmit”方法的程式码,调整确认新增记事以后要执行的工作:
~~~
// 点击确定与取消按钮都会呼叫这个方法
public void onSubmit(View view) {
// 确定按钮
if (view.getId() == R.id.ok_teim) {
// 读取使用者输入的标题与内容
String titleText = title_text.getText().toString();
String contentText = content_text.getText().toString();
// 设定记事物件的标题与内容
item.setTitle(titleText);
item.setContent(contentText);
// 如果是修改记事
if (getIntent().getAction().equals(
"net.macdidi.myandroidtutorial.EDIT_ITEM")) {
item.setLastModify(new Date().getTime());
}
// 新增记事
else {
item.setDatetime(new Date().getTime());
}
Intent result = getIntent();
// 设定回传的记事物件
result.putExtra("net.macdidi.myandroidtutorial.Item", item);
setResult(Activity.RESULT_OK, result);
}
// 结束
finish();
}
~~~
回到“net.macdidi.myandroidtutorial”套件下的“MainActivity.java”,找到“onActivityResult”方法,移除之前加入的注解,参考下列的程式码修改新增记事后需要处理的工作。因为修改记事的部份还没有完成,所以先把它们设定为注解。
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 如果被启动的Activity元件传回确定的结果
if (resultCode == Activity.RESULT_OK) {
// 读取记事物件
Item item = (Item) data.getExtras().getSerializable(
"net.macdidi.myandroidtutorial.Item");
// 如果是新增记事
if (requestCode == 0) {
// 设定记事物件的编号与日期时间
item.setId(items.size() + 1);
item.setDatetime(new Date().getTime());
// 加入新增的记事物件
items.add(item);
// 通知资料改变
itemAdapter.notifyDataSetChanged();
}
/*
// 如果是修改记事
else if (requestCode == 1) {
...
}
*/
}
}
~~~
完成上面的工作以后,执行这个应用程式,测试新增记式资料的功能是否正确。
## 9-4 修改记事的资料传送与接收
完成新增记事功能以后,接下来处理工作比较多一些的修改记事功能。开启“net.macdidi.myandroidtutorial”套件下的“MainActivity.java”,找到“processControllers”方法,移除之前加入的注解。在这个方法中找到处理ListView项目长按事件的程式码,先把它们设定为注解:
~~~
/*
// 建立选单项目长按监听物件
OnItemLongClickListener itemLongListener = new OnItemLongClickListener() {
...
}
};
// 注册选单项目长按监听物件
item_list.setOnItemLongClickListener(itemLongListener);
*/
~~~
接下来参考下列的程式码,修改处理ListView项目点击事件的程式码:
~~~
// 建立选单项目点击监听物件
AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view,
int position, long id) {
// 读取选择的记事物件
Item item = itemAdapter.getItem(position);
Intent intent = new Intent(
"net.macdidi.myandroidtutorial.EDIT_ITEM");
// 设定记事编号与记事物件
intent.putExtra("position", position);
intent.putExtra("net.macdidi.myandroidtutorial.Item", item);
startActivityForResult(intent, 1);
}
};
// 注册选单项目点击监听物件
item_list.setOnItemClickListener(itemListener);
~~~
你可以注意到在点击一个记事项目以后,传送的资料已经修改为Item物件,所以修改记事元件也要执行对应的调整。开启“net.macdidi.myandroidtutorial”套件下的“ItemActivity.java”,修改“onCreate”方法里面的程式码:
~~~
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item);
processViews();
// 取得Intent物件
Intent intent = getIntent();
// 读取Action名称
String action = intent.getAction();
// 如果是修改记事
if (action.equals("net.macdidi.myandroidtutorial.EDIT_ITEM")) {
// 接收记事物件与设定标题、内容
item = (Item) intent.getExtras().getSerializable(
"net.macdidi.myandroidtutorial.Item");
title_text.setText(item.getTitle());
content_text.setText(item.getContent());
}
// 新增记事
else {
item = new Item();
}
}
~~~
修改记事元件在使用者确认内容以后,回到主画面元件处理修改后的工作。开启“net.macdidi.myandroidtutorial”套件下的“MainActivity.java”,修改“onActivityResult”方法里面的程式码:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 如果被启动的Activity元件传回确定的结果
if (resultCode == Activity.RESULT_OK) {
// 读取记事物件
Item item = (Item) data.getExtras().getSerializable(
"net.macdidi.myandroidtutorial.Item");
// 如果是新增记事
if (requestCode == 0) {
...
}
// 如果是修改记事
else if (requestCode == 1) {
// 读取记事编号
int position = data.getIntExtra("position", -1);
if (position != -1) {
// 设定修改的记事物件
items.set(position, item);
itemAdapter.notifyDataSetChanged();
}
}
}
}
~~~
完成修改记事功能的调整工作,执行应用程式,点选一笔记事项目,修改内容并确定以后,看看功能是否正确。
## 9-5 设定记事颜色
像记事这类应用程式,使用一段时间以后,通常会储存很多资料,为了让使用者可以清楚的分类与查询这些记事资料,所以为应用程式加入颜色分类的功能。使用者在新增或修改记事资料的时候,可以依照自己的需求为它设定一个颜色,为设定颜色的功能设计一个Activity元件,元件的名称是“ColorActivity.java”,画面配置档的名称是“activity_color.xml”。在最顶端的“app”目录按鼠标左键,选择“New -> Activity -> Blank Activity”,元件与画面配置档名称依照上面的规划。建立元件以后,开启应用程式设定档“AndroidManifest.xml”,参考下列的内容,加入对话框样式的设定:
~~~
<activity
android:name="net.macdidi.myandroidtutorial.ColorActivity"
android:theme="@android:style/Theme.Dialog"
android:label="@string/title_activity_color" />
~~~
选择颜色功能的画面设计比较简单一些,开启在“res/layout”目录下的“activity_color.xml”,把它修改为下面的内容:
~~~
<HorizontalScrollView
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:padding="6sp"
android:spacing="3sp"
tools:context="net.macdidi.myandroidtutorial.ColorActivity">
<LinearLayout
android:id="@+id/color_gallery"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
</HorizontalScrollView>
~~~
开启在“net.macdidi.myandroidtutorial”套件下的“ColorActivity.java”,把它修改为下面的内容:
~~~
package net.macdidi.myandroidtutorial;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
public class ColorActivity extends Activity {
private LinearLayout color_gallery;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_color);
processViews();
ColorListener listener = new ColorListener();
for (Colors c : Colors.values()) {
Button button = new Button(this);
button.setId(c.parseColor());
LinearLayout.LayoutParams layout =
new LinearLayout.LayoutParams(128, 128);
layout.setMargins(6, 6, 6, 6);
button.setLayoutParams(layout);
button.setBackgroundColor(c.parseColor());
button.setOnClickListener(listener);
color_gallery.addView(button);
}
}
private void processViews() {
color_gallery = (LinearLayout) findViewById(R.id.color_gallery);
}
private class ColorListener implements OnClickListener {
@Override
public void onClick(View view) {
Intent result = getIntent();
result.putExtra("colorId", view.getId());
setResult(Activity.RESULT_OK, result);
finish();
}
}
}
~~~
完成准备工作以后,就可以回到记事元件加入需要的程式码。开启在“net.macdidi.myandroidtutorial”套件下的“ItemActivity.java”,参考下列的说明加入启动元件的程式码:
~~~
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
case R.id.take_picture:
break;
case R.id.record_sound:
break;
case R.id.set_location:
break;
case R.id.set_alarm:
break;
case R.id.select_color:
// 启动设定颜色的Activity元件
startActivityForResult(
new Intent(this, ColorActivity.class), START_COLOR);
break;
}
}
~~~
同样在ItemActivity.java,参考下列的程式码,执行选择颜色后的设定工作:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
case START_CAMERA:
break;
case START_RECORD:
break;
case START_LOCATION:
break;
case START_ALARM:
break;
// 设定颜色
case START_COLOR:
int colorId = data.getIntExtra(
"colorId", Colors.LIGHTGREY.parseColor());
item.setColor(getColors(colorId));
break;
}
}
}
private Colors getColors(int color) {
Colors result = Colors.LIGHTGREY;
if (color == Colors.BLUE.parseColor()) {
result = Colors.BLUE;
}
else if (color == Colors.PURPLE.parseColor()) {
result = Colors.PURPLE;
}
else if (color == Colors.GREEN.parseColor()) {
result = Colors.GREEN;
}
else if (color == Colors.ORANGE.parseColor()) {
result = Colors.ORANGE;
}
else if (color == Colors.RED.parseColor()) {
result = Colors.RED;
}
return result;
}
~~~
执行应用程式,在新增或修改记事资料的时候,执行设定颜色的测试。
## 9-6 选择记事资料与主功能表
这一章最后的工作是完成让使用者勾选记事资料、控制主功能表的显示与删除记事的功能。开启在“net.macdidi.myandroidtutorial”套件下的“MainActivity.java”,找到“processControllers”方法,修改记事项目长按事件的程式码,原来的点击事件也要执行相关的修改。因为在使用者勾选事件项目以后,主功能表就要根据选择的情况调整,所以也增加控制功能表显示的方法processMenu:
~~~
private void processControllers() {
// 建立选单项目点击监听物件
AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view,
int position, long id) {
// 读取选择的记事物件
Item item = itemAdapter.getItem(position);
// 如果已经有勾选的项目
if (selectedCount > 0) {
// 处理是否显示已选择项目
processMenu(item);
// 重新设定记事项目
itemAdapter.set(position, item);
}
else {
Intent intent = new Intent(
"net.macdidi.myandroidtutorial.EDIT_ITEM");
// 设定记事编号与记事物件
intent.putExtra("position", position);
intent.putExtra("net.macdidi.myandroidtutorial.Item", item);
startActivityForResult(intent, 1);
}
}
};
// 注册选单项目点击监听物件
item_list.setOnItemClickListener(itemListener);
// 建立记事项目长按监听物件
AdapterView.OnItemLongClickListener itemLongListener = new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView parent, View view,
int position, long id) {
// 读取选择的记事物件
Item item = itemAdapter.getItem(position);
// 处理是否显示已选择项目
processMenu(item);
// 重新设定记事项目
itemAdapter.set(position, item);
return true;
}
};
// 注册记事项目长按监听物件
item_list.setOnItemLongClickListener(itemLongListener);
...
}
// 处理是否显示已选择项目
private void processMenu(Item item) {
// 如果需要设定记事项目
if (item != null) {
// 设定已勾选的状态
item.setSelected(!item.isSelected());
// 计算已勾选数量
if (item.isSelected()) {
selectedCount++;
}
else {
selectedCount--;
}
}
// 根据选择的状况,设定是否显示选单项目
add_item.setVisible(selectedCount == 0);
search_item.setVisible(selectedCount == 0);
revert_item.setVisible(selectedCount > 0);
share_item.setVisible(selectedCount > 0);
delete_item.setVisible(selectedCount > 0);
}
同样在“MainActivity.java”,找到“onCreateOptionsMenu”方法,为了控制主功能表的显示,参考下列的程式码执行必要的修改:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu_main, menu);
// 取得选单项目物件
add_item = menu.findItem(R.id.add_item);
search_item = menu.findItem(R.id.search_item);
revert_item = menu.findItem(R.id.revert_item);
share_item = menu.findItem(R.id.share_item);
delete_item = menu.findItem(R.id.delete_item);
// 设定选单项目
processMenu(null);
return true;
}
~~~
同样在“MainActivity.java”,找到“clickMenuItem”方法,加入取消勾选与删除记事资料的程式码:
~~~
public void clickMenuItem(MenuItem item) {
// 使用参数取得使用者选择的选单项目元件编号
int itemId = item.getItemId();
// 判断该执行什么工作,目前还没有加入需要执行的工作
switch (itemId) {
case R.id.search_item:
break;
// 使用者选择新增选单项目
case R.id.add_item:
// 使用Action名称建立启动另一个Activity元件需要的Intent物件
Intent intent = new Intent("net.macdidi.myandroidtutorial.ADD_ITEM");
// 呼叫“startActivityForResult”,,第二个参数“0”表示执行新增
startActivityForResult(intent, 0);
break;
// 取消所有已勾选的项目
case R.id.revert_item:
for (int i = 0; i < itemAdapter.getCount(); i++) {
Item ri = itemAdapter.getItem(i);
if (ri.isSelected()) {
ri.setSelected(false);
itemAdapter.set(i, ri);
}
}
selectedCount = 0;
processMenu(null);
break;
// 删除
case R.id.delete_item:
// 没有选择
if (selectedCount == 0) {
break;
}
// 建立与显示询问是否删除的对话框
AlertDialog.Builder d = new AlertDialog.Builder(this);
String message = getString(R.string.delete_item);
d.setTitle(R.string.delete)
.setMessage(String.format(message, selectedCount));
d.setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 删除所有已勾选的项目
int index = itemAdapter.getCount() - 1;
while (index > -1) {
Item item = itemAdapter.get(index);
if (item.isSelected()) {
itemAdapter.remove(item);
}
index--;
}
// 通知资料改变
itemAdapter.notifyDataSetChanged();
selectedCount = 0;
processMenu(null);
}
});
d.setNegativeButton(android.R.string.no, null);
d.show();
break;
case R.id.googleplus_item:
break;
case R.id.facebook_item:
break;
}
}
~~~
完成这一章所有的工作了,执行应用程式,看看加入的功能是不是都可以正常的运作。
第三堂
最后更新于:2022-04-01 00:52:55
第二堂(4)建立与使用 Activity 元件
最后更新于:2022-04-01 00:52:52
大部份的Android应用程式,都需要一些画面提供使用者执行操作或浏览资料。Android系统使用Activity元件,负责提供应用程式一个画面的所有相关工作。一个画面就是一个继承自“android.app.Activity”的Java类别,所以通常会把它称为Activity元件,也有人叫它“活动”元件。Activity元件几乎是Android应用程式中最常使用的,应用程式的功能如果比较复杂,需要提供比较多的操作和浏览资料的画面,就会包含很多Activity元件。
每一个Activity元件除了撰写需要的Java原始程式码,也需要在应用程式设定档加入相关的设定,在application的开始和结束标签里面,使用activity标签为每一个Activity元件加入设定,所以从应用程式设定档的内容,也可以知道一个应用程式有几个Activity元件。
这一章介绍Activity元件的开发与设定方式,并了解关于Activity元件的生命周期概念,还有Activity元件之间的互动与资料传输。
## 8-1 记事本应用程式
之前已经建立好的应用程式主画面,提供基本的资料浏览与操作功能,现在要为它加入两个Activity元件,一个用来显示关于应用程式的资讯,另一个用来新增一笔记事本资料。
依照之前的说明,为应用程式设计需要的Activity元件,应该要先规划好画面与资源的需求,而且也要想好操作的流程,所以你可以简单的画一个像这样的图型:
[![AndroidTutorial5_02_04_01](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_01-300x162.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_01.png)
在规画这些元件的时候,就可以整理好需要建立的Activity与画面配置档:
* 应用程式资讯:AboutActivity.java,activity_about.xml
* 新增记事本:ItemActivity.java,activity_item.xml
使用者点击主画面下方的应用程式名称,应用程式启动资讯元件,画面的设计会比较简单一些,只有两个TextView和一个Button元件,画面需要的文字资源也要先建立好。
[![AndroidTutorial5_02_04_02](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_02-300x288.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_02.png)
使用者选择功能表的新增项目,应用程式启动新增记事本元件,在这个画面让使用输入标题与内容,为了后续加入的功能,先在画面中提供记事本功能按钮,例如录音与地图。
[![AndroidTutorial5_02_04_03](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_03-300x218.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_03.png)
## 8-2 建立与启动Activity元件
现在开始建立显示应用程式资讯的Activity元件,不过要先了解Activity元件的基本运作。应用程式可以呼叫“startActivity”方法启动其它Activity元件,呼叫“finish”方法可以结束Activity元件:
[![AndroidTutorial5_02_04_04](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_04-300x189.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_04.png)
现在开始新增应用程式资讯元件,在Android Studio开启MyAndroidStudio应用程式。开启“res/values/strings.xml”,加入下列的文字资源:
~~~
<string name="version">版本:AndroidTutorial_0.2.4</string>
~~~
开启“res/values/colors.xml”,加入下列的颜色资源:
~~~
<color name="dark_text">#111111</color>
~~~
在最顶端的“app”目录按鼠标左键,选择“New -> Activity -> Blank Activity”,元件与画面配置档名称依照上面的规划。开启画面配置档“activity_about.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:orientation="vertical"
android:background="@drawable/retangle_drawable"
tools:context="net.macdidi.myandroidtutorial.AboutActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="@dimen/default_margin"
android:padding="@dimen/default_padding"
android:text="@string/about"
android:textColor="@color/dark_text" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="@dimen/default_margin"
android:padding="@dimen/default_padding"
android:text="@string/version"
android:textColor="@color/dark_text" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="@dimen/default_margin"
android:padding="@dimen/default_padding"
android:text="@android:string/ok"
android:onClick="clickOk" />
</LinearLayout>
~~~
开启“AboutActivity.java”,加入取消应用程式标题的叙述,还有在负责执行按钮工作的方法中加入结束Activity元件的叙述,删除其它不需要的程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
public class AboutActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 取消元件的应用程式标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_about);
}
// 结束按钮
public void clickOk(View view) {
// 呼叫这个方法结束Activity元件
finish();
}
}
~~~
Android应用程式的每一个Activity元件,都需要在应用程式设定档加入对应的设定,使用Android Studio建立Activity元件会自动帮你加入默认的设定。开启“mainfests/AndroidManifest.xml”,找到ADT为你加入的设定,把它改为下面的内容:
~~~
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.macdidi.myandroidtutorial" >
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<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
android:name="net.macdidi.myandroidtutorial.AboutActivity"
android:theme="@android:style/Theme.Dialog" />
</application>
</manifest>
~~~
因为这个Activity元件的内容比较简单,使用整个萤幕显示画面的话,看起来会比较空旷一些,所以可以在设定档加入“android:theme="@android:style/Theme.Dialog"”的设定,让这个Activity元件使用对话框的样式。
最后的工作就是执行启动这个Activity元件,先检查应用程式主画面的画面配置档“activity_main.xml”,看看主画面下方显示应用程式名称元件有没有加入需要的设定:
~~~
<LinearLayout ...>
...
<!-- 加入“android:clickable="true"”的设定,TextView元件才可以点击 -->
<!-- 加入“android:onClick="方法名称"”的设定 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="@dimen/default_margin"
android:padding="@dimen/default_padding"
android:background="@drawable/retangle_drawable"
android:text="@string/app_name"
android:clickable="true"
android:onClick="aboutApp" />
</LinearLayout>
~~~
开启“MainActivity.java”,找到aboutApp方法,把程式码改为下面的内容:
~~~
package net.macdidi.myandroidtutorial;
import android.content.Intent;
...
public class MainActivity extends Activity {
...
// 点击应用程式名称元件后呼叫的方法
public void aboutApp(View view) {
// 建立启动另一个Activity元件需要的Intent物件
// 建构式的第一个参数:“this”
// 建构式的第二个参数:“Activity元件类别名称.class”
Intent intent = new Intent(this, AboutActivity.class);
// 呼叫“startActivity”,参数为一个建立好的Intent物件
// 这行叙述执行以后,如果没有任何错误,就会启动指定的元件
startActivity(intent);
}
}
~~~
执行应用程式,看看点击主画面下方应用程式名称元件后,会不会启动与显示新的画面。在启动的画面点击确定按钮,应用程式会回到主画面。这是在应用程式启动与结束一个Activity元件的基本作法。
## 8-3 在结束Activity元件时传送资料
在一般的应用程式运作的时候,经常需要启动另一个Activity元件执行选择、输入或修改资料的功能,完成工作以后,再把资料回传给原来的Activity元件使用。以记事本应用程式来说,主画面负责显示所有的记事资料,需要新增资料的时候,启动一个让使用者输入资料的Activity元件,完成新增的工作回到主画面,这个新增的记事资料就要加入主画面。
如果应用程式在启动的Activity元件结束并返回后,还要执行一些特定的工作,就要使用“startActivityForResult”启动Activity元件。这是新增记事本的元件流程:
[![AndroidTutorial5_02_04_05](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_05-300x183.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_04_05.png)
决定应用程式的流程以后,现在开始设计新增记事用的Activity元件。在最顶端的“app”目录按鼠标左键,选择“New -> Activity -> Blank Activity”,元件与画面配置档名称依照上面的规划,元件名称为“ItemActivity”,画面资源名称为“activity_item.xml”。开启activity_item.xml,把它改为下面的内容:
~~~
<?xml version="1.0" encoding="utf-8"?>
<TableLayout 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:stretchColumns="1"
tools:context="net.macdidi.myandroidtutorial.ItemActivity">
<TableRow>
<TextView
android:text="@string/title"
android:background="@drawable/retangle_drawable"
android:padding="6sp"
android:layout_margin="2sp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/title_text"
android:hint="@string/enter_title"
android:background="@drawable/retangle_drawable"
android:padding="6sp"
android:layout_margin="2sp"
android:textAppearance="?android:attr/textAppearanceMedium" />
</TableRow>
<TableRow>
<TextView
android:text="@string/content"
android:layout_height="200sp"
android:layout_gravity="center_vertical"
android:background="@drawable/retangle_drawable"
android:padding="6sp"
android:layout_margin="2sp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/content_text"
android:hint="@string/enter_content"
android:layout_gravity="top"
android:layout_height="200sp"
android:background="@drawable/retangle_drawable"
android:padding="6sp"
android:layout_margin="2sp"
android:textAppearance="?android:attr/textAppearanceMedium" />
</TableRow>
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="*">
<TableRow>
<ImageButton
android:id="@+id/take_picture"
android:src="@drawable/take_picture_icon"
android:onClick="clickFunction" />
<ImageButton
android:id="@+id/record_sound"
android:src="@drawable/record_sound_icon"
android:onClick="clickFunction" />
<ImageButton
android:id="@+id/set_location"
android:src="@drawable/location_icon"
android:onClick="clickFunction" />
<ImageButton
android:id="@+id/set_alarm"
android:src="@drawable/alarm_icon"
android:onClick="clickFunction" />
<ImageButton
android:id="@+id/select_color"
android:src="@drawable/select_color_icon"
android:onClick="clickFunction" />
</TableRow>
</TableLayout>
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="*">
<TableRow>
<Button
android:id="@+id/cancel_item"
android:text="@android:string/cancel"
android:onClick="onSubmit"
android:padding="6sp"
android:layout_margin="2sp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/ok_teim"
android:text="@android:string/ok"
android:onClick="onSubmit"
android:padding="6sp"
android:layout_margin="2sp"
android:textAppearance="?android:attr/textAppearanceMedium" />
</TableRow>
</TableLayout>
</TableLayout>
~~~
开启“ItemActivity.java”,修改为下面的内容。为了以后需要扩充的功能,加入一些控制按钮执行工作的程式码:
~~~
package net.macdidi.myandroidtutorial;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
public class ItemActivity extends Activity {
private EditText title_text, content_text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item);
processViews();
}
private void processViews() {
title_text = (EditText) findViewById(R.id.title_text);
content_text = (EditText) findViewById(R.id.content_text);
}
// 点击确定与取消按钮都会呼叫这个方法
public void onSubmit(View view) {
// 确定按钮
if (view.getId() == R.id.ok_teim) {
// 读取使用者输入的标题与内容
String titleText = title_text.getText().toString();
String contentText = content_text.getText().toString();
// 取得回传资料用的Intent物件
Intent result = getIntent();
// 设定标题与内容
result.putExtra("titleText", titleText);
result.putExtra("contentText", contentText);
// 设定回应结果为确定
setResult(Activity.RESULT_OK, result);
}
// 结束
finish();
}
// 以后需要扩充的功能
public void clickFunction(View view) {
int id = view.getId();
switch (id) {
case R.id.take_picture:
break;
case R.id.record_sound:
break;
case R.id.set_location:
break;
case R.id.set_alarm:
break;
case R.id.select_color:
break;
}
}
}
~~~
开启“AndroidManifest.xml”,找到Android Studio为你加入的设定,把它改为下面的内容:
~~~
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.macdidi.myandroidtutorial" >
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<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
android:name=".AboutActivity"
android:theme="@android:style/Theme.Dialog" />
<!-- 记事项目元件 -->
<activity
android:name="net.macdidi.myandroidtutorial.ItemActivity" />
</application>
</manifest>
~~~
开启“MainActivity.java”,把程式码改为下面的内容:
~~~
package net.macdidi.myandroidtutorial;
...
public class MainActivity extends ActionBarActivity {
...
public void clickMenuItem(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
case R.id.search_item:
break;
// 使用者选择新增选单项目
case R.id.add_item:
// 建立启动另一个Activity元件需要的Intent物件
Intent intent = new Intent(this, ItemActivity.class);
// 呼叫“startActivityForResult”,第二个参数“0”目前没有使用
startActivityForResult(intent, 0);
break;
case R.id.revert_item:
break;
case R.id.delete_item:
break;
case R.id.googleplus_item:
break;
case R.id.facebook_item:
break;
}
}
...
}
~~~
使用“startActivityForResult”启动Activity元件,结束并返回以后,Android会呼叫“onActivityResult”方法一次。所以覆写这个方法,在里面执行需要的判断与工作。同样在“MainActivity.java”,因为原来使用字串阵列提供资料给ListView元件,现在要把它换成“ArrayList”物件,这样可以修改ListView包装的资料项目。把程式码改为下面的内容:
~~~
package net.macdidi.myandroidtutorial;
import java.util.ArrayList;
...
public class MainActivity extends ActionBarActivity {
private ListView item_list;
private TextView show_app_name;
// 换掉原来的字串阵列
private ArrayList<String> data = new ArrayList<>();
private ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
processViews();
processControllers();
// 加入范例资料
data.add("关于Android Tutorial的事情");
data.add("一只非常可爱的小狗狗!");
data.add("一首非常好听的音乐!");
int layoutId = android.R.layout.simple_list_item_1;
adapter = new ArrayAdapter<String>(this, layoutId, data);
item_list.setAdapter(adapter);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 如果被启动的Activity元件传回确定的结果
if (resultCode == Activity.RESULT_OK) {
// 读取标题
String titleText = data.getStringExtra("titleText");
// 加入标题项目
this.data.add(titleText);
// 通知资料已经改变,ListView元件才会重新显示
adapter.notifyDataSetChanged();
}
}
...
private void processControllers() {
AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// 换掉“data[position]”
Toast.makeText(MainActivity.this,
data.get(position), Toast.LENGTH_LONG).show();
}
};
item_list.setOnItemClickListener(itemListener);
AdapterView.OnItemLongClickListener itemLongListener = new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
// 换掉“data[position]”
Toast.makeText(MainActivity.this,
"Long: " + data.get(position), Toast.LENGTH_LONG).show();
return false;
}
};
...
}
~~~
执行应用程式,点击功能表的新增项目,在启动的画面输入标题后,选择确定按钮,回到主画面后,看看有没有多一笔你刚才输入的资料。
## 8-4 在启动Activity元件时传送资料
以这个记事应用程式来说,除了已经写好的新增记事资料功能,通常也需要让使用者修改记事资料。在主画面点击一笔需要修改的记事项目以后,应用程式开启修改记事的元件,让使用者执行修改资料的工作。这个修改记事的元件其实跟新增记事的功能是差不多的,所以通常就不会另外设计一个新的Activity元件,让已经设计好的“ItemActivity”同时提供新增与修改两种功能。
为了让一个Activity元件可以执行两种工作,通常会帮这类元件另外取不同的“Action”名称。开启“AndroidManifest.xml”,把它改为下面的内容:
~~~
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
...
<application ... >
...
<!-- 记事项目元件 -->
<activity
android:name="net.macdidi.myandroidtutorial.ItemActivity">
<intent-filter>
<!-- 新增用的名称 -->
<action android:name="net.macdidi.myandroidtutorial.ADD_ITEM"/>
<!-- 修改用的名称 -->
<action android:name="net.macdidi.myandroidtutorial.EDIT_ITEM"/>
<!-- 一定要加入,内容固定不变 -->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
~~~
开启“ItemActivity.java”,修改为下面的内容:
~~~
package net.macdidi.myandroidtutorial;
...
public class ItemActivity extends Activity {
private EditText title_text, content_text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item);
processViews();
// 取得Intent物件
Intent intent = getIntent();
// 读取Action名称
String action = intent.getAction();
// 如果是修改记事
if (action.equals("net.macdidi.myandroidtutorial.EDIT_ITEM")) {
// 接收与设定记事标题
String titleText = intent.getStringExtra("titleText");
title_text.setText(titleText);
}
}
...
}
~~~
开启“MainActivity.java”,把程式码改为下面的内容:
~~~
package net.macdidi.myandroidtutorial;
...
public class MainActivity extends Activity {
private ListView item_list;
private TextView show_app_name;
private ArrayList<String> data = new ArrayList<>();
private ArrayAdapter<String> adapter;
...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
String titleText = data.getStringExtra("titleText");
// 如果是新增记事
if (requestCode == 0) {
// 加入标题项目
this.data.add(titleText);
// 通知资料已经改变,ListView元件才会重新显示
adapter.notifyDataSetChanged();
}
// 如果是修改记事
else if (requestCode == 1) {
// 读取记事编号
int position = data.getIntExtra("position", -1);
if (position != -1) {
// 设定标题项目
this.data.set(position, titleText);
// 通知资料已经改变,ListView元件才会重新显示
adapter.notifyDataSetChanged();
}
}
}
}
private void processViews() {
item_list = (ListView)findViewById(R.id.item_list);
show_app_name = (TextView) findViewById(R.id.show_app_name);
}
private void processControllers() {
OnItemClickListener itemListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// 使用Action名称建立启动另一个Activity元件需要的Intent物件
Intent intent = new Intent("net.macdidi.myandroidtutorial.EDIT_ITEM");
// 设定记事编号与标题
intent.putExtra("position", position);
intent.putExtra("titleText", data.get(position));
// 呼叫“startActivityForResult”,第二个参数“1”表示执行修改
startActivityForResult(intent, 1);
}
};
...
}
...
public void clickMenuItem(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
case R.id.search_item:
break;
case R.id.add_item:
// 使用Action名称建立启动另一个Activity元件需要的Intent物件
Intent intent = new Intent("net.macdidi.myandroidtutorial.ADD_ITEM");
// 呼叫“startActivityForResult”,,第二个参数“0”表示执行新增
startActivityForResult(intent, 0);
break;
case R.id.revert_item:
break;
case R.id.delete_item:
break;
case R.id.googleplus_item:
break;
case R.id.facebook_item:
break;
}
}
...
}
~~~
执行应用程式,试试看新增记事功能是否可以正常运作。在主画面点选一个记事项目,修改记事标题并确定后,看看主画面的记事资料会不会更新。目前完成的功能并没有处理记事资料的内容,也还没有储存到数据库,所以不会保存新增与修改后的资料。
第二堂(3)应用程式与使用者的互动
最后更新于:2022-04-01 00:52:50
Android API提供应用程式使用者互动的设计架构,你可以根据使用者在应用程式的操作,设计与提供应用程式与使用者的互动功能。例如使用者点击画面元件、按下实体按键,还有在触控萤幕上点击或滑动,这些操作行为通常会称为“事件”。应用程式可以依照需求加入事件的控制,当某一种事件发生的时候,也就是使用者执行某种操作,可以执行你为这些事件设计好的程式码。
Android系统的使用者操作事件控制,都是一些已经设计好的作法,根据使用者操作事件和画面元件的种类,通常是撰写实作一个接口(interface)的类别,根据这个接口的规定实作需要的方法,在方法里面设计需要执行的工作。
## 7-1 画面元件的onClick设定
想要让应用程式提供的画面元件,可以让使用者点击以后执行一个指定的工作,最简单的作法就是在画面元件加入“android:onClick”设定,例如最常用的按钮元件(Button)。如果需要的话,也可以为文字符件(TextView)执行onClick设定。开启“res/layout/activity_main.xml”档案,参考下面的内容加入需要的设定:
~~~
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
<!-- 加入“android:clickable="true"”的设定,TextView元件才可以点击 -->
<!-- 加入“android:onClick="方法名称"”的设定 -->
<TextView
...
android:clickable="true"
android:onClick="aboutApp" />
</LinearLayout>
~~~
TextView是用来显示文字的元件,所以要特别加入让它可以点击的设定,如果是按钮元件的话就不用特别设定。如果希望使用者点击TextView元件以后,在画面显示应用程式名称的讯息框,就要加入需要的程式码。开启专案的“MainActivity.java”,参考下面的内容加入需要的程式码:
~~~
package net.macdidi.myandroidtutorial;
...
// 加入讯息框的API
import android.widget.Toast;
public class MainActivity extends Activity {
...
// 方法名称与onClick的设定一样,参数的型态是android.view.View
public void aboutApp(View view) {
// 显示讯息框,指定三个参数
// Context:通常指定为“this”
// String或int:设定显示在讯息框里面的讯息或文字资源
// int:设定讯息框停留在画面的时间
Toast.makeText(this, R.string.app_name, Toast.LENGTH_LONG).show();
}
}
~~~
执行这个应用程式,在应用程式画面点击最下面的TextView元件,检查有没有显示讯息框。
## 7-2 选单事件控制
如果应用程式提供的功能比较多一些,为了让画面可以比较简洁,通常会把功能设计为选单,选单资源的部份已经在“第二堂(1)规划与建立应用程式需要的资源”建立好了。需要让使用者选择选单项目后执行一些特定的工作,最简单的作法是为选单项目加入“onClick”的设定。开启“res/menu/main_menu.xml”档案,参考下面的内容加入需要的设定:
~~~
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 为选单项目加入“android:onClick”设定 -->
<item
android:id="@+id/search_item"
android:showAsAction="always"
android:icon="@android:drawable/ic_menu_search"
android:onClick="clickMenuItem" />
<item
android:id="@+id/add_item"
android:showAsAction="always"
android:icon="@android:drawable/ic_menu_add"
android:onClick="clickMenuItem" />
<item
android:id="@+id/revert_item"
android:showAsAction="always"
android:icon="@android:drawable/ic_menu_revert"
android:onClick="clickMenuItem" />
<item
android:id="@+id/delete_item"
android:showAsAction="always"
android:icon="@android:drawable/ic_menu_delete"
android:onClick="clickMenuItem" />
<!-- 这是外层的选单项目,所以不用设定 -->
<item
android:id="@+id/share_item"
android:showAsAction="always"
android:icon="@android:drawable/ic_menu_share"
android:onClick="clickMenuItem" >
<menu>
<item
android:id="@+id/googleplus_item"
android:title="Google+"
android:onClick="clickMenuItem" />
<item
android:id="@+id/facebook_item"
android:title="Facebook"
android:onClick="clickMenuItem" />
</menu>
</item>
</menu>
~~~
这里执行的设定跟之前的说明不太一样,所有选单项目设定的方法名称都是“clickMenuItem”。你也可以为每一个选单项目设定不同的方法名称,可是这样做的话,Activity元件里面就要宣告很多方法,所以使用这样的作法。开启“MainActivity.java”档案,参考下面的内容加入需要的程式码:
~~~
package net.macdidi.myandroidtutorial;
...
import android.app.AlertDialog;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends ActionBarActivity {
...
// 加载选单资源
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main_menu, menu);
return true;
}
// 使用者选择所有的选单项目都会呼叫这个方法
public void clickMenuItem(MenuItem item) {
// 使用参数取得使用者选择的选单项目元件编号
int itemId = item.getItemId();
// 判断该执行什么工作,目前还没有加入需要执行的工作
switch (itemId) {
case R.id.search_item:
break;
case R.id.add_item:
break;
case R.id.revert_item:
break;
case R.id.delete_item:
break;
case R.id.googleplus_item:
break;
case R.id.facebook_item:
break;
}
// 测试用的程式码,完成测试后记得移除
AlertDialog.Builder dialog =
new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("MenuItem Test")
.setMessage(item.getTitle())
.setIcon(item.getIcon())
.show();
}
}
~~~
执行这个应用程式,选择画面上方的选单项目,检查有没有显示对话框。
## 7-3 监听与事件介绍
“android.view”和“android.widget”套件宣告了许多“Listener”接口,这些接口通常会叫作“监听接口”。每一个监听接口可以控制使用者在应用程式中执行的一种操作,这些接口的名称都很规则,都是使用“On种类Listener”的格式命名。例如下列宣告在“android.view.View”类别中的基本监听接口:
* View.OnClickListener:执行点击事件。
* View.OnLongClickListener:执行长按事件。
* View.OnKeyListener:执行实体按键操作事件。
* View.OnTouchListener:执行触控萤幕操作事件。
采用这种方式为某个画面元件加入事件控制,因为需要在程式码使用画面元件,所以一定要为元件取一个名称,设定元件名称使用“android:id="@+id/名称"”的格式:
~~~
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
<!-- 加入“android:id="@+id/名称"”的设定 -->
<TextView
android:id="@+id/show_app_name"
... />
</LinearLayout>
~~~
为需要执行事件控制的元件设定好名称(id)以后,让使用者在点击这个元件以后,使用对话框显示比较详细的应用程式资讯,所以先在文字资源档(res/values/strings.xml)加入需要的资源:
~~~
<resources>
...
<string name="about">这是Android Tutorial应用程式</string>
</resources>
~~~
开启专案的“MainActivity.java”,参考下面的内容加入需要的程式码:
~~~
package net.macdidi.myandroidtutorial;
...
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// 读取在画面配置档已经设定好名称的元件
TextView show_app_name = (TextView) findViewById(R.id.show_app_name);
// 建立点击监听物件
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View view) {
AlertDialog.Builder d =
new AlertDialog.Builder(MainActivity.this);
d.setTitle(R.string.app_name)
.setMessage(R.string.about)
.show();
}
};
// 注册点击监听物件
show_app_name.setOnClickListener(listener);
}
...
}
~~~
执行这个应用程式,在应用程式画面点击最下面的TextView元件,检查有没有显示对话框。因为你在这个TextView元件执行OnClickListener事件的注册,它的“android:onClick”设定就被覆蓋了,所以点击以后只会显示对话框。
一般的应用程式也很常使用长按事件,开启专案的“MainActivity.java”,参考下面的内容,把原来的点击事件改为长按事件:
~~~
package net.macdidi.myandroidtutorial;
...
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// 读取在画面配置档已经设定好名称的元件
TextView show_app_name = (TextView) findViewById(R.id.show_app_name);
// 建立长按监听物件
View.OnLongClickListener listener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
AlertDialog.Builder dialog =
new AlertDialog.Builder(MainActivity.this);
dialog.setTitle(R.string.app_name)
.setMessage(R.string.about)
.show();
return false;
}
};
// 注册长按监听物件
show_app_name.setOnLongClickListener(listener);
}
...
}
~~~
执行这个应用程式,在应用程式画面点击最下面的TextView元件,会显示原来设定的讯息框,长按TextView元件会显示对话框。经由这两个练习,就可以了解Android事件的设计方式,在大部份的情况下,监听接口都会以“On”开头,宣告与建立好监听物件以后,呼叫元件的“set监听接口”方法执行注册的工作。
## 7-4 ListView元件的事件控制
ListView元件在应用程式中的应用非常多,从应用程式的功能表、浏览大量的资料或是让使用者执行资料的选择,应用程式需要类似列表资料的需求,都可以使用它来完成。它可以简单的列出一些文字的项目在画面上,让使用者浏览与选择。也可以自己设计需要的项目画面,加入图示、CheckBox或其它需要的画面元件,它呈现的画面与可以提供的操作功能都非常灵活。
这个记事本的主画面使用ListView元件显示所有的记事资料,选择一个项目以后可以显示详细的内容与执行后续的工作,所以需要为ListView设定选择项目的事件控制。开启专案的“MainActivity.java”,参考下面的内容加入需要的程式码:
~~~
package net.macdidi.myandroidtutorial;
...
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 增加“final”关键字,让巢状类别中的程式码使用
final String[] data = {
"关于Android Tutorial的事情",
"一只非常可爱的小狗狗!",
"一首非常好听的音乐!"};
int layoutId = android.R.layout.simple_list_item_1;
ArrayAdapter<String> adapter =
new ArrayAdapter<String>(this, layoutId, data);
ListView item_list = (ListView)findViewById(R.id.item_list);
item_list.setAdapter(adapter);
// 建立选单项目点击监听物件
AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() {
// 第一个参数是使用者操作的ListView物件
// 第二个参数是使用者选择的项目
// 第三个参数是使用者选择的项目编号,第一个是0
// 第四个参数在这里没有用途
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MainActivity.this,
data[position], Toast.LENGTH_LONG).show();
}
};
// 注册选单项目点击监听物件
item_list.setOnItemClickListener(itemListener);
...
}
...
}
~~~
执行这个应用程式,在应用程式画面点击ListView的选单项目,看看有没有显示选单项目的内容讯息框。ListView元件也提供项目长按事件,你可以依照应用程式的需求,使用点击与长按事件提供使用者的操作。开启专案的“MainActivity.java”,参考下面的内容加入需要的程式码:
~~~
package net.macdidi.myandroidtutorial;
...
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 增加“final”关键字,让巢状类别中的程式码使用
final String[] data = {
"关于Android Tutorial的事情",
"一只非常可爱的小狗狗!",
"一首非常好听的音乐!"};
int layoutId = android.R.layout.simple_list_item_1;
ArrayAdapter<String> adapter =
new ArrayAdapter<String>(this, layoutId, data);
ListView item_list = (ListView)findViewById(R.id.item_list);
item_list.setAdapter(adapter);
...
// 建立选单项目长按监听物件
AdapterView.OnItemLongClickListener itemLongListener = new AdapterView.OnItemLongClickListener() {
// 第一个参数是使用者操作的ListView物件
// 第二个参数是使用者选择的项目
// 第三个参数是使用者选择的项目编号,第一个是0
// 第四个参数在这里没有用途
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MainActivity.this,
"Long: " + data[position], Toast.LENGTH_LONG).show();
return false;
}
};
// 注册选单项目长按监听物件
item_list.setOnItemLongClickListener(itemLongListener);
...
}
...
}
~~~
执行这个应用程式,在应用程式画面长按ListView的选单项目,看看有没有显示选单项目的内容讯息框。
## 7-5 重新规划Activity元件的程式码
如果Activity元件需要的画面元件比较多一些,使用者操作的功能也比较复杂,你应该可以想像得到,这个Activity元件类别的onCreate方法,会有一大堆呼叫findViewById方法取得画面元件物件的叙述,还有宣告与建立监听物件与执行注册的叙述。这些需要的叙述通通写在onCreate方法中,以程式设计的概念来说,一个方法的宣告有上百行的程式叙述,应该不是一种很好的写法,对开发人员来说,以后的维护与修改都会是一件不容易的工作。
为了让所有Activity元件的程式码,都可以使用一种比较固定而且容易的设计方式来完成需要的工作,建议你可以在开发每一个Activity元件类别的时候,使用像这个样版的模式来开发Activity元件:
~~~
public class SampleActivity extends Activity {
// 宣告所有需要的画面元件物件字段变量
private ...;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(...);
// 呼叫自己额外宣告的方法,执行所有取得画面元件物件的工作
processViews();
// 呼叫自己额外宣告的方法,执行所有注册的工作
processControllers();
}
private void processViews() {
// 在这个方法中,取得画面元件物件后指定给字段变量
... = (...) findViewById(R.id.xxx);
}
private void processControllers() {
// 在这个方法中,宣告或建立需要的监听物件
// 并执行所有需要的注册工作
...
}
...
}
~~~
熟悉这样的写法以后,原来需要执行的工作会在不同的方法中执行。你会先加入画面元件字段变量的宣告,然后在processViews方法中取得与设定画面元件物件,如果需要执行注册监听物件的工作,在processControllers方法中加入需要的程式码。这样把程式码依照工作简单的分开在不同方法中执行,对以后的维护与修改都会有一个比较固定的作法,而且比较不容易出错。所以就算是一个很简单的Activity元件,都建议你使用这样的写法。
开启专案的“MainActivity.java”,不改变原来撰写好的功能,把它改为下面的内容:
~~~
package net.macdidi.myandroidtutorial;
import android.app.AlertDialog;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity {
private ListView item_list;
private TextView show_app_name;
private static final String[] data = {
"关于Android Tutorial的事情",
"一只非常可爱的小狗狗!",
"一首非常好听的音乐!"};
private ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
processViews();
processControllers();
int layoutId = android.R.layout.simple_list_item_1;
adapter = new ArrayAdapter<String>(this, layoutId, data);
item_list.setAdapter(adapter);
}
private void processViews() {
item_list = (ListView)findViewById(R.id.item_list);
show_app_name = (TextView) findViewById(R.id.show_app_name);
}
private void processControllers() {
// 建立选单项目点击监听物件
AdapterView.OnItemClickListener itemListener = new AdapterView.OnItemClickListener() {
// 第一个参数是使用者操作的ListView物件
// 第二个参数是使用者选择的项目
// 第三个参数是使用者选择的项目编号,第一个是0
// 第四个参数在这里没有用途
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MainActivity.this,
data[position], Toast.LENGTH_LONG).show();
}
};
// 注册选单项目点击监听物件
item_list.setOnItemClickListener(itemListener);
// 建立选单项目长按监听物件
AdapterView.OnItemLongClickListener itemLongListener = new AdapterView.OnItemLongClickListener() {
// 第一个参数是使用者操作的ListView物件
// 第二个参数是使用者选择的项目
// 第三个参数是使用者选择的项目编号,第一个是0
// 第四个参数在这里没有用途
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MainActivity.this,
"Long: " + data[position], Toast.LENGTH_LONG).show();
return false;
}
};
// 注册选单项目长按监听物件
item_list.setOnItemLongClickListener(itemLongListener);
// 建立长按监听物件
View.OnLongClickListener listener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
AlertDialog.Builder dialog =
new AlertDialog.Builder(MainActivity.this);
dialog.setTitle(R.string.app_name)
.setMessage(R.string.about)
.show();
return false;
}
};
// 注册长按监听物件
show_app_name.setOnLongClickListener(listener);
}
// 加载选单资源
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu_main, menu);
return true;
}
// 使用者选择所有的选单项目都会呼叫这个方法
public void clickMenuItem(MenuItem item) {
// 使用参数取得使用者选择的选单项目元件编号
int itemId = item.getItemId();
// 判断该执行什么工作,目前还没有加入需要执行的工作
switch (itemId) {
case R.id.search_item:
break;
case R.id.add_item:
break;
case R.id.revert_item:
break;
case R.id.delete_item:
break;
case R.id.googleplus_item:
break;
case R.id.facebook_item:
break;
}
// 测试用的程式码,完成测试后记得移除
AlertDialog.Builder dialog =
new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("MenuItem Test")
.setMessage(item.getTitle())
.setIcon(item.getIcon())
.show();
}
// 方法名称与onClick的设定一样,参数的型态是android.view.View
public void aboutApp(View view) {
// 显示讯息框
// Context:通常指定为“this”;如果在巢状类别中使用,要加上这个Activity元件类别的名称,例如“元件类别名称.this”
// String或int:设定显示在讯息框里面的讯息或文字资源
// int:设定讯息框停留在画面的时间,使用宣告在Toast类别中的变量,可以设定为“LENGTH_LONG”和“LENGTH_SHORT”
Toast.makeText(this, R.string.app_name, Toast.LENGTH_LONG).show();
}
}
~~~
执行这个应用程式,确认所有功能都还是可以正确的运作。
第二堂(2)设计应用程式使用者界面
最后更新于:2022-04-01 00:52:48
行动装置提供各种不同应用的程式,让使用者可以随时执行一些工作、浏览网页和玩一些游戏。因为萤幕尺寸的关系,它不会像一般个人电脑上的应用程式,需要设计一些很复杂的操作画面。透过触控萤幕的操作,行动装置应用程式提供简单与直觉的操作画面,通常不需要使用说明书,就可以让使用者顺利的使用应用程式。
Android是一个开放的作业系统,这表示所有厂商都可以设计与制造各种使用Android作业系统的行动装置,这些装置的萤幕尺寸和内建的设备,并不是固定的。尤其是萤幕的尺寸和分辨率,会让Android应用程式的画面设计,跟其它技术比较不一样。一个设计良好的应用程式,在萤幕是3.4吋、分辨率是480X800的装置上执行,还有在萤幕是4.7吋、分辨率是1920X1080的装置上执行,应用程式的画面看起来应该是一样的。
Android应用程式的画面设计,采取一种比较灵活的方式,作法也跟其它技术比较不一样。这一章说明设计应用程式画面的重要概念与方式,学习使用各种Android画面控件和版面配置元件。
## 5-1 设计使用者界面
一般的Android应用程式,通常需要提供一些画面,让使用者执行操作或浏览资料。应用程式的画面是使用一些Android API中的画面物件组合起来的,这些画面API主要在“android.widget”套件下,这个套件提供各种画面元件的类别,例如在应用程式画面上的一个按钮,就是一个“Button”类别的物件。认识基本的画面元件以后,在规划与设计应用程式的画面时,你就可以制作像这样的文件资料:
[![AndroidTutorial5_02_02_01](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_01-300x195.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_01.png)
在上面的规划资料里面,你可以决定使用哪些画面元件,例如用来显示文字的“TextView”元件,或是让使用者输入文字用的“EditText”元件。针对整个画面的安排,你也会决定控制元件排列方式的画面配置元件,例如这个画面适合使用“RelativeLayout”这种画面配置元件。像按钮或输入这类元件,需要在程式码中设计按钮的工作与读取输入的内容,所以需要为它们取一个名称,这个工作也可以在规划应用程式的画面时就决定好元件的名称,在设计画面与撰写程式的时候,会使用这些名称执行一些设定。
跟一般其它技术的画面元件类似,Android跟画面相关的元件在“android.view”和“android.widget”两个套件中。下图显示主要的套件和部份的元件类别:
[![AndroidTutorial5_02_02_02](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_02-300x157.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_02.png)
上面的类别图形只有显示少数几个元件,在android.view套件下的“View”类别,是所有画面元件的父类别。同样在这个套件下的“ViewGroup”是所有画面配置元件的父类别。它们各自有很多子类别,提供各种画面和配置元件。大部份的Android应用程式设计,应该使用XML格式的画面配置档为Activity元件设计画面,它放在“res\layout”目录下,这是在这里说明的主要设计方式。
## 5-2 画面元件的基本设定
Android的画面元件是View和ViewGroup的子类别,提供各种应用程式画面需要的元件。在画面配置档中设计应用程式的画面,是采用与这些与元件类别名称一样的标签,再依照需要加入一些设定,所以跟程式设计的作法很不一样。画面配置档一定要放在“res/layout”目录,档案名称必须是小写的字母和底线,副档名一定是“.xml”。一个基本的画面配置档内容会像这样:
~~~
<?xml version="1.0" encoding="utf-8"?>
<!-- 使用Button标签,设定整个画面只有一个按钮元件 -->
<!--
xmlns:android="..." 这个设定一定要加在第一个标签,
后面还有其它标签的话,就不用再加入这个设定
-->
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello! GUI!" />
~~~
这个画面配置档只有在画面中加入一个按钮元件,它使用的是“Button”标签,在API里面也有一个名称为“Button”的类别。设计好需要的画面配置档,就可以在Activity元件的onCreate方法中,呼叫“setContentView”指定元件使用指定的画面资源:
~~~
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设定画面配置资源
// 指定的参数在“R.layout.”后面是档案名称
setContentView(R.layout.activity_test);
}
}
~~~
不管是设计一个简单或复杂的画面,对放到画面中的元件,都有一些基本而且通用的设定,在大部份的情况下,一定要为画面元件加入下列决定大小的设定:
* android:layout_width:设定画面元件的宽度
* android:layout_height:设定画面元件的高度
因为各种Android装置的萤幕尺寸与分辨率并不是一样的,所以在设定画面元件的大小时,应该要设定为“match_parent”或“wrap_content”。例如把元件的宽度设定为match_parent时,这个画面元件的宽度就会占满所用的空间。设定为wrape_content的话,会根据这个画面元件自动调整为足够显示内容的空间。如果画面中只有一个按钮元件,不同的设定组合会有不同的效果:
[![AndroidTutorial5_02_02_03](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_03-300x118.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_03.png)
这样的设定方式,让应用程式的画面在不同尺寸与分辨率的萤幕显示的时候,看起来会是一样的。你也可以为画面元件指定固定的宽与高,在设定的时候使用这些单位,例如“android:layout_width= "120sp"”:
* px:萤幕画素。
* dp:每英吋画面,160dp为一英吋。
* sp:和dp一样,不过会根据装置设定的字号自动调整。
* in:英吋。
* mm:公厘。
决定画面元件的大小以后,你可能需要设定与其它元件的间隔距离,这个设定在画面有比较多元件的时候,可以让所有元件不会黏成一团,画面看起来会好一些。这些是用来执行与其它元件的间隔距离设定,使用上面说明的单位设定需要的间隔:
* android:marginTop:设定上方的间隔。
* android:marginBottom:设定下方的间隔。
* android:marginLeft:设定左侧的间隔。
* android:marginRight:设定右侧的间隔。
* android:margin:设定上、下、左、右为同样的间隔。
你还可以控制画面元件内容的间隔距离设定,让一个元件的内容与它使用的空间有一点间隔,元件本身看起来就不会那么拥挤。这些是用来执行元件内容间隔距离的设定:
* android:paddingTop:设定内容上方的间隔。
* android:paddingBottom:设定内容下方的间隔。
* android:paddingLeft:设定内容左侧的间隔。
* android:paddingRight:设定内容右侧的间隔。
* android:padding:设定内容上、下、左、右为同样的间隔。
画面元件还有一个可以控制内容位置的设定,设定名称是“android:gravity”,可以让你比较容易把内容设定为需要的位置。下面是它的设定值:
* top、bottom、left、right:设定画面元件的内容对齐上、下、左、右。
* center_vertical、center_horizontal:设定画面元件的内容对齐垂直或水平的中央。
* center:设定画面元件的内容对齐垂直与水平的中央。
* fill_vertical、fill_horizontal:设定画面元件的内容占满垂直或水平空间。
* fill:设定画面元件的内容占满垂直与水平空间。
这些设定值也可以使用组合的方式,例如希望把内容对齐下方的右侧,就可以设定为“bottom|right”,多个设定值之间使用“|”隔开。这是一个设定效果的范例,它使用一个占满整个画面的TextView元件,如果没有设定的话,默认的内容位置是在左上方,使用一些不同的设定控制内容的位置:
[![AndroidTutorial5_02_02_04](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_04-300x114.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_04.png)
如果是可以显示文字内容的画面元件,例如文字(TextView)、按钮(Button)或输入(EditText)元件,可以为它们加入文字内容与样式设定:
* android:text:设定画面元件的文字内容。
* android:textSize:设定文字的大小。
* android:textAppearance:使用系统的默认值设定文字的大小,设定的格式为“?android:attr/设定值”,有textAppearanceLarge(大)、textAppearanceMedium(中)和textAppearanceSmall(小)三种设定值。
* android:textColor:设定文字的颜色。
* android:background:设定背景颜色。
## 5-3 使用画面配置元件
就算是一个简单的Android应用程式,大部份都会在一个画面中使用多个画面元件,组合成应用程式需要的画面,一个画面只有一个画面元件的情况应该是不多的。所以你在规划与设计应用程式的画面时,除了知道要使用哪一些画面元件,也要规划好画面使用配置方式,就是决定所有需要的元件如何在画面上排列。
因为各种Android实体装置的萤幕尺寸与分辨率并不是一样的,所以画面元件的排列、位置与大小也应该不是固定的,应用程式在不同装置运作的时候,画面看起来才会一样。所以Android建议你应该使用“Layout”来设计画面元件的排列方式,Layout是ViewGroup的子类别,ViewGroup是一种容器元件,可以把其它元件放在这些元件里面,组合成需要的画面。
在规划与设计应用程式画面的时候,应该就可以决定它们该使用哪一种Layout。Android在“android.widget”套件中提供的“LinearLayout”和“RelativeLayout”是基本的Layout元件。LinearLayout可以设定为依照水平(horizontal)或垂直(vertical)排列。RelativeLayout使用画面元件相对的位置来排列,适合用在比较不规则的画面元件配置。画面元件放在这两种Layout中的效果会像这样:
[![AndroidTutorial5_02_02_05](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_05-300x136.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_05.png)
如果应用程式的画面比较复杂一些,这样的Layout应该就不够用了,所以使用这些Layout的时候,可以使用巢状的方式组合成一个复杂的排列。例如这个画面的排列方式,在最外层使用垂直排列的LinearLayout,里面又包含上下两个LinearLayout,它们也可以依照自己的需求,设定为垂直和水平排列:
[![AndroidTutorial5_02_02_06](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_06-300x162.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_06.png)
ViewGroup还提供一种很常使用的“TableLayout”,它可以把画面切割为表格,你可以依照画面的需求设定为像是2X4的区块,每一个区块都可以放一个画面元件,如果需要的画面不是一个固定的表格,也可以调整它们的区块。使用TableLayout排列的画面会像这样:
[![AndroidTutorial5_02_02_07](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_07-300x104.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_07.png)
### 5-3-1 LinearLayout
LinearLayout可以提供比较简单的画面配置,你可以设定它依照水平(由左到右)或垂直(由上往下)排列画面元件。如果需要使用这种排列方式,在画面配置档中加入LinearLayout标签,使用“android:orientation”设定排列的方式,“horizontal”为水平,“vertical”为垂直:
~~~
<?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="vertical或horizontal" >
// 放在这里的画面元件会依照指定的方式排列
</LinearLayout>
~~~
放在LinearLayout里面的元件,还可以设定它们占用的空间比例,这样就可以让这种看起来很单纯的排列方式,变得比较灵活一些。要设定元件占用的空间比例,使用“android:layout_weight="比例"”,依照LinearLayout设定为水平或垂直排列,设定元件的比例后,它的宽或高就由比例来决定,所以应该把元件的宽或高设定为“0dp”。如果其中有一个元件没有设定比例,它会依照自己宽与高的设定决定大小,其它空间再由比例决定各自的大小。搭配使用这些设定,就可以很灵活的设定各种画面的配置:
[![AndroidTutorial5_02_02_08](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_08-300x110.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_08.png)
### 5-3-2 RelativeLayout
应用程式需要的画面,可能不会都是很规则的排列,如果画面元件的位置与排列比较复杂与不规则的时候,就比较适合使用RelativeLayout这种排列方式。它可以让你决定元件在画面上位置,还有设定元件与元件之间的相关位置与对齐方式。这种配置方式使用“RelativeLayout”标签:
~~~
<?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" >
// 使用关联的方式排列画面元件
</RelativeLayout>
~~~
放在RelativeLayout标签中的画面元件,可以依照画面的需求,决定自己在画面中的位置,也可以设定自己与其它元件的相对位置。这些是在RelativeLayout标签中的画面元件可以使用的设定:
* android:layout_位置="@id/元件名称":决定自己在指定元件的相对位置,格式中的“位置”可以使用above、below、toLeftOf或toRightOf,依照顺序表示把自己放在指定元件的上方、下方、左边或右边。
* android:layout_align对齐="@id/元件名称":决定自己和指定元件的对齐方式,格式中的“对齐”可以使用Top、Bottom、Left或right,依照顺序表示把自己的上、下、左或右对齐指定的元件。
* android:layout_alignParent对齐="true|flase":决定自己与容器的对齐方式,格式中的“对齐”可以使用Top、Bottom、Left或right,设定为true的时候,依照顺序表示把自己对齐容器的上、下、左或右。
* android:layout_center对齐="true|flase":决定自己与容器对齐中央的方式,格式中的“对齐”可以使用Horizontal、Vertical或InParent,设定为true的时候,依序表示将自己对齐容器的水平中央、垂直中央或容器中央。
搭配使用这些RelativeLayout提供的设定,就算是比较复杂或不规则的画面,也可以很容易完成设计的工作:
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout … >
<TextView
android:id="@+id/account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ACCOUNT: "
android:textSize="24sp" />
<!-- 把自己放在account元件的右边 -->
<EditText
android:id="@+id/account_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/account"
android:paddingLeft="6sp" />
<!-- 把自己放在account_value元件的下方,而且对齐容器右边 -->
<Button
android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/account_value"
android:text="OK" />
<!-- 把自己放在ok元件的左边,而且上方也对齐它 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/ok"
android:layout_toLeftOf="@id/ok"
android:text="Cancel" />
</RelativeLayout>
~~~
这个画面配置档呈现的画面会像这样:
[![AndroidTutorial5_02_02_09](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_09-242x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_09.png)
### 5-3-3 TableLayout
ViewGroup还提供一种很常使用的“TableLayout”画面排列方式,它可以把画面切割为表格,你可以依照画面的需求,把画面切割为像是2X4的区块,每一个区块都可以放一个画面元件,如果需要的画面不是一个固定的表格,也可以调整它们的区块。这种画面配置使用“TableLayout”标签,搭配“TableRow”标签建立需要的表格。TableLayout会控制画面元件的宽与高,所以在这个标签中的画面元件都不需要设定宽与高,就算加入设定也不会有效果:
~~~
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- 第一列 -->
<!-- TableRow不需要设定宽与高 -->
<TableRow>
<!-- 所有的元件都不需要设定宽与高 -->
...
</TableRow>
<!-- 第二列 -->
<TableRow>
...
</TableRow>
</TableLayout>
~~~
TableLayout会管理与控制画面元件的大小,默认的情况,画面元件的宽度都是“wrap_content”的效果。你可以在TableLayout标签中加入这些设定,执行元件的宽度与是否隐藏的设定:
* android:stretchColumns:放大指定的字段宽度。第一个字段是0,可以设定多个字段,例如“1,3,5”;设定为“*”表示所有字段。
* android:shrinkColumns:宽度不够显示所有内容的时候,指定的字段会自动缩小。第一个字段是0,可以设定多个字段,例如“1,3,5”;设定为“*”表示所有字段。
* android:collapseColumns:隐藏指定的字段。第一个字段是0,可以设定多个字段,例如“1,3,5”;设定为“*”表示所有字段。
在TableLayout中加入android:stretchColumns的设定,可以让指定的字段占用较大的空间。
[![AndroidTutorial5_02_02_10](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_10-300x126.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_10.png)
### 5-3-4 结合多种画面排列元件
上面说的LinearLayout、RelativeLayout和TableLayout,单独使用的时候,在设计一些比较复杂的画面时,就比较不符合需求。不过只要是Layout元件都可以搭配使用,所以在规画与设计应用程式的画面时,如果想要设计一个比较复杂的画面,应该要把画面上所有的元件切割成适合的区块,为每一个区块挑选一个合适的排列方式,最后再把它们结合起来。例如像这个计算机应用程式的画面,就会依照它们的需求使用LinearLayout和TableLayout组合成这样的画面:
[![AndroidTutorial5_02_02_11](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_11-300x203.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_11.png)
## 5-4 建立记事本应用程式主画面
了解Android应用程式画面的设计方式后,现在要回到记事本应用程式,为它设计一个用来显示所有资料的主画面:
[![AndroidTutorial5_02_02_12](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_12-300x217.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_12.png)
这个画面的需求并不会太复杂,使用LinearLayout画面配置元件就可以了,安排画面元件的时候,最好帮它们设定边界,元件才不会挤在一起。开启“res/values/dimens.xml”,加入需要的尺寸资源:
~~~
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="default_padding">6dp</dimen>
<dimen name="title_txt_size">24sp</dimen>
<!-- 加入边界尺寸资源设定 -->
<dimen name="default_margin">2dp</dimen>
</resources>
~~~
接下来开启“res/layout/activity_main.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:orientation="vertical"
tools:context=".MainActivity">
<ListView
android:id="@+id/item_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="@dimen/default_margin"
android:dividerHeight="1sp"
android:background="@drawable/retangle_drawable"
android:divider="@color/divider_color" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="@dimen/default_margin"
android:padding="@dimen/default_padding"
android:background="@drawable/retangle_drawable"
android:text="@string/app_name"/>
</LinearLayout>
~~~
执行这个应用程式,看看它在模拟装置中显示的画面。这个画面中的ListView是用来显示资料列表的元件,目前还没有为它设定任何资料,所以看起来是空白的。指定资料的工作必须在Activity元件的程式码执行,所以在画面配置档为ListView元件使用“android:id”为它设定一个名称。开启专案的“MainActivity.java”,修改为下面的内容:
~~~
package net.macdidi.at2;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 为ListView元件设定三笔资料
String[] data = {
"关于Android Tutorial的事情",
"一只非常可爱的小狗狗!",
"一首非常好听的音乐!"};
int layoutId = android.R.layout.simple_list_item_1;
ArrayAdapter adapter =
new ArrayAdapter(this, layoutId, data);
ListView item_list = (ListView) findViewById(R.id.item_list);
item_list.setAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
}
~~~
执行这个应用程式,检查ListView元件是否显示三笔资料:
[![AndroidTutorial5_02_02_13](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_13-167x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_02_13.png)
第二堂(1)规划与建立应用程式需要的资源
最后更新于:2022-04-01 00:52:45
第二堂的课程内容进入应用程式开发的阶段,从规划与建立应用程式的资源开始,接下来会说明Android最基本的Activity元件,还有设计应用程式需要的画面,最后帮应用程式加入让使用者操作与互动的功能。
Android行动装置在全世界已经是非常普及的随身电子设备,Android开发人员把应用程式上传到Google Play以后,全世界的使用者可以随时透过互联网下载与安装,在这样的情况下,应用程式就会有多国语言的需求了。应用程式通常会有各种需要显示文字的画面元件,例如TextView和Button,开发人员会希望这些文字可以使用装置的语言来显示。以提供确定功能的按钮元件来说,同样一个应用程式安装在繁体中文的装置,按钮上的文字应该显示“确定”,如果安装在英文的装置,就应该显示“OK”。
开发Android应用程式应该要先认识资源的规划与管理,建立好一点的设计习惯,对于应用程式的开发工作会有很大的帮助。为了让你在后续的学习可以得到最好的效果,这一堂课需要建立一Android应用程式专案,随着课程的进行,会依照课程内容依序完成一个完整的应用程式。参考下列的步骤建立一个Android应用程式专案:
1. 启动Android Studio,选择“Start a New Android Studio project”。
2. 在“Configure your new project”视窗,依照下列的说明输入需要的内容后,选择“Next”:
* Application Name输入“MyAndroidTutorial”
* Company Domain输入“macdidi.net”,或是你自己的网域名称。
* Project location选择一个储存专案的位置。
3. 勾选“Phone and Tablet”,Minimum SDK选择“API 15:Android 4.0.3(IceCreamSandwich)”,选择“Next”。
4. 在“Add an activity to Mobile”视窗选择“Blank Activity”后选择“Next”。
5. 在“Choose options for your new file”视窗采用默认的名称与设定,选择“Finish”完成建立应用程式的设定。
等候Android Studio启动新的应用程式视窗,就完成建立Android应用程式专案的工作。这个专案会从这里开始,一直持续使用到最后。每一段说明的内容,都会在这个专案加入或修改一些内容。
## 5-1 Android应用程式资源介绍
一个应用程式通常会包含许多画面,每一个画面中又会包含许多显示各种资讯的元件。应用程式通常也会提供使用者操作与互动的功能,这些工作需要撰写一些程式码来执行。如果是一个比较复杂的应用程式,就会包含很多画面、程式码和各种需要的内容。
为了简化应用程式的开发工作,Android平台采用目前比较普遍、也比较好的设计方式,把应用程式需要的程式码和其它需要的内容分开,开发人员在设计画面的时候,依照Android的设计规格提供一些“画面资源”,这些资源是XML格式的文件。应用程式需要使用的文字、阵列、颜色,也都会把它们设计为XML格式的资源档案。应用程式的程式码就只剩下基本与流程的工作,需要画面和文字的时候,就从XML格式的资源档案读进来使用。
### 5-1-1 应用程式专案的资源目录
建立一个Android应用程式专案以后,Android Studio会自动建立一些需要的专案目录,“res”的目录用来存放应用程式需要的各种资源,不同的资源要放在规定的目录下,例如画面配置档资源会放在“res/layout”。Android规定所有资源的种类和它们存放的目录,你一定要把资源放在对应的目录下。这些是Android应用程式专案在“res”目录下的各种资源目录:
* anim – 动画资源,XML格式档案,档案名称就是资源名称。
* color – 颜色状态资源,XML格式档案,档案名称就是资源名称。
* drawable – 图形与绘图资源,图形档案与XML格式档案,档案名称就是资源名称。
* layout – 画面配置资源,XML格式档案,档案名称就是资源名称。
* menu – 选单资源,XML格式档案,档案名称就是资源名称。
* raw – 档案资源,任何应用程式需要的档案,档案名称就是资源名称。
* values – 一般资源,包含文字、颜色、尺寸、阵列与样式资源,XML格式档案,每一个资源的名称在标签的设定中决定。
### 5-1-2 Android系统资源
Android系统已经内建许多资源,都是一些比较一般性与常用的资源,你的应用程式应该要优先使用系统提供的资源,如果没有的话再自己建立需要的资源,这样可以减少应用程式的大小,而且也比较方便一些。
Android内建的资源都宣告在API的“android.R”套件中,不论是系统或自己建立的资源,它们的分类方式都是一样的,会依照不同的种类使用“android.R.资源种类”的格式,例如系统内建的画面配置资源就放在“android.R.layout”套件。这些是宣告在“android.R”套件中常用的系统资源:
* android.R.anim – 系统动画资源。
* android.R.color – 系统颜色状态资源。
* android.R.dimen – 系统尺寸资源。
* android.R.drawable – 系统图形与绘图资源。
* android.R.layout – 系统画面配置资源。
* android.R.menu – 系统选单资源。
* android.R.string – 系统文字资源。
* android.R.style – 系统样式资源。
在建立应用程式需要的资源之前,应该要先认识系统内建的资源,在Android开发人员网站,提供和Java API文件同样格式的内容,方便开发人员查询Android平台提供的API,网址是[http://developer.android.com/reference/packages.html](http://developer.android.com/reference/packages.html)。选择在“android”套件下的“R”类别后,就可以看到所有系统内建的资源。例如选择“R.string”以后,可以看到宣告在这个类别里面的文字资源,一般应用程式常用的确定、取消文字,变量的名称是“ok”与“cancel”。
## 5-2 一般资源
Android把应用程式比较常用的一般资源放在“res/values”目录,这个目录可以建立需要的XML格式档案,在档案中使用规定的标签建立需要的资源,包含文字、颜色、尺寸、阵列和样式资源。XML档案名称只是用来规划与分类不同的资源,你可以把所有一般资源都放在同一个档案,不过一般会习惯使用这样的规划方式,在res/values目录建立这些档案储存不同种类的一般资源:
* strings.xml – 文字资源。
* colors.xml – 颜色资源。
* dimens.xml – 尺寸资源。
* arrays.xml – 阵列资源。
* styles.xml – 样式资源。
在“res/values”目录的XML档案,使用规定的格式建立一般资源,最外层使用“resources”标签,里面包含一般资源的标签:
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="文字資源名稱">文字資源</string>
<color name="顏色資源名稱">顏色資源</color>
<dimen name="尺寸資源名稱">尺寸資源</dimen>
<array name ="一般陣列資源名稱">
<item>一個陣列資源元素</item>
...
</array>
<string-array name="文字陣列資源名稱">
<item>一個陣列資源元素</item>
...
</string-array>
<integer-array name="數字陣列資源名稱">
<item>一個陣列資源元素</item>
...
</integer-array>
<style name="樣式">
<item name="設定名稱">設定值</item>
...
</style>
</resources>
~~~
### 5-2-1 建立文字资源
Android应用程式需要的文字,都不应该直接写在程式码或设定档里面,而是建立好文字资源以后再使用它们。以这里使用的范例来说,在规划应用程式的时候,就应该知道需要哪一些文字资源,例如这个新增记事本的画面:
[![AndroidTutorial5_02_01_01](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_01-176x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_01.png)
画面中需要的文字,根据之前的说明,“确定”和“取消”使用Android系统资源就可以了。建立文字资源使用“文字内容”的格式,开启Android Studio为你建立好的“res/values/strings.xml”档案,里面已经有app_name、title_activity_main与hello_world三个默认的文字资源,另外再加入这些需要的文字资源:
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">MyAndroidTutorial</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="title">標題</string>
<string name="enter_title">輸入標題</string>
<string name="content">內容</string>
<string name="enter_content">輸入內容</string>
</resources>
~~~
加入文字资源并储存盘案以后,就可以在其它XML档案(例如画面资源)或程式码中使用。在XML设定档使用“@string/文字资源名称”的格式设定文字资源。开启MyAndroidTutorial专案“res/layout/activity_main.xml”,选择预览画面的文字符件,在“Properties”区块找到“text”,选择设定值以后,再选择右测的设定按钮:
[![AndroidTutorial5_02_01_02](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_02-300x257.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_02.png)
在“Resources”选择对话框,选择名称为“title”的项目后选择“OK”:
[![AndroidTutorial5_02_01_03](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_03-300x221.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_03.png)
设定完成后可以看到预览画面上的文字已经变成“标题”,这是采用图形接口的设定方式。在编辑视窗选择“Text”标签,准备检视这个画面资源的内容:
[![AndroidTutorial5_02_01_04](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_04-300x292.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_04.png)
Android Studio显示activity_main.xml档案的内容,它是一个XML格式的档案。在这个画面可以看到刚才设定的内容:
[![AndroidTutorial5_02_01_05](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_05-300x153.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_05.png)
Android Studio在使用文字资源的部份,会直接显示文字资源的设定值。你可以把鼠标光标移到这个设定上面,就会显示原始的内容:
[![AndroidTutorial5_02_01_06](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_06-300x153.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_06.png)
或是点选这个设定以后,内容就会变成“@string/title”,你也可以直接在这里输入文字资源的名称:
[![AndroidTutorial5_02_01_07](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_07-300x153.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_07.png)
执行这个应用程式,在模拟装置启动应用程式以后,可以在画面看到“标题”讯息。
如果在开发应用程式之前经过完整的规划,现在就可以建立好大部份需要的文字资源。在开发的过程需要新增或修改文字资源的时候,再开启文字资源档案执行异动的工作。
### 5-2-2 建立颜色资源
如果设计应用程式画面的时候,没有特别设定元件的颜色,画面看起来应该会很单调。需要为画面设定颜色的时候,也不应该直接写在程式码与设定档。例如浏览与检视记事本内容的画面:
[![AndroidTutorial5_02_01_08](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_08-175x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_08.png)[![AndroidTutorial5_02_01_09](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_09-175x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_09.png)
建立颜色资源使用“颜色内容”的格式。颜色资源的内容使用下面这些设定格式:
* #RGB – 使用0~9、A~F设定红绿蓝的配色,共256种颜色。
* #RRGGBB – 使用00~FF设定红绿蓝的配色,共65535种颜色。
* #ARGB – 第一码使用0~9、A~F设定透明度,0表示完全透明,就是看不到了,F表示完全不透明。
* #AARRGGBB – 使用00~FF设定透明度,00表示完全透明,FF表示完全不透明。
这个画面需要一些灰阶的颜色资源,用来设定元件的背景颜色。依照下列的步骤建立颜色资源档案:
1. 在“res/values”目录上按鼠标右键,选择“New -> Values resources file”。
2. 在“File name”输入“colors”后选择“OK”。
[![AndroidTutorial5_02_01_10](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_10-300x168.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_10.png)
在颜色资源档案加入应用程式需要的颜色资源:
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="light_grey">#CCCCCC</color>
<color name="grey">#AAAAAA</color>
<color name="divider_color">#DD999999</color>
</resources>
~~~
在XML设定档使用“@color/颜色资源名称”的格式设定颜色资源。开启“res/layout/activity_main.xml”,修改这个档案的内容:
~~~
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<!-- 加入背景顏色設定 -->
<TextView
android:text="@string/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/light_grey"/>
</RelativeLayout>
~~~
你可以在预览画面检视,或是执行这个应用程式,在模拟装置启动应用程式以后,可以看到“标题”文字符件的背景颜色已经变成灰色。
### 5-2-2 建立尺寸资源
应用程式的画面通常需要设定元件的大小与排列间隔,例如一些比较重要的文字,应该会希望显示在画面的时候可以大一些;还有元件与元件之间,通常会设定一些间隔,这样画面才不会挤成一团。建立尺寸资源使用“尺寸内容”的格式。尺寸资源的内容使用下面这些单位:
* px – 萤幕画素。
* dp – 每英吋画面,160dp为一英吋。
* sp – 和dp一样,不过会根据装置设定的字号自动调整。
* in – 英吋。
* mm – 公厘。
建立专案以后已经有默认的尺寸资源档,开启“res/values/dimens.xml”,加入应用程式需要的尺寸资源:
~~~
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<!-- 加入新的尺寸資源設定 -->
<dimen name="default_padding">6dp</dimen>
<dimen name="title_txt_size">24sp</dimen>
</resources>
~~~
在XML设定档中使用“@dimen/尺寸资源名称”格式设定尺寸资源。开启“res/layout/activity_main.xml”,修改这个档案的内容:
~~~
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<!-- 加入邊界與文字大小的設定 -->
<TextView
android:text="@string/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/light_grey"
android:padding="@dimen/default_padding"
android:textSize="@dimen/title_txt_size"/>
</RelativeLayout>
~~~
你可以在预览画面检视,或是执行这个应用程式,在模拟装置启动应用程式以后,可以看到讯息的大小与边界已经改变。
## 5-3 图形资源
为了要提供使用者更清楚和美观的应用程式画面,通常会在设计画面的时候使用一些图形,例如按钮或选单的图示。应用程式安装在装置以后,显示在应用程式列表中的图示也很重要,一个适合又好看的图示,通常会比只有使用文字好一些。应用程式需要的图形资源都会放在应用程式专案的“res/drawable”目录。
Android系统的图形资源可以接受PNG、JPG和GIF格式的图档,档案名称必须是小写的英文字母和底线,没有包含副档名的档案名称就是资源名称。在res/drawable目录也可以建立XML格式的图形资源,提供应用程式一些简单的几何图形和绘图效果,例如使用渐层色彩绘制的矩形,可以使用在设定画面元件的背景。
### 5-3-1 图档图形资源
经过应用程式规划的阶段,应该已经知道需要哪些图形资源,例如这个画面需要的按钮图示:
[![AndroidTutorial5_02_01_11](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_11-176x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_11.png)
应用程式需要的基本图形档案,可以在Android开发人员网站下载,网址是[http://developer.android.com/design/downloads/index.html](http://developer.android.com/design/downloads/index.html),在网页选择“Action Bar Icon Pack”:
[![AndroidTutorial5_02_01_12](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_12-300x107.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_12.png)
储存并解压缩下载的档案,在“Action Bar Icons/holo_light”目录,有许多可以参考使用的图示档案。如果是提供给按钮使用的图示,选择“drawable-mdpi”目录下的图档,大小为32X32。依照下列的步骤,把图档复制到应用程式专案:
1. 在档案总管选择需要的图档以后,选择“复制”。
2. 回到Android Studio,在“res/drawable”目录上按鼠标右键,选择“Paste”。
3. 选择“…/app/src/main/res/drawable”以后选择“OK”:
[![AndroidTutorial5_02_01_13](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_13-300x185.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_13.png)
4. 选择“OK”:
[![AndroidTutorial5_02_01_14](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_14-300x84.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_14.png)
5. 复制完成后,在res/drawable目录下就会出现图档的名称:
[![AndroidTutorial5_02_01_15](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_15-300x290.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_15.png)
将应用程式需要的图档复制到res/drawable目录以后,就可以在XML资源档案中用“@drawable/资源名称”的格式使用这些图形资源。开启“res/layout/activity_main.xml”,修改这个档案的内容:
~~~
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<TextView
android:text="@string/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/light_grey"
android:padding="@dimen/default_padding"
android:textSize="@dimen/title_txt_size"/>
<!-- 加入圖示按鈕 -->
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/alarm_icon" />
</RelativeLayout>
~~~
你可以在预览画面检视,或是执行这个应用程式,在模拟装置启动应用程式以后,可以看到画面中央多一个图示按钮。
### 5-3-2 绘图图形资源
一个应用程式提供的功能最好是能够很实用,让使用者操作与浏览资料的画面美观也非常重要,使用者当然希望应用程式的画面可以好看一些。在设计画面配置资源的时候,你可以为画面元件设定大小尺寸、文字与背景的颜色,让应用程式的画面看起来比较不会那么单调,不过这些基本的设定所呈现的效果也是有限的。想要让画面的设计比较不一样,可以不用到处找一些图形,也不用学习绘图软件,Android提供一种使用XML格式设计的图形资源,可以让你设计简单的绘图图形资源,把这种图形资源设定给画面元件当作背景,就可以让画面元件拥有独特的外观。
绘图图形资源是一个XML格式的档案,储存在专案的“res/drawable”目录,档案名称就是资源名称。这是设计绘图图形资源的格式:
~~~
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape=["rectangle" | "oval" | "line" | "ring"] >
<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
<gradient
android:angle="integer"
android:centerX="integer"
android:centerY="integer"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:useLevel=["true" | "false"] />
<padding
android:left="integer"
android:top="integer"
android:right="integer"
android:bottom="integer" />
<size
android:width="integer"
android:height="integer" />
<solid
android:color="color" />
<stroke
android:width="integer"
android:color="color"
android:dashWidth="integer"
android:dashGap="integer" />
</shape>
~~~
这个绘图图形资源档案的内容,最外层是“shape”标签,标签中的“android:shape”设定决定绘图的种类,可以设定为下列这些设定值:
* Retangle – 绘制矩形,可以搭配“corners”标签设定四边的圆角。
* Oval – 绘制椭圆形。
* Line – 绘制现条。
* Ring – 绘制圆环,在shape标签中控制绘制的效果。
你可以使用绘图图形资源,为画面元件设定一个特殊的样式。依照下列的步骤建立一个绘图图形资源档案:
1. 在“res/drawable”目录上按鼠标右键,选择“New -> Drawable resource file”。
2. 在“File name”输入“retangle_drawable”后选择“OK”。
修改retangle_drawable.xml为下面的内容:
~~~
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<corners
android:topLeftRadius="5sp"
android:topRightRadius="5sp"
android:bottomLeftRadius="5sp"
android:bottomRightRadius="5sp" />
<solid android:color="#AAAAAA"/>
</shape>
~~~
储存建立好的绘图图形资源档案,就可以在XML资源档案中用“@drawable/资源名称”的格式使用这些资源。开启“res/layout/activity_main.xml”,修改这个档案的内容:
~~~
<RelativeLayout 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"
tools:context="${packageName}.${activityClass}" >
<!-- 修改android:background的設定 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title"
android:background="@drawable/retangle_drawable"
android:padding="@dimen/default_padding"
android:textSize="@dimen/title_txt_size" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/alarm_icon" />
</RelativeLayout>
~~~
储存盘案以后,你可以在预览画面检视,或是执行这个应用程式后执行这个应用程式,在模拟装置启动应用程式以后,可以看到讯息呈现圆角的图形背景。
## 5-4 选单资源
一般的应用程式通常使用画面元件提供使用者执行功能操作,例如按钮或下拉式选单,这些元件会显示在画面上,让使用者可以随时操作它们。可是应用程式可能需要一些不是经常执行的功能,例如检查资料是否需要更新,或是显示关于应用程式的资讯,这些功能也可以在应用程式的画面中使用按钮来执行,不过就会觉得有点占用画面。所以这类不是一定经常操作的功能,大部份就会设计为应用程式的选单,在需要执行的时候,使用者按下选单按钮,在出现的功能表中选择要执行的功能。
[![AndroidTutorial5_02_01_16](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_16-175x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_16.png)
选单资源是一个XML格式的档案,放在专案的“res\menu”目录,档案名称就是它的资源名称,设定档最外层的标签是“menu”,里面可以包含设定选单项目的“item”标签,选单项目标签里面有这些设定值:
* android:id – 选单项目的资源名称。
* android:title – 选单项目的文字。
* android:icon – 选单项目的图示,指定一个图形资源。
* android:showAsAction – 设定选单项目的样式,可以设定为“ifRoom”、“never”、“withText”、“always”和“collapseActionView”。设定为never表示使用一般选单的样式,其它的设定将选单显示在Action Bar。
* android:onClick – 选择选单以后呼叫的方法名称。
Android Studio已经为你建立一个默认的选单资源,开启“res/menu/menu_main.xml”,把它修改为下面的内容:
~~~
<menu 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"
tools:context=".MainActivity">
<item
android:id="@+id/search_item"
app:showAsAction="always"
android:title="SEARCH"
android:icon="@android:drawable/ic_menu_search"/>
<item
android:id="@+id/add_item"
app:showAsAction="always"
android:title="ADD"
android:icon="@android:drawable/ic_menu_add"/>
<item
android:id="@+id/revert_item"
app:showAsAction="always"
android:title="REVERT"
android:icon="@android:drawable/ic_menu_revert" />
<item
android:id="@+id/delete_item"
app:showAsAction="always"
android:title="DELETE"
android:icon="@android:drawable/ic_menu_delete" />
<item
android:id="@+id/share_item"
app:showAsAction="always"
android:title="SHARE"
android:icon="@android:drawable/ic_menu_share" >
<menu>
<item
android:id="@+id/googleplus_item"
android:title="Google+" />
<item
android:id="@+id/facebook_item"
android:title="Facebook" />
</menu>
</item>
</menu>
~~~
Android Studio在Activity元件,已经加入设定选单的程式码。开启专案的“MainActivity.java”,删除默认的onOptionsItemSelected方法,其它的部份不用修改:
~~~
package net.macdidi.myandroidtutorial;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
}
~~~
选单不会在预览画面显示,所以执行这个应用程式,在模拟装置启动应用程式以后,可以看到设定好的选单。目前还没有为它们设定选择以后要执行的工作,所以在选择以后都没有任何反应。
## 5-5 设计与使用动态资源
在上面说明的过程中,你应该可以发现图形资源的ic_lanucher.png,还有尺寸资源的dimens.xml有多个档案:
[![AndroidTutorial5_02_01_17](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_17-254x300.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_17.png)
以文字资源来说,Android装置可以设定为不同的语言与地区,如果希望应用程式可以根据装置设定的语言与地区,自动使用不同的文字资源,就需要另外建立其它语言的文字资源。目前建立的文字资源是繁体中文的内容,如果需要提供英文的文字资源,依照下列的步骤新增资源档案:
1. 在“res/values”目录上按鼠标右键,选择“New -> Values resources file”。
2. 在“File name”输入“strings”,在Avaliable qualifiers选择“Language”后选择“>>”:
[![AndroidTutorial5_02_01_18](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_18-300x168.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_18.png)
3. 在Language选择“en:English”后选择“OK”:
[![AndroidTutorial5_02_01_19](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_19-300x168.png)](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_02_01_19.png)
完成上面的步骤以后,Android Studio建立一个新的英文文字资源档,把“res/values/strings.xml”的内容复制过来,把它改为英文的内容:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">MyAndroidTutorial</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="title">Title</string>
<string name="enter_title">Enter title</string>
<string name="content">Content</string>
<string name="enter_content">Enter content</string>
</resources>
~~~
储存盘案以后,这个专案就有默认的繁体中文与英文文字资源。执行这个应用程式,在模拟装置执行“设定”应用程式,选择“语言与输入设定->语言”,选择“English(United States)”以后,在重新执行应用程式,画面上的讯息会根据装置的语言设定显示对应的文字,原来画面上显示的“标题”会变成“Title”。如果希望应用程式支援更多语言,可以使用同样的作法新增其它语言的文字资源。