第16章 首頁畫面小工具與硬體介面 16-1 首頁畫面小工具-手機靜音切換 16-2 感測器與遊戲控制-跳跳球遊戲

Slides:



Advertisements
Similar presentations
第一單元 建立java 程式.
Advertisements

LED CUBE 預期規劃.
第13章 繪圖與多媒體 13-1 顯示圖檔-行動相簿 13-2 音樂播放-音樂播放器 13-3 影片播放-視訊播放器
第13章 繪圖與多媒體 13-1 顯示圖檔-行動相簿 13-2 音樂播放-音樂播放器 13-3 影片播放-視訊播放器
第15章 網路與通訊 15-1 WebView元件-行動瀏覽器 15-2 簡訊處理-我的簡訊 15-3 寄送電子郵件-郵件寄送工具
實驗五:多媒體播放器選單介面.
Part 2 開發Android應用程式的流程
位置與地圖應用 此投影片為講解Android如何取得定位經緯度和使用Google Map地圖.
第二章 JAVA语言基础.
Android + Web Service 建國科技大學 資管系 饒瑞佶 2017/3 V1.
厦门大学数据库实验室 报告人:谢荣东 导师:林子雨 2014年8月30日
Services of the Mobile and Use of Communication Network
實驗四:單位轉換程式.
第2章 建立Android應用程式 2-1 Java語言、XML文件與Android 2-2 建立第一個Android應用程式
第7章 Android文件与本地数据库(SQLite)
Chapter 13 Android 實戰演練.
Android + JUnit 單元測試 建國科技大學資管系 饒瑞佶 2012/8/19V4.
第14章 Google地圖與定位服務 14-1 定位服務-我在哪裡 14-2 地圖解碼服務-找出景點座標
實驗十三:顯示目前經緯度位置.
Ch06 再談選單元件 物件導向系統實務.
使用Android控制Arduino 史先强
第10章 App微信分享的实现 倚动实验室.
Android資料庫處理 Android智慧型手機程式設計 程式設計與應用班 建國科技大學 資管系 饒瑞佶 2012/4 V1
第一个Android程序 本讲大纲: 1、创建Android应用程序 2、Android项目结构说明 3、运行Android应用程序
第8章 Android内容提供者(ContentProvider)应用
Chapter 6 Advanced UI Design.
客戶端的檔案上傳 HtmlInputFile檔案控制項 上傳單一檔案 同時上傳多個檔案.
Ch5 Android應用程式的主要組成.
Chapter 7 Android應用元件 Android應用元件可以幫助我們獲得系統資源訊息(ActivityManager)、提供系統服務(Service)、搜尋系統服務(SearchManager)、監聽Intent訊息(Broadcast Receiver)以及資料共享(ContentProvider和ContentResolver)。
Android介面設計 Android智慧型手機程式設計 建國科技大學 資管系 饒瑞佶 2012/4 V1 2012/8 V2
ANDROID 中的 3D 繪圖 作者:陳鍾誠.
Chapter 6 進階UI設計.
第4章 Android生命周期.
第9章 使用意圖啟動活動與內建應用程式 9-1 意圖的基礎 9-2 使用意圖啟動活動
ANDROID PROGRAMMING2.
CH7 佈局、按鈕與文字編輯元件.
Android + Service 建國科技大學 資管系 饒瑞佶.
類別(class) 類別class與物件object.
實驗十四:顯示與控制地圖.
第2讲 移动应用开发基础知识(二) 宋婕
建立Android新專案 Android智慧型手機程式設計 程式設計與應用班 建國科技大學 資管系 饒瑞佶 2012/4 V1
第6章 建立Android使用介面 6-1 介面元件的基礎 6-2 Android的事件處理 6-3 按鈕元件 6-4 文字元件
Java 程式設計 講師:FrankLin.
生活智慧王 樹德科技大學 資訊工程系 指導教授 : 陳毓璋 教授 小組成員: 劉上緯 翁維廷 洪文財.
第一單元 建立java 程式.
實驗十一:待辦事項程式 (儲存在手機上).
主编:钟元生 赵圣鲁.
Android Application Component
Chapter 5 Basic UI Design.
實驗九:延續實驗八, 製作一個完整音樂播放器
Location Based Services - LBS
進階UI元件:Spinner與接合器 靜宜大學資管系 楊子青
第二章 Java语法基础.
Broadcasts (廣播) 靜宜大學資管系 楊子青
Video 影像 (VideoPlayer 影像播放器、Camcorder 錄影機) 靜宜大學資管系 楊子青
Chapter 15 檔案存取 LabVIEW中的檔案存取函數也可將程式中的資料儲存成Excel或Word檔。只要將欲存取的檔案路徑位址透過LabVIEW中的路徑元件告訴檔案存取函數後,LabVIEW便可將資料存成Excel或Word檔;當然也可以將Excel或Word檔的資料讀入LabVIEW的程式中。
第二章 Java基本语法 讲师:复凡.
進階UI元件:ListView元件以及複選 靜宜大學資管系 楊子青
RecyclerView and CardView
Android藍芽聊天室 SDK內的範例程式
班級:博碩子一甲 授課老師:鐘國家 助教:陳國政
Android Speech To Text(STT)
Activity的生命週期: 播放音樂與影片 靜宜大學資管系 楊子青
用Intent啟動程式中的其他Activity、運用WebView顯示網頁 靜宜大學資管系 楊子青
第2章 Java语言基础.
第9章 BroadcastReceiver的使用
加速感測器 靜宜大學資管系 楊子青.
SQLite資料庫 靜宜大學資管系 楊子青.
Part 8 Broadcast Receiver、Service和App Widget
進階UI元件:Spinner與接合器 靜宜大學資管系 楊子青
Presentation transcript:

第16章 首頁畫面小工具與硬體介面 16-1 首頁畫面小工具-手機靜音切換 16-2 感測器與遊戲控制-跳跳球遊戲 16-3 相機-行車記錄器 16-4 相機與感測器的應用-聰明相機 16-5 藍牙-掃描藍牙裝置

