android 智慧型手機程式設計
TRANSCRIPT
ANDROID智慧型⼿手機程式設計
思創軟體 / 林彥宏資深軟體⼯工程師http://lyhcode.info
基礎實作課程
建⽴立開發環境
ADT Bundle
• developer.android.com/sdk
• Eclipse IDE with built-in ADT (Android Developer Tools)
DDMSDalvik Debug Monitor Server
Android Screen Monitor
http://goo.gl/mnMeaS
java -jar asm.jar
ctrl + 1 = 100%ctrl + 7 = 75%ctrl + 5 = 50%
縮放
執⾏行
AirDroid
Install “AirDroid”from
Google Play Store
線上免費課程 from Coursera
Programming Mobile Applications for Android Handheld Systems
Android App 原始碼範例
https://github.com/aporter/coursera-android
從 GitHub 網站下載
第⼀一課
打造使⽤用者介⾯面
ActivityLife Cycle
UI
友善的
UI 的設計⽅方式
• 程序式(procedural)在程式碼動態產⽣生畫⾯面 UI 元件
• 宣告式(declarative)在 Layout XML 定義畫⾯面 UI 元件
指尖操作⾏行為
手機程式的調色盤
Holo����������� ������������������ Colors����������� ������������������ Generator����������� ������������������
Action����������� ������������������ Bar����������� ������������������ Style����������� ������������������ Generator����������� ������������������
Adobe����������� ������������������ Color����������� ������������������ CC
Create a new Android Project
• Target SDK: Android 4.x
• Using Blank Activity
設定 API Level
• 編輯AndroidManifest.xml
• 設定 API Level 版本代號 <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16" />
Android API Level 對照表
• Change Layout => LinearLayout
• Add RadioButton
• Add Button
• Add TextView
畫⾯面佈置
• 修改 res/layout/activity_main.xml
• 調整 layout_width 為 fill_parent <Button android:id="@+id/button1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="showLocation" android:text="查詢地點" />
• Wrap Radio Buttonswith RadioGroup
Click Event
1
2
3
<Button ... android:onClick=“clickOpenMap” ...
public void clickOpenMap(View view) { ...
button1.setOnClickListener(new Button.OnClickListener() { ...
4
class MyButtonListener implements OnClickListener
public class MainActivity extends Activity implements OnClickListener
按鈕點擊事件的四種處理⽅方式
使⽤用 OnClickListener 類別
1. button1.setOnClickListener(new Button.OnClickListener() { 2. @Override 3. public void onClick(View v) { 4. ... 5. } 6. });
按鈕點擊後會執⾏行 onClick() ⽅方法
Show Location in TextView
1. public void showLocation(View view) {2. switch (radioGroup1.getCheckedRadioButtonId()) {3. case R.id.radioButton1:4. textView1.setText("澄清湖的位置是 22.660665,120.3509745");5. break;6. case R.id.radioButton2:7. textView1.setText("⻄西⼦子灣的位置是 22.629167,120.262778");8. break;9. }10.}
Intent
1. Intent intent = new Intent(); 2. intent.setClass( 3. MainActivity.this, 4. MainActivity2.class); 5. startActivity(intent);
參考資料• http://ccckmit.wikidot.com/ga:intentexample
常⾒見的 Intent ⽤用法
• http://google.com
• geo:22.660,120.350
• tel: 0800080123
打開網站
打開地圖
撥打電話
Open Map with Intent
1. public void openMap(View view) {2. Uri uri;3. Intent intent;4. switch (radioGroup1.getCheckedRadioButtonId()) {5. case R.id.radioButton1:6. uri = Uri.parse("geo:22.660,120.350");7. intent = new Intent(Intent.ACTION_VIEW, uri);8. startActivity(intent);9. break;10. }11.}
Intent Example⼿手機常⽤用功能快捷鍵
程式實作
【學習⺫⽬目標】1. Button2. Intent
RadioButton Example景點查詢⼯工具
程式實作
【學習⺫⽬目標】1. RadioButton2. TextView3. Intent
第⼆二課
顯⽰示訊息與偵錯記錄Logging / Debugging
Dialog對話框
彈出後等待使⽤用者確認後才消失
Dialog Example
1. AlertDialog.Builder builder;2. builder = new AlertDialog.Builder(view.getContext());
4. builder5. .setTitle(“Something Wrong")6. .setMessage(“Incorrect value, try again.”)7. .setNeutralButton("OK", null)8. .create()9. .show();
}builderstyle
Toast
Toast.makeText(view.getContext(), "You've 3 messages.", Toast.LENGTH_LONG);
message body
停留時間彈出後過幾秒⾃自動消失
訊息記錄(LOG)
• 給程式開發與測試⼈人員觀察程式執⾏行狀態
• 測試與除錯階段的重要資訊來源
輸出除錯訊息
System.out.println
Log.println tagpriority
使⽤用標準輸出(stdout)
使⽤用 Android 系統的 Log 機制
缺點:無法對訊息進⼀一步分類
訊息類型PRIORITY 名稱 ⽤用途
DEBUG 僅供除錯
INFORMATION ⼀一般訊息
WARNING 資料或處理異常
ERROR 嚴重的錯誤;可能導致程式終⽌止
使⽤用 Log 類別
Log.println(Log.WARN, "MyActivity", "User account not exists.");
tagpriority message body
Tag 的命名慣例
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
Log.println(Log.WARN, TAG, "User account not exists.");…
通常使⽤用該記錄存在的類別名稱(Class Name)
Log 類別提供的⽅方法簡寫
• v(String, String) (verbose) • d(String, String) (debug) • i(String, String) (information) • w(String, String) (warning) • e(String, String) (error)
依其重要程度(優先度)區分
}常⽤用
使⽤用 LogCat 監看除錯訊息
依照 Priority 篩選過濾
Dialog and Toast Example景點查詢⼯工具
程式實作
【學習⺫⽬目標】1. Dialog / Toast2. Log
第三課
多執⾏行緒與網路存取
應⽤用程式沒有回應
• 程式執⾏行超過五秒,UI 仍未能與使⽤用者互動
• 常發⽣生在:
• 處理複雜的演算法
• 等待網路傳輸資料
"Application Not Responding" (ANR)
處理 ANR 的⽅方法
• Thread / Runnable
• AsyncTask
• Handler
多執⾏行緒程式設計(Multithreading)
AsyncTask 範例
1. private class MyTask extends AsyncTask<Void, Void, Void> { 2. protected Long doInBackground(...) { 3. ... 4. } 5. }
Keeping Your App Responsivehttp://developer.android.com/training/articles/perf-anr.html
程式實作
• 修改 res/layout/activity_main.xml
• 加⼊入 Button 「顯⽰示地圖」
• 加⼊入 ImageView 在畫⾯面下⽅方
activity_main.xml
<ImageView android:id="@+id/imageView1" android:layout_width="fill_parent" android:layout_height="150dp" android:src="@drawable/ic_launcher" />
<Button android:id="@+id/button3" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="showMap" android:text="顯⽰示地圖" />
Button
ImageView
findViewById
public class MainActivity extends Activity {
private RadioGroup radioGroup1;private TextView textView1;private ImageView imageView1;
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
radioGroup1 = (RadioGroup) this.findViewById(R.id.radioGroup1);textView1 = (TextView) this.findViewById(R.id.textView1);imageView1 = (ImageView) this.findViewById(R.id.imageView1);
}
權限設定使⽤用 Manifest 權限設定⼯工具
權限設定(XML定義)
• 編輯 AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
實作 showMap ⽅方法
public void showMap(View view) {
switch (radioGroup1.getCheckedRadioButtonId()) {case R.id.radioButton1:
//...
break;}
}
lat 與 lng 變數
Double lat = null, lng = null;
switch (radioGroup1.getCheckedRadioButtonId()) {case R.id.radioButton1:
lat = 22.660;lng = 120.350;
break;}
if (lat != null && lng != null) { //... }
AsyncTask 實作if (lat != null && lng != null) {
String url = "...";
new AsyncTask<String, Void, Drawable>() {@Overrideprotected Drawable doInBackground(String... params) {
try {InputStream is = new URL(params[0]).openStream();return Drawable.createFromStream(is, "src");
} catch (Exception ex) {ex.printStackTrace();
}return null;
}
@Overrideprotected void onPostExecute(Drawable result) {
if (result != null) {imageView1.setImageDrawable(result);
}super.onPostExecute(result);
}}.execute(url);
}
2. 點擊「顯⽰示地圖」
1. 選擇景點
3. 地圖顯⽰示
加⼊入「查詢天氣」按鈕
<Button android:id="@+id/button4" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="showWeather" android:text="查詢天氣" />
showWeather ⽅方法實作public void showWeather(View view) {
Double lat = null, lng = null;
switch (radioGroup1.getCheckedRadioButtonId()) {case R.id.radioButton1:
lat = 22.660;lng = 120.350;
break;case R.id.radioButton2:
lat = 22.629;lng = 120.262;
break;}
}
OpenWeatherMap API
http://openweathermap.org
Web Services 存取天氣資料
http://api.openweathermap.org/data/2.5/weather?lat=22.629&lon=120.262&&units=metric
http://api.openweathermap.org/data/2.5/weather?q=Taipei,TW&units=metric
OpenWeatherMapWeb Services APIAndroid App
HTTP Request
Data ResponseXML, JSON, HTML, …
AsyncTask 實作
if (lat != null && lng != null) {String url = "...";
new AsyncTask<String, Void, String>() {@Overrideprotected String doInBackground(String... params) {
//...}
@Overrideprotected void onPostExecute(String json) {
//...}
}.execute(url);}
存取 HTTP Web Services
protected String doInBackground(String... params) {try {
BufferedReader reader = new BufferedReader( new InputStreamReader(new URL(params[0]).openStream()));
String json = reader.readLine();reader.close();return json;
} catch (Exception ex) {ex.printStackTrace();
}return null;
}
解析 JSON 資料
protected void onPostExecute(String json) {if (json != null) {
try {JSONObject jsonObject = new JSONObject(json);Double temp = jsonObject.getJSONObject("main").getDouble("temp");textView1.setText(temp + "℃");
}catch (Exception ex) {
ex.printStackTrace();}
}super.onPostExecute(json);
}
2. 點擊「查詢天氣」
1. 選擇景點
3. 顯⽰示地區氣溫
第四課
建⽴立新專案 MyBrowser
activity_main.xml
MainActivitypublic class MainActivity extends Activity {
EditText editText1;WebView webView1;Button button1;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText1 = (EditText) this.findViewById(R.id.editText1); webView1 = (WebView) this.findViewById(R.id.webView1); button1 = (Button) this.findViewById(R.id.button1); }
權限設定
WebView 設定
//設定只⽤用 WebView 開啟網址 webView1.setWebViewClient(new WebViewClient()); //打開 JavaScript webView1.getSettings().setJavaScriptEnabled(true);
按鈕 onClick 設定
<Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/editText1" android:layout_alignRight="@+id/webView1" android:text="好⼿手氣" android:onClick="loadWebPage" />
public void loadWebPage(View view) {
webView1.loadData("Loading...", "text/plain", "UTF-8"); String keyword = editText1.getText().toString(); try {
keyword = URLEncoder.encode(keyword, "UTF-8");} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch blocke.printStackTrace();
}
String url = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=" + keyword;
new AsyncTask<String, Void, String>() {@Overrideprotected String doInBackground(String... params) {
return null;}
@Overrideprotected void onPostExecute(String json) {}
}.execute(url); }
doInBackground
@Overrideprotected String doInBackground(String... params) {
try {InputStream stream = new URL(params[0]).openStream();InputStreamReader isr = new InputStreamReader(stream, "UTF-8");BufferedReader reader = new BufferedReader(isr);String json = reader.readLine();return json;
} catch (Exception ex) {ex.printStackTrace();
}return null;
}
onPostExecute
@Overrideprotected void onPostExecute(String json) {
if (json != null) {try {
JSONObject jsonObject = new JSONObject(json);
JSONArray results = jsonObject.getJSONObject("responseData").getJSONArray("results");
if (results.length() > 0) {String url = results.getJSONObject(0).getString("url");webView1.loadUrl(url);
}else {
webView1.loadData("Not found...", "text/plain", "UTF-8");}
}catch (Exception ex) {
ex.printStackTrace();}
}}
儲存紀錄
this.getPreferences(MODE_PRIVATE).edit().putString("LAST_QUERY", editText1.getText().toString());
讀取記錄
editText1.setText(this.getPreferences(MODE_PRIVATE).getString("LAST_QUERY", "PCHome"));
建⽴立新專案
2D 繪圖
setContentView(new View(this) { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setStyle(Paint.Style.FILL); paint.setColor(Color.WHITE); canvas.drawPaint(paint); paint.setColor(Color.GREEN); canvas.drawCircle(100, 100, 20, paint); } });
使⽤用 Canvas 製作動畫
• 使⽤用 onDraw 更新畫⾯面
• 缺點:每次都要重新繪製整個畫⾯面
public class MyView extends View implements Runnable {
private Handler handler = new Handler();
private int x = 0;private int y = 0;
private int offsetX = 5;private int offsetY = 5;
public MyView(Context context) {super(context);
this.setFocusable(false);
new Thread(this).start();}
@Overrideprotected void onDraw(Canvas canvas) {}
@Overridepublic void run() {}
}
@Overrideprotected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();paint.setStyle(Paint.Style.FILL);paint.setColor(Color.WHITE);canvas.drawPaint(paint);paint.setColor(Color.GREEN);canvas.drawCircle(x+=offsetX, y+=offsetY, 20, paint);
if (x > canvas.getWidth() || x < 0) {offsetX = -offsetX;
}if (y > canvas.getHeight() || y < 0) {
offsetY = -offsetY;}
}
@Overridepublic void run() {
while (true) {handler.post(new Runnable() {
@Overridepublic void run() {
invalidate();}
});}
}
Paint paint = new Paint();paint.setStyle(Paint.Style.FILL);paint.setColor(Color.BLACK);canvas.drawPaint(paint);
int clockX = canvas.getWidth() / 2;int clockY = canvas.getHeight() / 2;int clockRadius = (canvas.getWidth() - 150) / 2; paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(3);paint.setColor(Color.RED);canvas.drawCircle(clockX, clockY, clockRadius, paint);
paint.setStyle(Paint.Style.FILL);paint.setColor(Color.RED);canvas.drawCircle(clockX, clockY, 10, paint);
private double getAngle(int index, int size) {return Math.PI / size * 2 * (index - size / 4);
}
private int getStopX(int x, double angle, int radius) {return (int)(x + Math.cos(angle) * radius);
}
private int getStopY(int y, double angle, int radius) {return (int)(y + Math.sin(angle) * radius);
}