第六堂(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”。如果希望应用程式支援更多语言,可以使用同样的作法新增其它语言的文字资源。
';