16-1 首頁畫面小工具-手機靜音切換 16-1-1 顯示今天日期小工具 16-1-2 小工具與IntentService服務-手機靜音切換

16-1 首頁畫面小工具-手機靜音切換 首頁畫面小工具(Home Screen Widget)也稱為應用程式小工具(App Widget)是位在行動裝置的首頁畫面中,可以與之互動的一種程式,其主要功能是提供使用者重要的更新資訊,例如:顯示目前行程、今天日期、現在時間、即時天氣、即時股票行情和背景播放音樂的詳細資料等。 當我們將小工具新增至首頁畫面後,它會佔用一塊固定區域來顯示應用程式提供的內容,使用者一樣可以透過小工具與應用程式進行互動,例如:手機靜音切換、切換WiFi、暫停或切換至下一首音樂等,如果擁有背景服務,我們還可以定時更新小工具顯示的內容。

16-1-1 顯示今天日期小工具 步驟一:開啟和執行Android Studio專案 顯示今天日期小工具是一個在首頁畫面顯示今天日期的小工具,它是使用紅色粗體的文字來顯示今天日期。 請啟動Android Studio開啟專案Ch16_1_1,專案本身的活動類別並沒有任何功能,只是顯示一段文字內容,其執行結果如右圖所示:

16-1-1 顯示今天日期小工具 步驟二:建立小工具的定義檔 建立小工具的首要工作是建立位在「res\xml」目錄下XML定義檔appwidgetprovider.xml,此檔案定義小工具尺寸和更新頻率,在AndroidManifest.xml檔註冊小工具時也需參考此檔案,如下所示: <?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="40dp" android:minHeight="40dp" android:updatePeriodMillis="1800000" android:initialLayout="@layout/widget"/>

16-1-1 顯示今天日期小工具 步驟三:建立小工具介面的版面配置 如同活動,我們需要替小工具建立位在「res\layout」目錄的版面配置檔widget.xml,如下所示: <?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"> <TextView android:id="@+id/widgettext" android:layout_height="wrap_content" android:gravity="center" android:textStyle="bold" android:textColor="#f00"/> </LinearLayout>

16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類別覆寫相關方法1 首頁畫面小工具事實上就是一個廣播接收器,收到廣播後更新小工具顯示的內容,在Android SDK提供AppWidgetProvider提供者類別,我們可以直接繼承AppWidgetProvider類別覆寫相關方法來建立小工具,如下所示: public class DateAppWidget extends AppWidgetProvider { private SimpleDateFormat formatter = new SimpleDateFormat("\n\n\nyyyy年\nMM月dd日"); ..... }

16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類別覆寫相關方法2 onDeleted()方法 當刪除1或多個小工具實例後,也就是將首頁畫面的小工具移至垃圾桶,AppWidget管理員物件會送出ACTION_APPWIDGET_DELETED 廣播,onDeleted()方法可以回應此廣播,程式碼只是使用Toast類別顯示呼叫此方法的訊息文字,如下所示: @Override public void onDeleted(Context context, int[] appWidgetIds) { Toast.makeText(context, "onDeleted()", Toast.LENGTH_LONG).show(); }

16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類別覆寫相關方法3 onDisabled()方法 當刪除屬於此小工具的最後1個物件實例後,就會送出ACTION_APPWIDGET_DISABLED廣播,onDisabled()方法可以回應此廣播,程式碼只是使用Toast類別顯示呼叫此方法的訊息文字,如下所示: @Override public void onDisabled(Context context) { Toast.makeText(context, "onDisabled()", Toast.LENGTH_LONG).show(); }

16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類別覆寫相關方法4 onEnabled()方法 當小工具初始化新增至首頁畫面後,就會送出ACTION_APPWIDGET_ENABLED廣播,onEnabled()方法可以回應此廣播,程式碼只是使用Toast類別顯示呼叫此方法的訊息文字,如下所示: @Override public void onEnabled(Context context) { Toast.makeText(context, "onEnabled()", Toast.LENGTH_LONG).show(); }

16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類別覆寫相關方法5 onUpdate()方法 當小工具被要求更新RemoteViews物件的介面元件時,就會送出ACTION_APPWIDGET_UPDATE廣播,它就是在定義檔使用android:updatePeriodMillis 屬性指定的更新頻率,onUpdate()方法可以回應此廣播,更新小工具顯示的內容,如下所示: @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); String today = formatter.format(new Date()); RemoteViews remoteView = new RemoteViews( context.getPackageName(), R.layout.widget); remoteView.setTextViewText(R.id.widgettext, today); appWidgetManager.updateAppWidget(appWidgetIds, remoteView); Toast.makeText(context, "onUpdate()", Toast.LENGTH_LONG).show(); }

16-1-1 顯示今天日期小工具 步驟五:在AndroidManifest.xml註冊小工具 <receiver android:name=".DateAppWidget" android:label="@string/app_name" android:icon="@drawable/ic_launcher"> <intent-filter> <action android:name= "android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidgetprovider" /> </receiver>

16-1-2 小工具與IntentService服務- 手機靜音切換1 因為首頁畫面本身是一個在行動裝置上執行,名為啟動器的應用程式,小工具只是在首頁畫面中特定區域執行的程式,因為Android作業系統並不允許開發者修改執行中的程式碼,小工具為了能夠更新內容和與使用者互動,使用的是RemoteViews介面元件架構。

16-1-2 小工具與IntentService服務- 手機靜音切換2 RemoteViews介面元件架構允許在首頁畫面建立遠端控制的介面元件,換句話說,在首頁畫面顯示的是獨立行程執行的遠端介面元件,實際處理此介面的程式就是繼承AppWidgetProvider提供者類別的物件。 當使用者在小工具的遠端介面進行互動時,例如:按一下,Android作業系統就像是一個路由器,可以將此廣播轉向送至小工具來處理,例如:更新遠端介面元件的內容。

16-1-2 小工具與IntentService服務-手機靜音切換 步驟一:開啟和執行Android Studio專案 請啟動Android Studio開啟專案Ch16_1_2,專案的活動類別本身並沒有任何功能,只是顯示一段文字內容,其執行結果如右圖所示:

16-1-2 小工具與IntentService服務-手機靜音切換 步驟二:建立小工具的定義檔 此步驟的定義檔和第16-1-1節的步驟二完全相同,筆者就不重複說明。

16-1-2 小工具與IntentService服務-手機靜音切換 步驟三:建立小工具介面的版面配置 小工具的版面配置檔widget.xml是位在「res\layout」目錄,使用RelativeLayout編排一個ImageView元件,如下所示: <ImageView android:id="@+id/phoneState" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_centerInParent="true" android:src="@drawable/ic_launcher" android:clickable="true" /> 上述android:clickable屬性值為true,表示介面元件可以產生Click事件。

16-1-2 小工具與IntentService服務-手機靜音切換 步驟四:繼承AppWidgetProvider類別覆寫相關方法1 public class SilentAppWidget extends AppWidgetProvider { ..... }

16-1-2 小工具與IntentService服務-手機靜音切換 步驟四:繼承AppWidgetProvider類別覆寫相關方法2 onRecieve()方法 通常覆寫onReceive()方法是用來呼叫AppWidgetProvider類別的其他方法,在此是處理使用者第1次在首頁畫面新增小工具時,能夠更新成目前的鈴聲狀態,if條件是呼叫Intent物件的getAction()方法檢查是否有動作,沒有,就呼叫startService()方法啟動ToggleSilentService服務來更新鈴聲狀態,如下所示: @Override public void onReceive(Context context, Intent intent) { if (intent.getAction() == null) { context.startService(new Intent(context, ToggleSilentService.class)); } else { super.onReceive(context, intent); }

16-1-2 小工具與IntentService服務-手機靜音切換 步驟四:繼承AppWidgetProvider類別覆寫相關方法3 onUpdate()方法 在onUpdate()方法呼叫參數Context物件的startService()方法啟動ToggleSilentService服務,如下所示: @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { context.startService(new Intent(context, ToggleSilentService.class)); }

16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-1 IntentService類別是Service類別的子類別,可以用來處理非同步Intent意圖的請求,每一個Intent物件是新增至佇列後再依序處理,即啟動執行緒來處理每一個Intent物件,當任務完成後就自動停止服務,並且可以使用廣播方式來將資料送回應用程式。 一般來說,當我們有任務需要在主執行緒外,啟動其他執行緒來執行此任務,以維持應用程式的執行效能時,或有多個處理請求,需要使用佇列來一一快速處理時,就可以繼承IntentService類別來建立服務,如下所示: public class ToggleSilentService extends IntentService { public ToggleSilentService() { super("ToggleSilentService"); } …

16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-2 onHandleIntent()方法 onHandleIntent()方法是負責處理啟動服務的Intent物件,即處理步驟四呼叫startService()方法啟動服務的方法參數,執行緒是在請求行程後就會呼叫此方法,在每一個時間只有一個Intent物件會處理,如下所示: @Override protected void onHandleIntent(Intent arg0) { ComponentName cn = new ComponentName( this, SilentAppWidget.class); AppWidgetManager awManager = AppWidgetManager.getInstance(this); awManager.updateAppWidget(cn, updatePhoneStatus(this)); }

16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-3 updatePhoneStatus()方法 updatePhoneStatus()方法的傳回值是RemoteViews物件,它是使用AUDIO_SERVICE系統服務來更新手機的鈴聲狀態,方法首先從版面配置資源載入來建立RemoteViews物件,如下所示: private RemoteViews updatePhoneStatus(Context context) { RemoteViews remoteView = new RemoteViews( context.getPackageName(), R.layout.widget); AudioManager manager = (AudioManager) context.getSystemService(Activity.AUDIO_SERVICE);

16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-4 if (manager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) { remoteView.setImageViewResource( R.id.phoneState, R.drawable.phone_on); manager.setRingerMode( AudioManager.RINGER_MODE_NORMAL); } else { R.id.phoneState, R.drawable.phone_silent);

16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-5 manager.setRingerMode( AudioManager.RINGER_MODE_SILENT); } Intent i = new Intent(this, SilentAppWidget.class); PendingIntent pi = PendingIntent.getBroadcast( context, 0, i, 0); remoteView.setOnClickPendingIntent(R.id.phoneState,pi); return remoteView;

16-1-2 小工具與IntentService服務-手機靜音切換 步驟六:在AndroidManifest.xml註冊小工具與服務 在AndroidManifest.xml檔註冊小工具與服務,也就是註冊廣播接收器和ToggleSilentService服務,如下所示: <service android:name=".ToggleSilentService" > </service> <receiver android:name=".SilentAppWidget" > <intent-filter> <action android:name= "android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidgetprovider" /> </receiver>

16-2 感測器與遊戲控制-跳跳球遊戲 16-2-1 傾斜監測 16-2-2 感測器與遊戲控制-跳跳球遊戲

16-2 感測器與遊戲控制-跳跳球遊戲 Android支援多種感測器來監測行動裝置目前的狀態,例如:數位羅盤、加速感測器、重力感測器、趨近感測器、陀螺儀和環境光線感測器等,請注意!行動裝置可能只支援其中幾項感測器,而且Android模擬器不支援感測器,我們只能使用實機來測試感測器。 在實務上,我們最常使用「加速感測器」(Accelerometer),所以本節是以加速感測器為例,說明如何應用在遊戲控制。

16-2-1 傾斜監測 傾斜監測是使用加速感測器判斷行動裝置目前是否傾斜。當我們取得感測器系統服務的ServiceManager物件後,就可以註冊SensorEventListener傾聽者物件來取得感測器的偵測資料,為了避免耗用過多電力,建議在onResume()方法註冊;onPause()方法取消註冊。

16-2-1 傾斜監測 步驟一:開啟和執行Android Studio專案 請啟動Android Studio開啟專案Ch16_2_1,內含1個Java類別檔和版面配置檔activity_main.xml,因為Android模擬器不支援感測器,筆者是使用實機進行測試,其執行結果如右圖所示:

16-2-1 傾斜監測 步驟二:建立使用介面的版面配置-1 <TableLayout android:layout_width="match_parent" android:layout_height="match_parent" android:stretchColumns="0,1,2"> <TableRow android:layout_weight="1"> <TextView android:id="@+id/lblTop" android:layout_column="1"/> </TableRow> <TextView android:id="@+id/lblLeft" android:layout_column="0"/> <TextView android:id="@+id/lblRight" android:layout_column="2"/>

16-2-1 傾斜監測 步驟二:建立使用介面的版面配置-2 <TableRow android:layout_weight="1"> <TextView android:id="@+id/lblBottom" android:layout_column="1"/> </TableRow> </TableLayout> <TextView android:id="@+id/lblOutput" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true"/>

16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-1 在MainActivity活動類別實作SensorEventListener介面的2個方法,類別開頭宣告成員的SensorManager、Sensor和TextView物件變數,如下所示: public class MainActivity extends ActionBarActivity implements SensorEventListener { private SensorManager manager; private Sensor accelerometer; private TextView output, top, bottom, left, right; …... }

16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-2 onCreate()方法 在覆寫的onCreate()方法載入版面配置後,就可以取得感測器系統服務的SensorManager物件manager,如下所示: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); manager = (SensorManager) getSystemService( SENSOR_SERVICE); accelerometer = manager.getDefaultSensor( Sensor.TYPE_ACCELEROMETER); txtOutput = (TextView) findViewById(R.id.output); txtTop = (TextView) findViewById(R.id.top); txtBottom = (TextView) findViewById(R.id.bottom); txtLeft = (TextView) findViewById(R.id.left); txtRight = (TextView) findViewById(R.id.right); }

16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-3 onResume()方法 在覆寫onResume()方法註冊SensorEventListener傾聽者物件,即自己,如下所示: @Override protected void onResume() { super.onResume(); manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL); }

16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-4 onPause()方法 在覆寫onPause()方法取消註冊SensorEventListener傾聽者物件,如下所示: @Override protected void onPause() { super.onPause(); manager.unregisterListener(this); }

16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-5 實作SensorEventListener介面的方法 SensorEventListener傾聽者物件需要實作2個介面方法,不過,我們只有使用onSensorChanged()方法,這是當感測器資料改變時呼叫的方法,如下所示: @Override public void onAccuracyChanged(Sensor arg0, int arg1) { } public void onSensorChanged(SensorEvent event) { float[] values = event.values; float x, y; int xFactor, yFactor; x = values[0] / 10; y = values[1] / 10;

16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-6 xFactor = (int) Math.min(Math.abs(x) * 255, 255); yFactor = (int) Math.min(Math.abs(y) * 255, 255); if (x > 0) { txtRight.setBackgroundColor(Color.TRANSPARENT); txtLeft.setBackgroundColor( Color.argb(xFactor, 255, 255, 0)); } else { txtRight.setBackgroundColor(Color.argb(xFactor, 255, 255, 0)); Color.TRANSPARENT); } if (y > 0) {

16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-7 txtTop.setBackgroundColor(Color.TRANSPARENT); txtBottom.setBackgroundColor( Color.argb(yFactor, 255, 255, 0)); } else { txtTop.setBackgroundColor( txtBottom.setBackgroundColor(Color.TRANSPARENT); } txtOutput.setText(String.format( "X軸: %1$1.2f, Y軸: %2$1.2f, Z軸: %3$1.2f", values[0], values[1], values[2]));

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟一:開啟和執行Android Studio專案 加速感測器最常應用在遊戲程式,這一節我們準備使用加速感測器來移動螢幕上的黃色球,可以傾斜行動裝置來控制球的滾動方向。 請啟動Android Studio開啟專案Ch16_2_2,內含2個Java類別檔和版面配置檔activity_main.xml,因為Android模擬器不支援感測器,筆者是使用實機測試,其執行結果如右圖所示:

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟二:建立使用介面的版面配置 使用介面的版面配置是定義在activity_main.xml版面配置檔,只有一個FrameLayout版面配置,如下所示: <FrameLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/gameboard"> </FrameLayout>

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:隱藏活動的動作列 在活動介面隱藏動作列是使用佈景來隱藏,請更改「res\values」目錄下styles.xml檔案的應用程式佈景,如下所示: <resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> </style> </resources> 上述Theme.AppCompat.Light.NoActionBar最後的NoActionBar就是隱藏動作列。

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-1 MainActivity活動類別實作SensorEventListener介面的2個方法,在類別開頭宣告成員的SensorManager、Sensor、重繪的Handler、計時的Timer、TimerTask和PointF物件變數,如下所示: public class MainActivity extends ActionBarActivity implements SensorEventListener { private SensorManager manager; private Sensor accelerometer; private MyBallView ball = null; private Handler redrawHandler = new Handler(); private Timer moveTimer = null; private TimerTask moveTask = null; private int sWidth, sHeight; private PointF ballPos, ballSpeed; …... }

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-2 onCreate()方法 在覆寫onCreate()方法的開始是使用requestWindowFeature()方法來隱藏標題列,如下所示: @Override public void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(0xFFFFFFFF, LayoutParams.FLAG_FULLSCREEN | LayoutParams.FLAG_KEEP_SCREEN_ON); super.onCreate(savedInstanceState); setContentView(R.layout.main); final FrameLayout board = (FrameLayout) findViewById(R.id.gameboard);

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-3 Display display = getWindowManager().getDefaultDisplay(); sWidth = display.getWidth(); sHeight = display.getHeight(); ballPos = new PointF(); ballSpeed = new PointF(); ballPos.x = sWidth / 2; ballPos.y = sHeight / 2; ballSpeed.x = 0; ballSpeed.y = 0; ball = new MyBallView(this, ballPos.x, ballPos.y, 10); board.addView(ball); ball.invalidate();

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-4 manager = (SensorManager) getSystemService( SENSOR_SERVICE); accelerometer = manager.getDefaultSensor( Sensor.TYPE_ACCELEROMETER); board.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, android.view.MotionEvent e) { ballPos.x = e.getX(); ballPos.y = e.getY(); return true; } });

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-5 onResume()方法 在覆寫onResume()方法首先註冊SensorEventListener傾聽者物件,即自己後,建立Timer計時器物件來移動黃色球至新位置,如下所示: @Override public void onResume() { manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL); moveTimer = new Timer(); moveTask = new TimerTask() {

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-6 public void run() { Log.d("Ch16_2_2","更新時間 - " + ballPos.x + ":" + ballPos.y); ballPos.x += ballSpeed.x; ballPos.y += ballSpeed.y; float oX = 10 * Math.abs(ballSpeed.x); float oY = 10 * Math.abs(ballSpeed.y); if (ballPos.x > sWidth) ballPos.x -= oX; if (ballPos.y > sHeight) ballPos.y -= oY; if (ballPos.x < 0) ballPos.x += oX;

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-7 if (ballPos.y < 0) ballPos.y += oY; ball.updatePosition(ballPos.x, ballPos.y); redrawHandler.post(new Runnable() { public void run() { ball.invalidate(); } }); }}; moveTimer.schedule(moveTask, 10, 10); super.onResume();

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-8 onPause()方法 在覆寫onPause()方法呼叫cancel()方法取消Timer計時器物件和取消註冊SensorEventListener傾聽者物件,如下所示: @Override public void onPause() { moveTimer.cancel(); moveTimer = null; moveTask = null; super.onPause(); manager.unregisterListener(this); }

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立Activity活動類別使用加速感測器-9 實作SensorEventListener介面的方法 SensorEventListener傾聽者物件需要實作介面的2個方法,我們只有使用onSensorChanged()方法,如下所示: @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } public void onSensorChanged(SensorEvent event) { ballSpeed.x = -event.values[0]; ballSpeed.y = event.values[1]; }

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟五:建立畫出紅色球的View介面類別-1 public class MyBallView extends View { private float x; private float y; private final int r; private final Paint paint = new Paint( Paint.ANTI_ALIAS_FLAG); public MyBallView(Context context, float x, float y, int r) { super(context); paint.setColor(Color.RED); this.x = x; this.y = y; this.r = r; }

16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟五:建立畫出黃色球的View介面類別-2 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(x, y, r, paint); } public void updatePosition(float x, float y) { this.x = x; this.y = y;

16-3 相機-行車記錄器 16-3-1 照相-我的相機 16-3-2 錄影-行車記錄器

16-3-1 照相-我的相機(說明1) 雖然Android作業系統本身就內建相機程式(可以使用Intent物件啟動),如果應用程式需要內建相機功能,我們就可以整合本節程式來提供基本的照相功能。 Android SDK的android.hardware.Camera類別(不是android.graphics.Camera類別)是硬體相機的介面類別,相機服務的客戶端類別,可以照相、截取圖片、預覽圖片和更改相關設定。

16-3-1 照相-我的相機(說明2) 請注意!Camera API是Android 5.0之前版本的照相功能,Android 5.0版API 21之後建議使用全新android.hardware.camera2 API,為了與舊版相容,本節仍然是使用Camera API,在Android Studio程式碼編輯器會在Camera類別上加上刪除線,但是並不會影響程式的編譯與執行。 在本節【我的相機】程式提供兩種照相功能:一是使用Intent物件啟動內建相機程式,然後將照相結果顯示在ImageView元件;另一是使用SurfaceView元件來預覽畫面,和Camera類別照相和儲存至SD卡。

16-3-1 照相-我的相機 步驟一:開啟和執行Android Studio專案 請啟動Android Studio開啟專案Ch16_3_1,內含2個Java類別檔和2個版面配置檔,因為Android模擬器的相機模擬功能不佳,筆者是使用實機測試照相功能,其執行結果如右圖所示:

16-3-1 照相-我的相機 步驟二:建立主活動使用介面的版面配置 在主活動使用介面的版面配置是定義在activity_main.xml版面配置檔,使用LinearLayout垂直編排2個Button和1個ImageView元件,如下圖所示:

16-3-1 照相-我的相機 步驟三:建立MainActivity主活動類別-1 在MainActivity活動類別按鈕的事件處理方法,可以啟動內建相機程式來照相,或是直接在程式進行照相,在類別開頭定義常數和宣告成員的ImageView物件變數,如下所示: public class MainActivity extends ActionBarActivity { private static final int REQUEST_IMAGE = 100; private ImageView imageView; …... }

16-3-1 照相-我的相機 步驟三:建立MainActivity主活動類別-2 onCreate()方法 在覆寫onCreate()方法載入版面配置後,取得ImageView元件,如下所示: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView)findViewById(R.id.imageView); }

16-3-1 照相-我的相機 步驟三:建立MainActivity主活動類別-3 onActivityResult()方法 覆寫onActivityResult()方法可以取得相機程式回傳的照片資料,在建立成Bitmap物件後,在ImageView元件顯示照片,如下所示: @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_IMAGE && resultCode == Activity.RESULT_OK) { Bitmap userImage = (Bitmap) data.getExtras().get("data"); imageView.setImageBitmap(userImage); }

16-3-1 照相-我的相機 步驟三:建立MainActivity主活動類別-4 button~2_Click()事件處理方法 Button元件的事件處理,都是建立Intent物件來啟動活動,在button_Click()方法啟動內建相機程式,可以傳回照片結果,如下所示: public void button_Click(View view) { Intent intent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(intent, REQUEST_IMAGE); } public void button2_Click(View view) { Intent intent = new Intent(this, CameraView.class); startActivity(intent);

16-3-1 照相-我的相機 步驟四:建立照相活動使用介面的版面配置 照相活動使用介面的版面配置是定義在activity_cameraview.xml版面配置檔,使用LinearLayout編排1個SurfaceView元件,如下所示: <SurfaceView android:id="@+id/cameraview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1"> </SurfaceView> 上述SurfaceView元件是一個可以在其中繪圖的元件,我們使用此元件來動態顯示從相機硬體取得的預覽串流畫面。

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-1 CameraView照相活動類別實作SufaceHolder.Callback介面和OnClickListener介面,在類別開頭宣告成員的Camera、SurfaceView和SurfaceHolder物件變數,如下所示: public class CameraView extends Activity implements SurfaceHolder.Callback, OnClickListener { private Camera camera; boolean isPreviewRunning = false; …... }

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-2 onCreate()方法 在覆寫onCreate()方法載入版面配置前,指定螢幕為全螢幕顯示且橫向,如下所示: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); setContentView(R.layout.activity_cameraview);

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-3 SurfaceView surfaceview = (SurfaceView) findViewById(R.id.cameraview); surfaceview.setOnClickListener(this); SurfaceHolder surfaceHolder = surfaceview.getHolder(); surfaceHolder.addCallback(this); }

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-4 建立PictureCallback物件 我們是使用匿名內層類別來建立PictureCallback物件(PictureCallback是介面),這是當拍攝照片後,用來支援取得的影像資料,類別需要實作onPictureTaken()介面方法,如下所示: Camera.PictureCallback pictureCallback = new Camera.PictureCallback() { public void onPictureTaken(byte[] imageData, Camera c) { if (imageData != null) { saveImage(CameraView.this, imageData, 50); camera.startPreview(); } };

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-5 實作SurfaceHolder.Callback介面方法 活動類別實作SurfaceHolder.Callback介面,可以接收SurfaceView元件的改變來進行處理,當第1次建立SurfaceView元件就馬上呼叫surfaceCreated()方法,我們在此方法開啟相機,如下所示: public void surfaceCreated(SurfaceHolder holder) { camera = Camera.open(); }

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-6 我們需要呼叫surfaceChanged()方法指定尺寸與格式,如下所示: public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { if (isPreviewRunning) { camera.stopPreview(); } Camera.Parameters p = camera.getParameters(); p.setPreviewSize(w, h); camera.setParameters(p);

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-7 在下方try/catch例外處理呼叫setPreviewDisplay()方法指定SurfaceHolder物件擁有的SurfaceView元件作為動態預覽,如下所示: try { camera.setPreviewDisplay(holder); } catch (IOException e) { e.printStackTrace(); } camera.startPreview(); isPreviewRunning = true;

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-8 當釋放SurfaceView元件的資源後,就會呼叫surfaceDestroyed()方法,如下所示: public void surfaceDestroyed(SurfaceHolder holder) { camera.stopPreview(); isPreviewRunning = false; camera.release(); }

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-9 實作OnClickListener介面方法 活動類別實作OnClickListener介面來執行拍攝,即點選螢幕,就呼叫onClick()方法執行相機的拍攝,如下所示: public void onClick(View arg0) { camera.takePicture(null, pictureCallback, pictureCallback); }

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-10 saveImage()方法 saveImage()方法是我們新增的成員方法,可以將取得的影像資料儲存JPEG格式的圖檔,首先建立File物件的儲存路徑,如下所示: public boolean saveImage(Context mContext, byte[] imageData, int quality) { File path = new File("/sdcard"); try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 5; Bitmap image = BitmapFactory. decodeByteArray(imageData, 0, imageData.length,options);

16-3-1 照相-我的相機 步驟五:建立CameraView照相活動類別-11 FileOutputStream fos = new FileOutputStream( path.toString() +"/image.jpg"); BufferedOutputStream bos = new BufferedOutputStream(fos); image.compress(CompressFormat.JPEG, quality, bos); bos.flush(); bos.close(); } catch (Exception e) { e.printStackTrace(); } return true;

16-3-1 照相-我的相機 步驟六:在AndroidManifest.xml註冊活動和新增權限 因為程式新增CameraView照相活動,所以AndroidManifest.xml檔需要註冊此活動,如下所示: <activity android:name=".CameraView" android:label="@string/app_name"/> 而且程式需要使用到相機和將資料儲存在SD卡,所以需要新增CAMERA和WRITE_EXTERNAL_STORAGE權限,如下所示: <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE"/>

16-3-2 錄影-行車記錄器 步驟一:開啟和執行Android Studio專案 請啟動Android Studio開啟專案Ch16_3_2,內含4個Java類別檔和2個版面配置檔,筆者是使用實機來測試錄影功能,其執行結果如下圖所示:

16-3-2 錄影-行車記錄器 步驟二:建立主活動使用介面的版面配置 主活動使用介面的版面配置是定義在activity_main.xml版面配置檔,使用LinearLayout垂直編排3個Button和1個TextView元件,如下圖所示:

16-3-2 錄影-行車記錄器 步驟三:建立MainActivity主活動類別-1 在MainActivity活動類別提供按鈕的事件處理方法,可以啟動內建相機程式來錄影和直接進行錄影,在類別開頭定義常數,和宣告成員的File物件變數,如下所示: public class MainActivity extends ActionBarActivity { private static final int REQUEST_VIDEO_CAPTURE = 101; private File file; private String fileName = "myVideo.3gp"; …... }

16-3-2 錄影-行車記錄器 步驟三:建立MainActivity主活動類別-2 onCreate()方法 在覆寫onCreate()方法載入版面配置後,建立儲存檔案路徑的File物件,如下所示: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); file = new File(Environment. getExternalStorageDirectory(), fileName); }

16-3-2 錄影-行車記錄器 步驟三:建立MainActivity主活動類別-3 onActivityResult()方法 覆寫onActivityResult()方法可以取得相機程式回傳儲存的檔案路徑,然後在TextView元件顯示此路徑,如下所示: @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == Activity.RESULT_OK) { String path = data.getData().toString(); TextView output = (TextView) findViewById(R.id.lblFile); output.setText(path); }

16-3-2 錄影-行車記錄器 步驟三:建立MainActivity主活動類別-4 button_Click、button2_Click、button3_Click()事件處理方法 public void button1_Click(View view) { Intent intent = new Intent(this, VideoRecorder.class); startActivity(intent); } public void button2_Click(View view) { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); startActivityForResult(intent, REQUEST_VIDEO_CAPTURE); public void button3_Click(View view) { Intent intent = new Intent(this, VideoPlayer.class);

16-3-2 錄影-行車記錄器 步驟四:建立VideoRecorder錄影活動類別-1 在VideoRecorder錄影活動類別開頭宣告MediaRecorder物件的成員變數,如下所示: public class VideoRecorder extends ActionBarActivity { private MediaRecorder recorder; …... }

16-3-2 錄影-行車記錄器 步驟四:建立VideoRecorder錄影活動類別-2 onCreate()方法 在覆寫onCreate()方法建立MediaRecorder物件和指定錄影參數,如下所示: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); recorder = new MediaRecorder(); recorder.setVideoSource( MediaRecorder.VideoSource.CAMERA); recorder.setOutputFormat( MediaRecorder.OutputFormat.THREE_GPP); recorder.setVideoEncoder( MediaRecorder.VideoEncoder.MPEG_4_SP);

16-3-2 錄影-行車記錄器 步驟四:建立VideoRecorder錄影活動類別-3 onCreate()方法 然後在下方建立錄影預覽的VideoPreview物件(類別宣告在下一步驟),如下所示: preview = new VideoPreview(this,recorder); setRequestedOrientation(ActivityInfo. SCREEN_ORIENTATION_LANDSCAPE); setContentView(preview); } 上述程式碼指定橫向顯示後,指定活動的使用介面為VideoPreview物件(所以此活動沒有版面配置檔)。

16-3-2 錄影-行車記錄器 步驟四:建立VideoRecorder錄影活動類別-4 onPrepareOptionsMenu()方法 在覆寫onCreateOptionsMenu()方法建立動作列選單,如下所示: @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_videorecorder, menu); return super.onCreateOptionsMenu(menu); }

16-3-2 錄影-行車記錄器 步驟四:建立VideoRecorder錄影活動類別-5 onPrepareOptionsMenu()方法 在XML選項檔的內容只有1個【停止錄影】選項,如下所示: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/stop" app:showAsAction="ifRoom|withText" android:title="停止錄影"/> </menu>

16-3-2 錄影-行車記錄器 步驟四:建立VideoRecorder錄影活動類別-6 onOptionsItemSelected()方法 在覆寫onOptionsItemSelected()方法處理選項選單的選項,如為R.id.stop,就呼叫stop()方法停止錄影,如下所示: @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.stop: if (recorder != null) { recorder.stop(); recorder.release(); recorder = null; } break; return super.onOptionsItemSelected(item);

16-3-2 錄影-行車記錄器 步驟五:建立預覽錄影的VideoPreview類別-1 預覽錄影VideoPreview類別是繼承SurfaceView類別且實作SurfaceHolder.Callback介面,在類別開頭宣告MediaRecorder物件變數,如下所示: public class VideoPreview extends SurfaceView implements SurfaceHolder.Callback { private MediaRecorder recorder; ...... }

16-3-2 錄影-行車記錄器 步驟五:建立預覽錄影的VideoPreview類別-2 在第1個VideoPreview()過載建構子只是呼叫父類別的建構子,在第2個取得參數MediaRecorder物件,如下所示: public VideoPreview(Context context) {super(context);} public VideoPreview(Context context, MediaRecorder recorder) { super(context); this.recorder = recorder; SurfaceHolder holder = getHolder(); holder.addCallback(this); }

16-3-2 錄影-行車記錄器 步驟五:建立預覽錄影的VideoPreview類別-3 實作SurfaceHolder.Callback介面方法 活動類別實作SurfaceHolder.Callback介面的3個方法,不過,我們只使用前2個方法,在surfaceCreated()方法指定輸出檔案路徑,和預覽檢視是getSurface()方法傳回的SurfaceView物件,如下所示: @Override public void surfaceCreated(SurfaceHolder holder) { recorder.setPreviewDisplay(holder.getSurface()); recorder.setOutputFile( Environment.getExternalStorageDirectory() .getPath()+"/myVideo.3gp"); try { recorder.prepare(); // 準備錄影 recorder.start(); // 開始錄影 } catch (Exception e) { e.printStackTrace(); recorder.release(); recorder = null; }

16-3-2 錄影-行車記錄器 步驟五:建立預覽錄影的VideoPreview類別-4 實作SurfaceHolder.Callback介面方法 在下面surfaceDestroyed()方法釋放MediaRecorder物件佔用的資源,如下所示: @Override public void surfaceDestroyed(SurfaceHolder holder) { if (recorder != null) { recorder.release(); recorder = null; } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

16-3-2 錄影-行車記錄器 步驟六:建立播放錄影的活動類別與版面配置 VideoPlayer活動類別可以播放錄影檔案,它是在版面配置檔videoplayer.xml的VideoView元件播放,即第13-3節的視訊播放器,所以筆者就不多作說明。

16-3-2 錄影-行車記錄器 步驟七:在AndroidManifest.xml註冊活動和新增權限1 因為行車記錄器新增VideoRecorder和VideoPlayer活動,所以在AndroidManifest.xml檔需要註冊這2個活動,如下所示: <activity android:name=".VideoPlayerActivity" android:label="@string/title_activity_video_play" > </activity> <activity android:name=".VideoRecorder" android:label="@string/app_name">

16-3-2 錄影-行車記錄器 步驟七:在AndroidManifest.xml註冊活動和新增權限2 程式需要錄影、使用到相機和將資料儲存在SD卡,所以需要新增RECORD_VIDEO、CAMERA和WRITE_EXTERNAL_STORAGE權限,如下所示: <uses-permission android:name="android.permission.RECORD_VIDEO" /> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE"/>

16-4 相機與感測器的應用-聰明相機(執行) 在本節的範例專案是相機與感測器的整合應用,筆者使用加速感測器偵測的數值來判斷行動裝置的相機是否拿歪。

16-4 相機與感測器的應用-聰明相機(程式碼1) MainActivity類別是修改自第16-3-1節的CameraView照相活動類別,關於照相部分筆者就不重複說明,至於如何判斷相機是否拿歪,它是在活動的onCreate()方法取得感測器的系統服務和使用加速感測器,如下所示: manager = (SensorManager) getSystemService(SENSOR_SERVICE); accelerometer = manager.getDefaultSensor( Sensor.TYPE_ACCELEROMETER); mListener = new MySensorListener(); 上述程式碼建立MySensorListner傾聽者物件後,在onResume()方法註冊,如下所示: manager.registerListener(mListener, accelerometer, SensorManager.SENSOR_DELAY_UI); 在onPause()和onStop()方法取消註冊,如下所示: manager.unregisterListener(mListener);

16-4 相機與感測器的應用-聰明相機(程式碼2) 最後宣告MySensorListner傾聽者類別實作SensorEventListener介面,我們只有使用onSensorChanged()方法,如下所示: class MySensorListener implements SensorEventListener { @Override public void onSensorChanged(SensorEvent event) { if (event.sensor != accelerometer) return; if (event.values[0] > 9.4 || event.values[1] > 9.4) { output.setText("相機是正的.."); } else { output.setText("相機是歪的..");

16-5 藍牙-掃描藍牙裝置 藍牙(Bluetooth)是一種使用在電腦、行動裝置和其他家電用品上的無線傳輸技術,最初是由易利信公司開發,後來由藍牙技術聯盟訂定其技術標準。 藍牙的運作原理是在2.45GHz頻帶上進行資料傳輸,資料可以加密保護,因為每一分鐘變頻1600次且不受電磁波干擾,所以是一種十分安全的資料傳輸方式。藍牙除了傳送數位資料外,也可以傳送聲音,每一個藍牙技術連接的裝置擁有IEEE 802標準制定的48-bits地址,可以進行一對一或一對多的連接,其傳輸範圍最遠可達10公尺,傳輸量可達每秒1MB。

16-5 藍牙-掃描藍牙裝置 步驟一:開啟和執行Android Studio專案 請啟動Android Studio開啟專案Ch16_5,內含1個Java類別檔和版面配置檔activity_main.xml,因為Android模擬器不支援藍牙,筆者是使用實機測試,其執行結果如下圖所示:

16-5 藍牙-掃描藍牙裝置 步驟二:建立使用介面的版面配置 使用介面的版面配置是定義在activity_main.xml版面配置檔,依序編排1個TextView、Button和ListView元件,如下圖所示:

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-1 在MainActivity活動類別是使用匿名內層類別建立廣播接收器來新增找到的藍牙裝置,在類別開頭定義常數,和宣告成員的ListView、TextView、BluetoothAdapter和ArrayAdapter物件變數,如下所示: public class MainActivity extends ActionBarActivity { private static final int REQUEST_ENABLE_BLUETOOTH = 1; private ListView listDevices; private TextView state; private BluetoothAdapter btAdapter; private ArrayAdapter<String> btArrayAdapter; …... }

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-2 onCreate()方法 在覆寫的onCreate()方法載入版面配置後,依序取得TextView和ListView元件,如下所示: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); state = (TextView)findViewById(R.id.state); listDevices = (ListView)findViewById(R.id.listdevices); btArrayAdapter = new ArrayAdapter<String>( MainActivity.this, android.R.layout.simple_list_item_1); listDevices.setAdapter(btArrayAdapter); btAdapter = BluetoothAdapter.getDefaultAdapter(); checkBluetoothState(); registerReceiver(BluetoothFoundReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND)); }

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-3 onDestroy()方法 在覆寫onDestroy()方法取消註冊廣播接收器,如下所示: @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(BluetoothFoundReceiver); }

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-4 btnScan_Click()事件處理方法 在Button元件的事件處理方法呼叫BluetoothAdapter物件的startDiscovery()方法開始掃描藍牙裝置,如下所示: public void btnScan_Click(View view) { btArrayAdapter.clear(); btAdapter.startDiscovery(); }

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-5 checkBluetoothState()方法 checkBluetoothState()方法可以檢查行動裝置的藍牙狀態,如果沒有開啟,就使用Intent物件開啟藍牙,首先檢查BluetoothAdapter物件是否是null,以判斷行動裝置是否支援藍牙,如下所示: private void checkBluetoothState() { if (btAdapter == null){ state.setText("行動裝置不支援藍牙..."); } else {

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-6 if (btAdapter.isEnabled()) { if (btAdapter.isDiscovering()) { state.setText("目前正在掃描藍牙裝置..."); } else { state.setText("行動裝置藍牙已啟用..."); Button btnScan = (Button) findViewById(R.id.btnScan); btnScan.setEnabled(true);

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-7 else { state.setText("行動裝置藍牙沒有啟用..."); // Intent i = new Intent(BluetoothAdapter. ACTION_REQUEST_ENABLE); startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH); }

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-8 onActivityResult()方法 在覆寫的onActivityResult()方法取得回傳值,方法只是再次呼叫checkBluetoothState()方法來檢查行動裝置的藍牙狀態,如下所示: @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_ENABLE_BLUETOOTH ) { checkBluetoothState(); }

16-5 藍牙-掃描藍牙裝置 步驟三:建立Activity活動類別掃描藍牙裝置-9 建立廣播接收器物件 程式碼是使用匿名內層類別建立廣播接收器物件來新增找到的藍牙裝置,類別覆寫onReceive()方法,如下所示: private final BroadcastReceiver BluetoothFoundReceiver = new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra( BluetoothDevice.EXTRA_DEVICE); btArrayAdapter.add(device.getName() + "\n" + device.getAddress()); btArrayAdapter.notifyDataSetChanged(); } };

16-5 藍牙-掃描藍牙裝置 步驟四:在AndroidManifest.xml新增存取藍牙權限 因為需要掃描藍牙裝置,所以在AndroidManifest.xml檔需要新增BLUETOOTH和BLUETOOTH_ADMIN權限,如下所示: <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name= "android.permission.BLUETOOTH_ADMIN"/>

End