tag:blogger.com,1999:blog-65012482000928711582024-02-20T07:05:36.975-08:00路西華備忘錄Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.comBlogger67125tag:blogger.com,1999:blog-6501248200092871158.post-81567600974655827752013-09-06T00:17:00.002-07:002013-09-06T00:17:21.953-07:00如何在 ADT 中開發 Google Map V2<h2>
<span style="color: blue;">1. 在 ADT 中安裝 Google Play Service</span></h2>
<br />
打開 ADT 中的 Android SDK Manager, 將 Extra 中的 Google Play service 勾選安裝。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsoWShsPfMnl6s6Jm3sci7jq-wcXs9ZpSmT4FZpVvcsQX67WVIj1GopO82zDclA8TjNbIDo8KHfPn_NbBqlaqUhc0wXtxJZYVHoqi1i6H4mjEb0IFW2MvYL0mu_Bx7grlHj8_0Oif8B3om/s1600/map1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="454" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsoWShsPfMnl6s6Jm3sci7jq-wcXs9ZpSmT4FZpVvcsQX67WVIj1GopO82zDclA8TjNbIDo8KHfPn_NbBqlaqUhc0wXtxJZYVHoqi1i6H4mjEb0IFW2MvYL0mu_Bx7grlHj8_0Oif8B3om/s640/map1.PNG" width="640" /></a></div>
<br />
<br />
<a name='more'></a><br />
<br />
選取 Accept License, 然後 Install。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiz9JP3axtnj4XJ5Vc4aZw1LDbX3x_-yhyphenhyphenIbc1YR_4N0GqXC3wIX8hT-zatsHdBNq8y4Iqy_R1uJDhvWIWK4H3Sa-Po9H1OTcF27F9fsrQstjFsgC4h5Znt2V6LmqOm_QC-9ChYl9_C0Lza/s1600/map2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="402" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiz9JP3axtnj4XJ5Vc4aZw1LDbX3x_-yhyphenhyphenIbc1YR_4N0GqXC3wIX8hT-zatsHdBNq8y4Iqy_R1uJDhvWIWK4H3Sa-Po9H1OTcF27F9fsrQstjFsgC4h5Znt2V6LmqOm_QC-9ChYl9_C0Lza/s640/map2.PNG" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
接下來我們要把 Google Play service 匯入到專案中, 我們可以在 ADT 中選 File -> Import 開啟Import 畫面, 然後選取 "Existing Android Code Into Workspace"開啟匯入專案畫面。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjaM4Pj4snKE5oct65WfEVuXTW4t4pQfhBqdfmGiwcfuEIP-TY2yvRPwkzqlYclOVzVYVbQophzce-dO6YYQR2THhwJw5qADeT3Spe-0FvWdmGpAYNXsFoEIxmt0BVT2BD88QDP8YRTpsX/s1600/map3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjaM4Pj4snKE5oct65WfEVuXTW4t4pQfhBqdfmGiwcfuEIP-TY2yvRPwkzqlYclOVzVYVbQophzce-dO6YYQR2THhwJw5qADeT3Spe-0FvWdmGpAYNXsFoEIxmt0BVT2BD88QDP8YRTpsX/s640/map3.PNG" width="610" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
在這個畫面我們選取 Google Play service 的安裝路徑, 預設路徑會在 : {ADT 路徑}\sdk\extras\google\google_play_services\libproject\google-play-services_lib。</div>
<div class="separator" style="clear: both; text-align: left;">
選好路徑後在 Projects 中勾選起來, 要不要把這個專案 copy 到你的 workspace 就看你自己囉。選完後選 Finish 完成。完成後就可以在專案瀏覽器中看你 google-play-serives_lib 的專案。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOHB7yda-OXlH1nyojjdRjp5uFkvXIOdw6myzhyphenhyphen7v19h9mRxa0EaW0ZFXVR-8VPOpO0veRv-HGHgcbsyDmBo6jLNmPxTV7drvsDV7MCsKVkIKFtt_IDtfz1wty6Xv4uY_1Xt-BA6JvYxxo/s1600/map4.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOHB7yda-OXlH1nyojjdRjp5uFkvXIOdw6myzhyphenhyphen7v19h9mRxa0EaW0ZFXVR-8VPOpO0veRv-HGHgcbsyDmBo6jLNmPxTV7drvsDV7MCsKVkIKFtt_IDtfz1wty6Xv4uY_1Xt-BA6JvYxxo/s640/map4.PNG" width="628" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
接下來我們要讓我們要開發的專案去參考 google-play-services_lib 這個專案。我們可以在專案名稱上安滑鼠右鍵, 選取 Properties, 然後在 Android 分類中先按下 Add, 這時會開啟另一個視窗, 在視窗中點選 google-play-services_lib 項目。記得將 Is Library 勾選起來, 然後按下 Apply 套用, 最後按下 OK 鍵就完成了。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkKeDqLQLTArFx6XKXbfIy0ldgPsUUklEvvMOGaIeWi5-OMr2_3ua1bc-SAAmpepamvyuy-BkWXge1FpeC4kiBYHmnByFzRYvImvNoEMgZ4iGQThIvrySLx7jFRpb8WY6L4-hXxIzdTFXu/s1600/map5.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkKeDqLQLTArFx6XKXbfIy0ldgPsUUklEvvMOGaIeWi5-OMr2_3ua1bc-SAAmpepamvyuy-BkWXge1FpeC4kiBYHmnByFzRYvImvNoEMgZ4iGQThIvrySLx7jFRpb8WY6L4-hXxIzdTFXu/s640/map5.PNG" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
<span style="color: blue;">2. 申請 Google Map API Key</span></h2>
<div>
先用自己的 gmail 帳號登入 <a href="https://code.google.com/apis/console" target="_blank">Google API Console 網站</a> , 登入後在 Services 中找出 Google Maps Android API v2, 將其開啟為 ON。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmFUCXBboz9CbLA9hfVYt5sAmZfnhQgGu2MmRn8iQIpEV2TNaqCKgyVwS_GWCs2I23n0UeObymEDTNIv4mjDf08uv9muX7eS4yZ5PRT5q6MqMDhp4Wbep9kFiByXlYQY4ph8XG_-Rk797f/s1600/map6.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="134" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmFUCXBboz9CbLA9hfVYt5sAmZfnhQgGu2MmRn8iQIpEV2TNaqCKgyVwS_GWCs2I23n0UeObymEDTNIv4mjDf08uv9muX7eS4yZ5PRT5q6MqMDhp4Wbep9kFiByXlYQY4ph8XG_-Rk797f/s640/map6.PNG" width="640" /></a></div>
<div>
<br /></div>
<div>
接下來我們要找出自己開發用的 debug.keystore, 我們可以在 ADT 中的 Windows->Preferences 中開啟下面這個畫面, 我們可以直接在 Android 分類中的 Build 項目中找出 SHA1 fingerprint, 那就是我們要的值。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFPmUTLHPs5DVvKIafcvURfKV7i3ujTr9JPQyWYVcD5ixZ6pRZ1DmncgFDV7GTB_1iYb2byTdNtHxLKUC-kgDQp69QYX2MMwsG3uoYLnZ29ualet-ARi3wvJogPe3oqFlWSMY7jbNPcr1a/s1600/map7.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="354" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFPmUTLHPs5DVvKIafcvURfKV7i3ujTr9JPQyWYVcD5ixZ6pRZ1DmncgFDV7GTB_1iYb2byTdNtHxLKUC-kgDQp69QYX2MMwsG3uoYLnZ29ualet-ARi3wvJogPe3oqFlWSMY7jbNPcr1a/s640/map7.PNG" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
我們把 SHA1 fingerprint 複製起來, 回到 Google API Console 網站。在 API Access 中, 先建立一個 Simple API Access, 可以找出 「Create new Android key...」 的按鈕, 開啟以下畫面。 將剛剛複製起來的 SHA1 fingerprint 貼上去就可以了, 然後接上分號「;」, 在分號之後接上你 Android 專案的 Package 名稱即可。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO6kA7vhLnB9wuvIcOTGAHlanFLff-sbDs5IHAnu2uJ7ZgdxFcnIP6dgD2nZ1X68nUm5CaDZ4JZASxqM29_ErInWPner2FE2cNLqImvBIr8UoNLkHB2kPUUEC5z5oRCqViIkfqxK1ZFbp6/s1600/map9.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="442" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO6kA7vhLnB9wuvIcOTGAHlanFLff-sbDs5IHAnu2uJ7ZgdxFcnIP6dgD2nZ1X68nUm5CaDZ4JZASxqM29_ErInWPner2FE2cNLqImvBIr8UoNLkHB2kPUUEC5z5oRCqViIkfqxK1ZFbp6/s640/map9.PNG" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
接下來你就可以看到 API Key 已經建立完成了。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdxuw0EBHtRh9GpxBGJSS8ptz635gw3yfNFhp4C13s3SYLgjN76Kwsm7TN8AbmVX18Kt-9e53eSaMNGra9LxNQJqMMNWyhe20XhJtqUxDvp9-keLFSltQJqCSP6san8JTxcMiGGyYeyljn/s1600/map10.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="94" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdxuw0EBHtRh9GpxBGJSS8ptz635gw3yfNFhp4C13s3SYLgjN76Kwsm7TN8AbmVX18Kt-9e53eSaMNGra9LxNQJqMMNWyhe20XhJtqUxDvp9-keLFSltQJqCSP6san8JTxcMiGGyYeyljn/s640/map10.PNG" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h2>
<span style="color: blue;">3. 將 API Key 加入專案中</span></h2>
<div>
首先, 我們先將剛剛弄出來的 API Key 放到 Manifest 檔中。</div>
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mapsample"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<span style="color: blue;"><uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
</span><span style="color: #6aa84f;"><!-- 以下兩個權限非必需, 但建議加上 --></span><span style="color: blue;">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /></span>
<span style="color: magenta;"><uses-feature
android:glEsVersion="0x00020000"
android:required="true" /></span>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.mapsample.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>
<span style="color: red;"><meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyAUo1DxxxxxxxxxxxxxxxxxxxxyixaP6dU" /></span>
</application>
</manifest>
</pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
首先, API Key 要放在紅字的部份, 請貼上你剛剛申請的 API Key。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
藍字是權限的部份。因為 Google Map 需要從線上下載地圖, 所以在權限上需要網路存取權限。另外下載的圖資會放在 External storage 中, 所以也需要 External Storage 的權限。最後再加上取得 GPS 的權限。另外兩個非必須的權限是利用網路定位, 加上是比較好的。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
紫字的部份, 聽說是GL繪圖用的參數, 這部份還沒有仔細研究, 先加上去。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
把 Manifest 設定好後, 接下來處理 Activity 的程式。Google Map v2 是利用 Google 已經寫好的 MapFragment 套在 Activity 中, 所以一般 API Level 需要支援到 11 以上。還好 Google 有支援 SupportMapFragment 物件, 所以我在 Manifest 檔中只設定到 Level 8 就可以了。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
首先我們在 layout.xml 檔中加入一個 FrameLayout 來塞 Fragment。</div>
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<FrameLayout
android:id="@+id/layoutMap"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
</RelativeLayout>
</pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
接下來在 Activity 的 class 檔中, 一樣是利用 FragmentManager 與 FragmentTransaction 將 SupportMapFragment 帶入畫面中。</div>
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;">
package com.example.mapsample;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MainActivity extends FragmentActivity {
private static final LatLng LOCATION = new LatLng(25.047795, 121.516900);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = this.getSupportFragmentManager();
SupportMapFragment mapFragment = new SupportMapFragment() {
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container,
savedInstanceState);
<span style="color: red;">GoogleMap map = this.getMap();</span>
MarkerOptions options = new MarkerOptions();
options.position(LOCATION);
options.title("台北火車站.");
options.snippet("Taipei Railway Station.");
map.addMarker(options);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(LOCATION, 16));
return view;
}
};
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.layoutMap, mapFragment);
ft.commit();
}
}
</pre>
<br />
在這個範例中我先把座標定在台北火車站, 是固定寫死的。當然如果熟悉 GPS 取得座標的方法的話也可以透過 GPS 來取得經緯度座標。這段程式和一般的 Fragment 使用方式沒什麼太大的差異。<br />
<br />
但要注意的是, 我們對 GoogleMap 的操作是透過 com.google.android.gms.maps.GoogleMap 物件。這個物件需要透過 MapFragment/SupportMapFragment 物件的 getMap() 方法來取得。但在 Fragemnt 中必須要在執行 onCreateView 事件後才會產生 GoogleMap 的實體, 所以如果我們直接在 new MapFragment() 後直接呼叫 getMap() 方法, 會傳回 null 值, 這時進行操作就會產生 NullPointException 的錯誤。所以在這個範例中, 我是將操作的動作寫在 MapFragment 的 onCreateView() 之中。<br />
<br />
寫完後以手機當模擬器執行, 應該就可以看到這段程式的結果。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Tvdk5Rp9kga53_n55rHl71Bxugzsv9oIe4XjGs3k0C52ENO9vaE5aUUhWJminfZIQRaToPaJL5PgfLd_4nSgHga_SA3UUIUAlU00HMZC5KDw6kZI6BMKLPkHaHrI1ZCPi89uvSnAo0Sz/s1600/Screenshot_2013-09-06-14-34-09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Tvdk5Rp9kga53_n55rHl71Bxugzsv9oIe4XjGs3k0C52ENO9vaE5aUUhWJminfZIQRaToPaJL5PgfLd_4nSgHga_SA3UUIUAlU00HMZC5KDw6kZI6BMKLPkHaHrI1ZCPi89uvSnAo0Sz/s640/Screenshot_2013-09-06-14-34-09.png" width="360" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
目前這段程式應該還無法在模擬器上執行, 主要是因為模擬器上沒有 Google Play Service 和 Google Play Store。先寫到這, 大家興趣可以試試。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<a href="https://dl.dropboxusercontent.com/u/16912340/android/samples/MapSample.zip" target="_blank">範例程式在此下載</a>。但是記得要將 Google Play Service 依上述步驟加入專案, 並且將 API Key 更換後才能執行。</div>
<br />Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-50417305583211000742013-09-05T02:44:00.002-07:002013-09-05T18:03:45.204-07:00Service 進階應用之前在<a href="http://lucifernet.blogspot.tw/2013/09/android-service.html" target="_blank">這篇文章</a>介紹了何謂 Service。但落落長一大篇的理論, 卻沒看到什麼很實務的應用, 所以這篇就決定以實務應用為切入點。<br />
<br />
<h2>
<span style="color: blue;"><b>1. 如何使用 Service 下載檔案 ?</b></span></h2>
在這個例子裡具有兩個特性<br />
A. 檔案下載通常都是一次性的<br />
B. 不能在主執行緒使用 HTTP 傳輸, 所以必須要使用非同步的方法。<br />
<br />
運氣很好, Android 官方有提供一個 IntentService 類別, 這個類別的特性完全符合我們的需求。使用上也非常簡單, 只需要把原本繼承 Service 的物件改成繼承 IntentService 類別, 接著覆寫其中的 onHandleIntent(Intent)方法即可 :<br />
<br />
<a name='more'></a><br /><br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;">public class DownloadService <b><span style="color: red;">extends IntentService</span></b> {
public static final String URL = "urlpath";
public static final String FILENAME = "filename";
public static final String FILEPATH = "filepath";
public DownloadService() {
super("DownloadService");
}
@Override
<span style="color: red;"><b>protected void onHandleIntent(Intent intent)</b></span> {
String urlPath = intent.getStringExtra(URL);
String fileName = intent.getStringExtra(FILENAME);
File output = new File(Environment.getExternalStorageDirectory(),
fileName);
if (output.exists()) {
output.delete();
}
InputStream stream = null;
FileOutputStream fos = null;
try {
URL url = new URL(urlPath);
URLConnection connection = url.openConnection();
connection.connect();
int fileLength = connection.getContentLength();
System.out.println("Content Length : " + fileLength);
fos = new FileOutputStream(output);
stream = connection.getInputStream();
InputStream reader = new BufferedInputStream(stream);
byte[] data = new byte[1024];
int next = -1;
while ((next = reader.read(data)) != -1) {
fos.write(data, 0, next);
}
reader.close();
} catch (Exception e) {
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
}
}
}
}
}
</pre>
由於 onHandleIntent 已經是非同步執行了, 所以我們不需要再將 HTTP 的方法另外再透過 AsyncTask 等類別進行非同步處理。<br />
<br />
因為這個範例需要透過 HTTP 下載檔案, 而下載下來後會儲存在 SD 卡上, 所以在 Manifest 檔中除了要宣告這個 Service 外, 也記得要向使用者要求這兩個權限 。<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-size: 13px; line-height: 18px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span style="font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace;"><span style="line-height: 16px;"> </span></span>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.intentservicesample"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<span style="color: red;"><b> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/></b></span>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.intentservicesample.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>
<b><span style="color: blue;"><service android:name="com.example.intentservicesample.DownloadService"></service></span></b>
</application>
</manifest>
</pre>
<br />
接下來我們只要在 Activity 中 Button 按下去的事件中啟動它即可。
<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-size: 13px; line-height: 18px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span style="font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace;"><span style="line-height: 16px;"> </span></span>
package com.example.intentservicesample;
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;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) this.findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Intent intent = new Intent(MainActivity.this,
DownloadService.class);
intent.putExtra(DownloadService.FILENAME, "3.jpg");
intent.putExtra(
DownloadService.URL,
"https://dl.dropboxusercontent.com/u/16912340/android/3.jpg");
<b><span style="color: red;">startService(intent);</span></b>
}
});
}
}
</pre>
這個範例並沒有加入下載完成的通知機制, 是因為不想加入其它與 Service 無關的方法以免失焦。如果想要有下載完成的通知方法, 可以再搭配 Broadcast 與 BroadcastReceiver 來使用即可。<br />
<br />
完整範例程式碼可以在<a href="https://dl.dropboxusercontent.com/u/16912340/android/samples/IntentServiceSample.zip" target="_blank">這裡下載</a>。<br />
<br />
<h2>
<span style="color: blue;"><b>2. 如何開啟一個啟動後持續運作的 Service ?</b></span></h2>
有時候我們希望一個 service 啟動後可以持續運作不中斷, 例如每隔一段時間去伺服器檢查是否有新訊息, 這時我們應該怎麼做 ?<br />
<br />
首先, 我們一樣要先建立一個負責的 Service 類別, 程式碼如下 :<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-size: 13px; line-height: 18px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span style="font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace;"><span style="line-height: 16px;"> </span></span>
package com.example.infiniteservicesample;
import java.util.Random;
import android.app.Service;
import android.content.Intent;
<span style="color: blue;"><b>import android.os.Handler;</b></span>
import android.os.IBinder;
public class InfiniteService extends Service {
public static final String ACTION = "com.example.infiniteservicesample";
public static final String PARAM_RAND = "rand";
public static final String URL = "URL";
<b><span style="color: blue;">private Handler mHandler = new Handler();</span></b>
private long mDelay = 10000;
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
<b><span style="color: blue;">mHandler.postDelayed(mRunnable, 0);</span></b>
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
<span style="color: blue;"><b>mHandler.removeCallbacks(mRunnable);</b></span>
super.onDestroy();
}
<span style="color: red;"><b>private Runnable mRunnable = new Runnable() {</b></span>
@Override
public void run() {
try {
Random r = new Random();
int i = r.nextInt();
broadcast(i);
} catch (Exception e) {
e.printStackTrace();
} finally {
<span style="color: blue;"><b><u><i>mHandler.postDelayed(this, mDelay);</i></u></b></span>
}
}
<b><span style="color: red;">};</span></b>
private void broadcast(int i) {
Intent intent = new Intent();
intent.setAction(ACTION);
intent.putExtra(PARAM_RAND, i);
this.sendBroadcast(intent);
}
}
</pre>
<br />
這邊和其它的 Service 類別沒什麼不同, 只是我們使用了 android.os.Handler 物件來管理要執行的任務(<span style="color: blue;"><b>藍字部份</b></span>)。而要執行的任務程式碼放在 Runnable 物件中 (<b><span style="color: red;">紅字部份</span></b>)。而每次任務執行完成時, 透過 sendBroadcast(Intent) 方式廣播通知其它人知道即可。<br />
<br />
Runnable 物件在原生的 java 中最常拿來搭配 Thread 使用, 但要注意的是 Handler 物件本身仍是在主執行緒上執行。所以如果要存取 HTTP 的話, 仍需要自行處理非同步的部份。<br />
<br />
而每次任務執行結束, 再透過 Handler 物件的 postDelayed(Runnable, int)方法交待多少毫秒後再執行一次任務, 如此循環, 這支 Service 就可以一直生存下去。<br />
<br />
搭配 Broadcast/BroadcastReceiver/Notification 使用, 就可以達成每十秒接受一次資料的效果, 即使將 Activity 關閉仍會跑出 Notification 出來, 並藉由 Notification 來重新開啟應用程式。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWOFFjdplwSjZnY-8pjScOrc1YbpXjUDNqaZPQKfVx45QZxI4fxvqqj0w0tXULIUeCBinFxj0X4RRx2YPs_IwwsnETKLkLH2K1M9vSe19Ac4Sy2MnErje92Hv5oilF5v77tuH8-7GLn-II/s1600/notification.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWOFFjdplwSjZnY-8pjScOrc1YbpXjUDNqaZPQKfVx45QZxI4fxvqqj0w0tXULIUeCBinFxj0X4RRx2YPs_IwwsnETKLkLH2K1M9vSe19Ac4Sy2MnErje92Hv5oilF5v77tuH8-7GLn-II/s640/notification.PNG" width="395" /></a></div>
<br />
其它 BroadcastReceiver 和 Notification 的部份, 不屬於本文重點, 在此就不貼程式碼了。有興趣的話可以<a href="https://dl.dropboxusercontent.com/u/16912340/android/samples/InfiniteServiceSample.zip" target="_blank">下載原始碼</a>自行研究囉。Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-53487574824960894892013-09-04T01:44:00.003-07:002013-09-05T02:46:25.778-07:00Android Service 介紹<h3>
<div style="font-size: medium; font-weight: normal;">
我們在開發 Android 應用程式時, 常會遇到一些特殊需求, 會希望在 Activity 或者應用程式關閉時, 仍然能夠有些服務在進行。例如檢查 Email 是否有新的信件, 或者像即時通訊軟體一般, 一旦有新的訊息時能夠馬上通知使用者。但這時會遇上一個雞生蛋或蛋生雞的問題 : 你的應用程式可能已經被關閉了, 程式已經完全處於生命終結的狀態, 那又該怎麼提供服務呢 ?</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
<div style="font-size: medium; font-weight: normal;">
為了滿足有這樣需求的應用程式開發, 在 Android 上就提供了 Service 這種機制, 這篇文章就是對 Service 進行一些簡單的介紹。</div>
<div style="font-size: medium; font-weight: normal;">
<br /></div>
</h3>
<h2 style="text-align: center;">
<span style="color: blue;">什麼是 Service ?</span></h2>
Service 簡單來說就是一個在背景執行的元件, 不需要與使用者互動, 所以也沒有操作介面。更重要的是, 它的生命週期是和 Activity 脫勾的, Activity 就算關閉了, Service 仍舊可以繼續運作。<br />
<br />
Service 通常用在執行重覆性的事(例如每隔一段時間檢查是否有更新資料), 或者需要執行很久的事情上(例如透過 HTTP 下載大量的資料或檔案)。<br />
<br />
Service 有兩種形式 :<br />
<br />
<b><span style="color: blue;">Started </span>: </b><br />
當 Service 一旦透過 Context.startService() 啟動後, 這個 Service 就可以無限期的在背景執行, 即使啟動這個 Service 的 Activity 或應用程式被關閉, 這個 Service 仍可以完成你所交付的工作。但一般來說, Service 並不會和它的開啟者有什麼互動, Service 完成後也不會回傳或通知它的開啟者。所以如果一旦 Service 執行完畢, Service 本身應該就會自動停止。<br />
<br />
<span style="color: blue;"><b>Bind </b>: </span><br />
而 Service 的另一個角色就是負責跨 Process 的功能協作。各個不同的 client 可以透過 Context.bindService() 的方法來使用此應用程式在 Service 裡所提供的功能, 以達到兩個應用程式的互動。<br />
<br />
一個 Service 可以被多個不同的應用程式透過 Context.bindService() 的方式綁定。只要有其它應用程式開始綁定這個 Service , 這個 Service 就會啟動。一直到所有的應用程式都不再使用這個服務, 這個服務就會自動被銷毀。<br />
<br />
以上兩種形式其實並不衝突, 一個服務可以透過 startService() 的方式被啟動, 然後再被其它應用程式綁定。你只要同時實作 onStartCommand() 與 onBind() 兩個事件即可。<br />
<br />
當然, 你也可以在 manifest 檔中將 Service 宣告成 private, 這樣其它應用程式就不能啟動這個服務了。<br />
<br />
<br />
<h2 style="text-align: center;">
<span style="color: blue;">Thread ? Process ? Service ?</span></h2>
要瞭解 Service , 有許多看起來作用很像的名詞, 如果不先講清楚, 在學習過程中很容易產生概念上的混淆。所以邊簡單的解說一下 :<br />
<br />
<b><span style="color: red;">Process : 中文翻譯成進程或線程。</span></b><br />
每一個應用程式啟動時, Android 就會將這個應用程式執行在它獨有的 Process 上。這點和 Linux 是一樣的(因為 Android 就是拿 Linux 改出來的嘛 )。大家看一下 windows 的工作管理員大概就可以理解 Process 是什麼東西了。<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXsECNs7GbXuBqkIORl5CT-Fw4KizkIcuJfDGjdNXQ1Nrb8gnwfcimHWipgA3kXsPCyf8PoVNpeSfcSnX7u5kg9RKTH_PidZeYTSVCwjRqcHa27UKu98hwt8avgW1yYduvYZl0qEDbvPy1/s1600/process.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="436" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXsECNs7GbXuBqkIORl5CT-Fw4KizkIcuJfDGjdNXQ1Nrb8gnwfcimHWipgA3kXsPCyf8PoVNpeSfcSnX7u5kg9RKTH_PidZeYTSVCwjRqcHa27UKu98hwt8avgW1yYduvYZl0qEDbvPy1/s640/process.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Windows 工作管理員中可以看到目前運行許多 Process</td></tr>
</tbody></table>
<br />
<b><span style="color: red;">Thread : 執行緒</span></b><br />
當一個 Process 啟動時, 作業系統會開啟一條執行緒, 負責 UI 處理和運算等所有工作, 這個執行緒稱為主執行緒(main-thread)。但如果我們在主執行緒上執行一件可能需要執行很久的工作, 例如 HTTP 資料傳輸或大量資料運算, 很可能會擔誤主執行緒上其它運算的效率或順暢度 (例如操作介面很可能變頓甚至無法運作)。 所以我們通常會在 Process 中建立其它的執行緒並將工作交付給它, 讓主執行緒上所負責的 UI 能夠正常操作, 以獲得最佳的使用者操作經驗。一般來說我們稱之為 Background Thread (背景執行)。<br />
<br />
一個 Process 可以建立超過一個以上的 Thread。 但要注意的是, 雖然 Background Thread 不會影響主執行緒的正常運作, 但若建立過多的 Background Thread 則可能讓 CPU 忙於切換這 Thread 而導致整體運作效能下降。不過整體而言數十個 Thread 甚至數百個 Thread 都還是在可以接受的範圍(視 CPU 的能力與執行工作的性質不同數字會有落差)。<br />
<br />
順帶一提, Android 在大部份的情況下是不允許在主執行緒上進行 HTTP 資料存取。一般會建議使用 AsyncTask 或 HandlerThread 方式來處理。<br />
<br />
Thread 的生命週期會依附在其所屬的 Process 上。一旦關閉一個 Process, 其附屬運行中的所有 Thread 都會連帶被中止。<br />
<br />
<span style="color: red;"><b>Service : 服務</b></span><br />
Service 是 Android 提供出來的一個元件。它<span style="color: red;">預設會建立在與 程用程式同一個 Process 的主執行緒上</span>。這點很常被誤解。我們可以透過 Context.startService() 的方法要求系統將指定的工作加入排程, 若執行緒有空時則開始執行。並不會自動建立另一個 Thread 或 Process 去執行。<br />
<br />
當然 Android 也有提供方法可以讓我們把 Service 建立在他獨立的 Process 上。這樣運作時就會在該 Process 的主執行緒上, 就不會和原用程式的主執行緒爭資源了。不過就算是使用 Service 獨立的 Process, 在使用 HTTP 時仍必須在 Background Thread 中呼叫。<br />
<br />
而 Service 的另一個角色就是負責跨應用程式的功能協作。其它的應用程式可以透過 Context.bindService() 的方法來使用此應用程式在 Service 裡所提供的功能, 以達到兩個應用程式的互動。<br />
<br />
<br />
<h2 style="text-align: center;">
<span style="color: blue;">我應該用 Service 還是用 Thread ?</span></h2>
<div style="text-align: left;">
Service 通常是用於可在背景執行, 鮮少甚至不需要與使用者產生互動的工作。例如在背景下載一個檔案之類的工作。即使是結束 Activity 時檔案仍未下載完成, 下載的工作也可以在 Service 中被執行完成。</div>
<br />
但如果你需要的是希望當應用程式結束時就連帶中止的工作, 例如在程式執行時播放 MP3 當背景音樂, 但一旦使用者結束應用程式, 背景音樂也要被中止。這時你就應該是使用 Thread 而不是 Service 了。順帶一提, 在 Android 官方建議使用 <a href="http://developer.android.com/reference/android/os/AsyncTask.html" target="_blank">AsyncTask </a>或者 <a href="http://developer.android.com/reference/android/os/HandlerThread.html" target="_blank">HandlerThread </a>來取代 JAVA 傳統的 <a href="http://developer.android.com/reference/java/lang/Thread.html" target="_blank">Thread</a>。若有興趣的話可以看看官方<a href="http://developer.android.com/guide/components/processes-and-threads.html#Threads" target="_blank">Processes and Threading</a>, 有比較詳細的資訊。<br />
<br />
<br />
<h2 style="text-align: center;">
<span style="color: blue;">Service 的生命週期</span></h2>
一個 Service 可以被系統啟動的原因有兩個, 當有人呼叫 Context.startService() 方法時, 系統會呼叫 Service 物件內的 onCreate() 方法, 接下來會呼叫 onStartCommand(Intent, int, int) 方法讓 client 端提供參數。一直到 Context.stopService() 方法被呼叫, 或者是 Service 內自己呼叫 stopSelf() 方法才會停止。<br />
<br />
另一個被啟動的方式是有其它的 Client 端透過 Context.bindService() 來取得與 Service 的聯結。如果該 Service 不在啟動狀態的話, 這種方式則會啟動這個 Service, onCreate() 的方式這時就會被呼叫, 但這種方式並不會觸發 onStartCommand() 事件。啟動這個 Service 的client 會透過 onBind(Intent) 方法取得 IBinder 物件。接下來就可以透過 IBinder 物件來取得 Service 的事件(透過 call back 的方法)。只要連線不被中斷, Service 就會保持運作。<br />
<br />
但之前提到, Service 可以同時支援 Bound 與 Started 兩種模式。在這種情況, Service 會持續運作到被 started 模式被關閉, 同時 bound 模式中已經沒有任何連線時才會結束, 結束時就會觸發 onDestroy() 事件。<br />
<br />
Service 的運作優先權相當的高, 一般來說除非系統資源耗畫, 不然 Android 不太會去主動關閉一個已被啟動的 Service。一旦系統有足夠的資源, 被 Android 關閉的 Service 也會被重新啟動。<br />
<br />
因為預設 Service 的和應用程式預設是在同一個 process 的主執行緒中, 所以即使 Service 被啟動了, 一旦應用程式被關閉或清除時, Service 也會被清掉。所以我們可以讓 Service 活在自己一個獨立的 process 中。這時我們只要在 Manifest 檔設定 Service 時, 可以加上 process=":YourProcessName" 這個屬性。<span style="color: red;">屬性值以冒號『:』開頭, Android 在啟動 Service 時就會讓它有獨立的 Process</span>。<br />
<br />
其實一般來說很少需要特別指定另一個 Process 來執行 Service, 除非這個 Service 會對其它應用程式提供服務時才需要。<br />
<h2 style="text-align: center;">
<span style="color: blue;">如何定義 Service</span></h2>
<br />
講了這麼多, 看都應該看到睡著了。但是說到怎麼寫, 則是<strike>完全沒有畫面</strike>一句都還沒提到。其實 Service 的定義很簡單, 大致上只需要三個步驟 :<br />
<br />
1. 在 AndroidManifest.xml 檔中新增定義 :<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><service android:name="<span" class="hl-string" style="color: blue;"><com.sample.MyService /></service></pre>
這裡的 android:name 必須是你自訂 Service 類別的完整名稱。<br />
<br />
2. 實作一個繼承自 android.app.Service 類別的物件<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span style="color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;"><span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> </span><span style="color: purple; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;"><b>class </b></span><span style="color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;">MyService </span><span style="color: blue; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;">extends </span><span style="color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;">Service {
</span><span style="color: #666666; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;">@Override</span><span style="color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;">
</span><span style="font-family: Droid Sans Mono, Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace;"><span style="line-height: 16px;"><span style="color: #222222;"> </span><span style="color: purple;"><b>public </b></span><span style="color: #222222;">IBinder onBind(Intent intent) {
return null;
}
</span><span style="color: #999999;">@Override</span><span style="color: #222222;">
</span><b><span style="color: purple;">public </span></b>int <span style="color: #222222;">onStartCommand(Intent intent, int flags, int startId) {
</span><span style="color: #38761d;">//這裡實作你想做的工作
</span></span></span><span style="font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; line-height: 16px;"><span style="color: #38761d;"> </span></span><span style="color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; line-height: 16px;">return Service.START_STICKY;
</span><span style="color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; line-height: 16px;"> }
</span><span style="color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; line-height: 16px;">}</span></pre>
<br />
在 onStartCommand 中, 最後會回傳一個常數, 定義在 Service 類別中。這個回傳值是用在如果這個 Service 被 Android 作業系統終止, 你想要怎麼做。 常數值分別為 :<br />
<br />
<ul>
<li>Service.START_STICKY : </li>
<ul>
<li>Service 如果被中止的話會自動重啟。用在 onStartCommand 方法中不需要依賴 Intent 傳入的資料就可以執行的時候( 重啟時重新傳入的 Intent 會是 null )。</li>
</ul>
<li>Service.START_NOT_STICKY : </li>
<ul>
<li>Servcie 如果被中止的話不啟動, 用在 onStartCommand 方法中所執行的工作需要依賴 Intent 物件內帶進來的參數。</li>
</ul>
<li>Service.START_REDELIVER_INTENT : </li>
<ul>
<li>和 START_STICKY 差不多, 但 Android 會在中止 Service 之前將 Intent 保留下來, 等待重啟時再將原本的 Intent 物件交還給 onStartCommand 事件。</li>
</ul>
</ul>
<br />
<br />
3. 在 Activity 中呼叫 startService(Intent) 來執行<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span style="color: #222222; font-family: Droid Sans Mono, Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace;"><span style="line-height: 16px;"> </span></span><em style="color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;">@Override</em><span style="color: #222222; font-family: Droid Sans Mono, Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace;"><span style="line-height: 16px;">
</span></span><span class="hl-keyword" style="color: #7f0055; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; font-weight: bold; line-height: 16px;">public</span><span style="color: #222222; font-family: Droid Sans Mono, Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace;"><span style="line-height: 16px;"> View onCreate(Bundle savedInstanceState) {
</span></span><span style="color: purple; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px;"><b>super</b></span><span style="font-family: Droid Sans Mono, Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace;"><span style="line-height: 16px;"><span style="color: #222222;">.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
</span><span style="color: red;"><b>Intent intent = new Intent(</b><b>this, MyService.class);
this.startService(intent);</b></span><span style="color: #222222;">
}</span></span></span></pre>
<br />
基本上只要以上三個步驟就可以啟動一個 Service囉。<br />
<br />
<br />
<h2 style="text-align: center;">
<span style="color: blue;"><b>透過 Bind 方式來啟動 Service</b></span></h2>
<div style="text-align: center;">
<span style="color: blue;"><b><br /></b></span></div>
<div style="text-align: left;">
但如果是希望採用 Bind 的方式來啟動一個 Service, 只要在實作 Service 改成以下方法 : </div>
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> <span class="hl-keyword" style="color: #7f0055; font-weight: bold;">class</span> MyService <span class="hl-keyword" style="color: #7f0055; font-weight: bold;">extends</span> Service {
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">private</span> <span class="hl-keyword" style="color: #7f0055; font-weight: bold;">final</span> IBinder mBinder = <span class="hl-keyword" style="color: #7f0055; font-weight: bold;">new</span> MyBinder();
<span style="color: purple;"><b>private </b></span>String info = "";
<em><span class="hl-annotation" style="color: grey;">@Override</span></em>
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> IBinder onBind(Intent intent) {
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">return</span> mBinder;
}
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> <span class="hl-keyword" style="color: #7f0055; font-weight: bold;">class</span> MyBinder <span class="hl-keyword" style="color: #7f0055; font-weight: bold;">extends</span> Binder {
MyService getService() {
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">return</span> MyService.<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">this</span>;
}
}
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> String getInfo() {
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">return</span> info;
}
} </pre>
<br />
基本上就是實作 onBind(Intent) 事件。而 IBinder 介面則負責介接 client 端與 Service 的溝通。上面的作法是用在 client 端與 Service 是在同一個應用程式內時, 可以直接將 Service 的實體透過 Binder 的延伸方法的方式取得。<br />
<br />
而 client 的部份, 必須先實作 android.content.ServiceConnection 介面, 如下所示<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-keyword" style="color: #7f0055; font-weight: bold;">private</span> ServiceConnection mServiceConnection = <span style="color: blue;"><b>new </b></span>ServiceConnection() {
<em><span class="hl-annotation" style="color: grey;">@Override</span></em>
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> void onServiceConnected(ComponentName className, IBinder binder) {
<span style="color: #6aa84f;">//這裡就可以取出 service 的實體</span>
service = ((MyService.MyBinder)binder).getService();
}
<i><span style="color: #999999;">@Override</span></i>
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> void onServiceDisconnected(ComponentName className) {
service = null;
}
} </pre>
<br />
而負責啟動的 Activity 中, 我們可以在 onResume 與 onStop 的事件中管理與 Service 的連線<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-keyword" style="color: #7f0055; font-weight: bold;">private</span> ServiceConnection mServiceConnection = <span style="color: blue;"><b>new </b></span>ServiceConnection() {
<em><span class="hl-annotation" style="color: grey;">@Override</span></em>
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> void onResume() {
<span style="color: purple;"><b>super</b></span>.onResume();
Intent intent = new Intent(this, MyService.class);
this.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
<i><span style="color: #999999;">@Override</span></i>
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> void onPause() {
super.onPause();
this.unbindService(mServiceConnection);
}
} </pre>
<br />
但這個範例雖然可以透過 IBinder 取回 Service 的實體, 但只能用在同一個應用程式內, 跨不同的應用程式無法將 binder 轉型成指定的物件形態, 所以無法使用這個方法來溝通。所以跨應用程式的話我們通常會使用 Service 搭配 Broadcast 與 BroadcastReceiver 的方式來溝通, 就不在這裡說明了。<br />
<br />
上面的完整程式可以在<a href="https://dl.dropboxusercontent.com/u/16912340/android/samples/ServiceSample.zip" target="_blank">這裡下載</a>, 有興趣的可以下載來看看。<br />
<br />
關於 Service 的應用, 可以參考<a href="http://lucifernet.blogspot.tw/2013/09/service.html" target="_blank">另一篇文章</a>。<br />
<br />
參考資料<br />
<a href="http://developer.android.com/guide/components/services.html" target="_blank">Android Developer : Services</a><br />
<a href="http://www.vogella.com/articles/AndroidServices/article.html" target="_blank">vogella.com : Android Service Tutorial</a><br />
<a href="http://developer.android.com/guide/components/processes-and-threads.html" target="_blank">Android Developer : Processes and Threads</a><br />
<a href="http://developer.android.com/reference/android/app/Service.html" target="_blank">Android Develper : Service</a>Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com1tag:blogger.com,1999:blog-6501248200092871158.post-53791051455645259802013-08-29T21:44:00.001-07:002013-08-29T21:50:13.052-07:00ActionBar.Tab 使用介紹<div class="separator" style="clear: both; text-align: center;">
</div>
<div style="margin-left: 1em; margin-right: 1em; text-align: left;">
<br /></div>
<div style="margin-left: 1em; margin-right: 1em; text-align: left;">
自從 iphone 第一代從2007年初發表到現在已經過了六年之久了, 在 mobile 的 UI 設計上也逐漸的被整理出許多模式(Pattern)。其中一個很常用的模式就是使用 Tab 來切換不同的視窗內容。所以這篇文章會大致介紹 Tab 要怎麼使用。</div>
<div style="margin-left: 1em; margin-right: 1em;">
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><img alt="" height="390" src="http://developer.android.com/images/ui/actionbar-splitaction@2x.png" style="margin-left: auto; margin-right: auto;" width="640" /></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Android 中 ActionBar 提供預設的 Tab 樣貌(圖片來自<a href="http://developer.android.com/guide/topics/ui/actionbar.html">Android Developer官網</a>)</td></tr>
</tbody></table>
<br />
<br />
<a name='more'></a><br />
<br />
在 Android 2.3 之前, Android 尚未提供 ActionBar 的功能, 當時如果要寫一個頁籤的功能, 我們可以簡單利用橫向 LinearLayout 加上一排 Button 來完成。像下面的範例 :<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-tag" style="color: #008800;"><linearlayout span=""> <LinearLayout <span class="hl-attribute" style="color: #7f0055;">xmlns:android</span>=<span class="hl-value" style="color: blue;">"http://schemas.android.com/apk/res/android"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:orientation</span>=<span class="hl-value" style="color: blue;">"horizontal"</span><span class="hl-tag" style="color: #008800;"> ></span>
<span class="hl-tag" style="color: #008800;"><framelayout span="">
<Button <span class="hl-attribute" style="color: #7f0055;">android:id</span>=<span class="hl-value" style="color: blue;">"@+id/btn_fragment1"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"0dp"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_weight</span>=<span class="hl-value" style="color: blue;">"0.5"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"wrap_content"</span>
<span class="hl-attribute" style="color: #7f0055;">android:text</span>=<span class="hl-value" style="color: blue;">"@string/fragment1"</span>
/>
<Button <span class="hl-attribute" style="color: #7f0055;">android:id</span>=<span class="hl-value" style="color: blue;">"@+id/btn_fragment2"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"0dp"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_weight</span>=<span class="hl-value" style="color: blue;">"0.5"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"wrap_content"</span>
<span class="hl-attribute" style="color: #7f0055;">android:text</span>=<span class="hl-value" style="color: blue;">"@string/fragment2"</span>
/>
</LinearLayout></framelayout></span></linearlayout></span></pre>
這時在 Eclipse ADT 的開發環境中編輯 Activity Layout 的 XML 檔案時, 如果編輯器發現你像以下寫法, 他就會自動提示你的 Button 應該是無框的才是, 依照他的提示完成後程式碼就會像下面這樣 :<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-tag" style="color: #008800;"><linearlayout span=""> <LinearLayout <span class="hl-attribute" style="color: #7f0055;">xmlns:android</span>=<span class="hl-value" style="color: blue;">"http://schemas.android.com/apk/res/android"</span>
<span class="hl-attribute" style="color: #7f0055;">style</span>=<span class="hl-value" style="color: red;">"?android:attr/buttonBarStyle"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"0dp"</span>
<span class="hl-attribute" style="color: #7f0055;">android:orientation</span>=<span class="hl-value" style="color: blue;">"horizontal"</span><span class="hl-tag" style="color: #008800;"> ></span>
<span class="hl-tag" style="color: #008800;"><framelayout span="">
<Button <span class="hl-attribute" style="color: #7f0055;">android:id</span>=<span class="hl-value" style="color: blue;">"@+id/btn_fragment1"</span>
<span class="hl-attribute" style="color: #7f0055;">style</span>=<span class="hl-value" style="color: red;">"?android:attr/buttonBarButtonStyle"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"0dp"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_weight</span>=<span class="hl-value" style="color: blue;">"0.5"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"wrap_content"</span>
<span class="hl-attribute" style="color: #7f0055;">android:text</span>=<span class="hl-value" style="color: blue;">"@string/fragment1"</span>
/>
<Button <span class="hl-attribute" style="color: #7f0055;">android:id</span>=<span class="hl-value" style="color: blue;">"@+id/btn_fragment2"</span>
<span class="hl-attribute" style="color: #7f0055;">style</span>=<span class="hl-value" style="color: red;">"?android:attr/buttonBarButtonStyle"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"0dp"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_weight</span>=<span class="hl-value" style="color: blue;">"0.5"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"wrap_content"</span>
<span class="hl-attribute" style="color: #7f0055;">android:text</span>=<span class="hl-value" style="color: blue;">"@string/fragment2"</span>
/>
</LinearLayout></framelayout></span></linearlayout></span></pre>
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMbnc0rUJhVBW-62gIUP1O8cD8N8de8Mg8DY84be5dd5t1ySQ-WeWZ4bfwnjrcFjayDcRv9_cKLelhOG4Z-84o1TOc3mscpnZwysIpNXlK3XzKR3HXBBeTuvViNJZgMKSuHackhnqqb7Fq/s1600/fragment1.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMbnc0rUJhVBW-62gIUP1O8cD8N8de8Mg8DY84be5dd5t1ySQ-WeWZ4bfwnjrcFjayDcRv9_cKLelhOG4Z-84o1TOc3mscpnZwysIpNXlK3XzKR3HXBBeTuvViNJZgMKSuHackhnqqb7Fq/s400/fragment1.png" width="248" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">最後就會做成這個樣子, 範例程式可以在<a href="https://dl.dropboxusercontent.com/u/16912340/android/samples/SimpleFragmentSample.zip">這邊下載</a></td></tr>
</tbody></table>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
這種作法的好處是, Tab 的部份可以相容 Android 2.3 以前的版本, 不過因為我很偷懶沒針對 Fragment 做相容性處理, 所以 app 的最低 API Level 還是設在11。<br />
<br />
<h2>
ActionBar.Tab</h2>
在 Android 3.0 (API Level 11) 之後, Google 對 Android 進行了相當大的改革, 其中包含了提供了強大的 ActionBar。而 ActionBar 中提供了三種瀏覽模式(Navigation Mode), 其中一種就是提供了 Tab 的 UI 介面(本文第一張圖就是囉)。<br />
<br />
不過 ActionBar 到目前為止似乎還沒看到在 support library 中可以向前相容的作法。 也就是說, <span style="color: red;"><b>如果你使用了 ActionBar.Tab 方式來處理 Tab, 那麼你最低的 API Level 至少必須設定到 level 11 以上</b></span>。<br />
<br />
而 ActionBar.Tab 其實是搭配 Fragment 來使用。那麼因為 ActionBar 至少要 level 11, 那我們的 Fragment 就不用費心的使用 support library 中的那些方式了。<br />
<br />
因為 ActionBar 中的 Tab 其實相當簡單, 這邊就以上頭那個範例改寫, 直接切入如何實作。<br />
<br />
第一步, 就是先設定好ActionBar 的 Navigation Mode<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"> <em>@Override</em>
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> View onCreate(Bundle savedInstanceState) {
<span style="color: purple;"><b>super</b></span>.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar bar = this.getActionBar();
bar.setNavigationMode(<span style="color: red;"><b>ActionBar.NAVIGATION_MODE_TABS</b></span>);
}</pre>
<br />
接下來, 產生幾個 Tab 加進去, 這裡的 ActionBar.Tab 其實是個 abstract class。千萬不要天真的自己去繼承他, 直接使用原生提供的 actionBar.newTab() 方式產生就可以了。最後給它一個名字( Text ) 和事件的 Listener 就可以了。<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"> Tab tab = bar.newTab();
tab.setText(R.string.fragment1);
setTabListener(mTabListener);</pre>
<br />
至於事件的部份, 只要實作 android.app.<a class="header" href="eclipse-javadoc:%E2%98%82=ActionBarTabSample/E:%5C/dev%5C/adt-bundle-windows-x86_64-20130729%5C/sdk%5C/platforms%5C/android-18%5C/android.jar%3Candroid.app(ActionBar.class%E2%98%83ActionBar">ActionBar</a>.TabListener 介面即可, 這裡它提供了三個不同的事件 :<br />
<br />
<ul>
<li>void <b><span style="color: red;">onTabSelected</span></b>(Tab tab, FragmentTransaction ft) : </li>
<ul>
<li> 當使用者點選到此 Tab 時觸發</li>
</ul>
<li>void <span style="color: red;"><b>onTabUnselected</b></span>(Tab tab, FragmentTransaction ft) :</li>
<ul>
<li>當使用者選擇其它 Tab 時觸發</li>
</ul>
<li>void <span style="color: red;"><b>onTabReselected</b></span>(Tab tab, FragmentTransaction ft) :</li>
<ul>
<li>當使用者原本就選了此 Tab, 又重覆點選了此 Tab 時觸發</li>
</ul>
</ul>
這邊我們特別注意傳入參數的部份, 基本上三個方法的傳入參數都一樣, 第一個參數點選到的 Tab 就是觸發者的實體。基本上這樣設計的好處是每個 Tab 都可以共用同一個 Listener instance(省一點記憶體, 程式碼也會短一點), 而事件觸發時用這個參數讓你判斷事件是由哪個 Tab 觸發的, 相當聰明的設計 XD。<br />
<br />
第二個參數就是 FragmentTranscation 了, 這個部份我在 <a href="http://lucifernet.blogspot.tw/2013/08/fragment_29.html" target="_blank">如何使用Fragment</a> 這篇當中已經介紹過了。這邊當成參數傳入, 基本上就是讓我們不需要再另外透過 FragmentManager 來自己處理 Transaction。但要注意的是, 它所提供的 FragmentTransaction 物件不能呼叫 addToBackStack() 方法。如果你真得堅持要把這個動作加到 back stack 中, 那就自己透過 FragmentManager 去控制 Fragment 的切換吧。<br />
<br />
另一個要注意的, 就是預設提供的 FragmentTransaction 物件不需要也不能自己呼叫 commit() 方法。這點官網中似乎沒有提到, 不過如果呼叫了, 程式執行時爆掉了你自然就會知道了......。<br />
<br />
如果針對 Fragment 不需要進行太複雜的處理的話, 事件的處理上可以很簡單的寫成下面這樣 :<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); color: #222222; font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><b><span style="color: purple;">private</span></b> TabListener mTabListener = <b><span style="color: blue;">new </span></b>TabListener() {
@Override
<b><span style="color: purple;">public </span></b>void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
@Override
<b><span style="color: purple;">public </span></b>void <span style="color: red;"><b>onTabSelected</b></span>(Tab tab, FragmentTransaction ft) {
Fragment fragment;
if(tab.getText() == getString(R.string.fragment1))
fragment = <span style="color: blue;"><b>new </b></span>Sample1Fragment();
else
fragment = <span style="color: blue;"><b>new </b></span>Sample2Fragment();
ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
ft.replace(R.id.layout_fragment, fragment);
}
@Override
<span style="color: purple;"><b>public </b></span>void onTabReselected(Tab tab, FragmentTransaction ft) {
}
};
</pre>
<br />
上頭的是直接使用 FragmentTransaction 中的 replace() 方法, 每次點到時都重新產生一個新的 fragment 物件。如果要有更好效能或保留 fragment 狀態的作法, 那麼可以自己試著改寫成使用 attach/detach 的方法。<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQh9QflSVKIncu_ugkmg9ogAry0IUyXw2oAQEC8Wfh9ajQSIHrm4xGMyEXlMT_w1MhQEcVnRWoFAzQ4Mt3dd8G-AamR8pc6N14infV0TcyHS7vYBEk0Fs5WWfD4kbnuHqhgRAbzA3owCNf/s1600/ActionBarTab.PNG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQh9QflSVKIncu_ugkmg9ogAry0IUyXw2oAQEC8Wfh9ajQSIHrm4xGMyEXlMT_w1MhQEcVnRWoFAzQ4Mt3dd8G-AamR8pc6N14infV0TcyHS7vYBEk0Fs5WWfD4kbnuHqhgRAbzA3owCNf/s400/ActionBarTab.PNG" width="248" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">以上改寫成ActionBar.Tab 成品, 可以在<a href="https://dl.dropboxusercontent.com/u/16912340/android/samples/ActionBarTabSample.zip" target="_blank">這裡下載</a></td></tr>
</tbody></table>
<br />
<h3>
參考資料</h3>
<div>
<a href="http://developer.android.com/guide/topics/ui/actionbar.html#Tabs" target="_blank">Adding Navigation Tabs</a></div>
<div>
<a href="http://developer.android.com/reference/android/support/v7/app/ActionBar.Tab.html" target="_blank">ActionBar.Tab</a></div>
<div>
<a href="http://developer.android.com/reference/android/app/ActionBar.TabListener.htm" target="_blank">ActionBar.TabListener</a></div>
Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com1tag:blogger.com,1999:blog-6501248200092871158.post-57627450875731895682013-08-29T01:37:00.000-07:002013-08-29T01:43:05.843-07:00如何使用 Fragment在前一篇文章 <a href="http://lucifernet.blogspot.tw/2013/08/fragment.html" target="_blank">Fragment 介紹</a> 中我講了一大堆什麼是 Fragment, 該用在哪, 生命週期, 如何設計等等 ......。但就是沒跟大家說要怎麼用!? 身為一個以 copy + paste 然後改改改就產出為最高指導原則的工程師這種寫法當然是不被允許的, 所以這篇來說一下怎麼使用 Fragment 這種神奇的好物!<br />
<br />
其實 Fragment 的實作上步驟都是差不多的 :<br />
<br />
第一步 : 實作繼承自 Fragment 的類別<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-keyword" style="color: #7f0055; font-weight: bold;">package</span> com.example;
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">import</span> android.app.Fragment;
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">import</span> android.os.Bundle;
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">import</span> android.view.LayoutInflater;
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">import</span> android.view.View;
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">import</span> android.view.ViewGroup;
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> <span class="hl-keyword" style="color: #7f0055; font-weight: bold;">class</span> MyFragment <span class="hl-keyword" style="color: #7f0055; font-weight: bold;">extends</span> Fragment {
......
} </pre>
<br />
<br />
<a name='more'></a><br />
<br />
第二步 : 若是想要載入自己設計好的畫面, 在 res/layout 資料夾中新增一個 Layout 的 XML 檔, 例如 fragment_a.xml<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-directive" style="color: maroon;"></span>
<span class="hl-tag" style="color: #008800;"><linearlayout span=""> <LinearLayout <span class="hl-attribute" style="color: #7f0055;">xmlns:android</span>=<span class="hl-value" style="color: blue;">"http://schemas.android.com/apk/res/android"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:orientation</span>=<span class="hl-value" style="color: blue;">"vertical"</span><span class="hl-tag" style="color: #008800;"> ></span>
<span class="hl-tag" style="color: #008800;"><textview span="">
<TextView <span class="hl-attribute" style="color: #7f0055;">android:id</span>=<span class="hl-value" style="color: blue;">"@+id/textView1"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"wrap_content"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"wrap_content"</span>
<span class="hl-attribute" style="color: #7f0055;">android:text</span>=<span class="hl-value" style="color: blue;">"@string/hello_world"</span><span class="hl-tag" style="color: #008800;"> /></span>
</LinearLayout>
<span class="hl-tag" style="color: #008800;"></span></textview></span></linearlayout></span> </pre>
<br />
第三步 : 在 onCreateView() 中透過 LayoutInflater 載入<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"> <em>@Override</em>
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">public</span> View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_a,
container, false);
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">return</span> view;
}</pre>
<br />
第四步 : 將訂好的 Fragment 物件放到 Activity 的畫面中<br />
這裡有兩種不同的作法, 第一種是直接用 <fragment ...=""> 的標籤將 fragment 直接放在 Activity 指定的地方</fragment><br />
<fragment ...=""></fragment><br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-directive" style="color: maroon;"></span>
<span class="hl-tag" style="color: #008800;"><linearlayout span=""> <LinearLayout <span class="hl-attribute" style="color: #7f0055;">xmlns:android</span>=<span class="hl-value" style="color: blue;">"http://schemas.android.com/apk/res/android"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:orientation</span>=<span class="hl-value" style="color: blue;">"horizontal"</span><span class="hl-tag" style="color: #008800;"> ></span>
<span class="hl-tag" style="color: #008800;"><fragment span="">
<fragment <span class="hl-attribute" style="color: #7f0055;">android:id</span>=<span class="hl-value" style="color: blue;">"@+id/myFragment"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"0dp"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_weight</span>=<span class="hl-value" style="color: blue;">"1"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:name</span>=<span class="hl-value" style="color: blue;">"com.example.MyFragment"</span><span class="hl-tag" style="color: #008800;"> /></span><span class="hl-tag" style="color: #008800;"></span></fragment></span>
</LinearLayout>
<span class="hl-tag" style="color: #008800;"></span></linearlayout></span> </pre>
<br />
第二種是在 Activity 的 layout 中不直接放入 <fragment ...=""> 而是放置一個 <framelayout ...=""></framelayout></fragment><br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;"><span class="hl-directive" style="color: maroon;"></span>
<span class="hl-tag" style="color: #008800;"><linearlayout span=""> <LinearLayout <span class="hl-attribute" style="color: #7f0055;">xmlns:android</span>=<span class="hl-value" style="color: blue;">"http://schemas.android.com/apk/res/android"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"match_parent"</span>
<span class="hl-attribute" style="color: #7f0055;">android:orientation</span>=<span class="hl-value" style="color: blue;">"horizontal"</span><span class="hl-tag" style="color: #008800;"> ></span>
<span class="hl-tag" style="color: #008800;"><framelayout span="">
<FrameLayout <span class="hl-attribute" style="color: #7f0055;">android:id</span>=<span class="hl-value" style="color: blue;">"@+id/layout_fragment"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_width</span>=<span class="hl-value" style="color: blue;">"0dp"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_weight</span>=<span class="hl-value" style="color: blue;">"1"</span>
<span class="hl-attribute" style="color: #7f0055;">android:layout_height</span>=<span class="hl-value" style="color: blue;">"match_parent"</span> />
</LinearLayout>
<span class="hl-tag" style="color: #008800;"></span></framelayout></span></linearlayout></span> </pre>
然後再由 Activity 的程式中將 Fragment 放置於指定 layout 中<br />
<pre class="programlisting" style="background-color: #ffffee; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-top-left-radius: 9px; border-top-right-radius: 9px; border: 1px solid rgb(187, 187, 187); font-family: 'Droid Sans Mono', Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 13px; line-height: 16px; margin: 10px; overflow: auto; padding: 1em; word-wrap: break-word;">FragmentManager fm = getFragmentManager();
MyFragment fragment = (MyFragment) fm.findFragmentById(R.id.layout_fragment);
<span class="hl-keyword" style="color: #7f0055; font-weight: bold;">if</span> (fragment==null || ! fragment.isInLayout()) {
fragment = new MyFragment();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.layout_fragment, fragment);
ft.commit();
}</pre>
這兩種方式的效果看起來差不多, 不過我個人比較偏愛後者, 原因如下 :<br />
1. 前者的方式 fragment 是一開始就嵌死在 Activity 中的, 後者可動態寫在任何事件中, 較靈活。<br />
2. 前者的 fragment 種類無法抽換, 後者可以動態決定要使用哪個 Fragment。<br />
3. 前者至少必須使用 Android API Level 11 以上, 後者有在 Support Library 中可向前相容。<br />
<br />
<h2>
讓我們來認識幾個重要的元件</h2>
<h3>
<a href="http://developer.android.com/reference/android/app/FragmentManager.html" target="_blank">FragmentManager</a></h3>
<div>
故名思義, FragmentManager 是管理 Activity 與 Fragment 互動的類別, 可以在 Activity 中透過 getFragmentManager() 方式取得。最常用的幾個方法</div>
<h4>
Fragment<span style="color: blue;"> </span><span style="color: red;">findFragmentById</span>(int id) :</h4>
<div>
透過指定 Layout 的 resource id 來取得該 Layout 中存在的 fragment 實體。</div>
<h4>
FragmentTransaction <span style="color: red;">beginTransaction</span>() :</h4>
<div>
傳回 FragmentTransaction 物件, 表示要開始一系列 Fragment 與 FragmentManager 間的互動。</div>
<div>
<br /></div>
<h3>
<a href="http://developer.android.com/reference/android/app/FragmentTransaction.html#remove(android.app.Fragment)" target="_blank">FragmentTransaction</a></h3>
<div>
一般我們工程師看到 Transaction 這個詞, 就會聯想到資料庫的 Transaction。也就是一次執行多個動作, 然後最後執行送交(commit)後要嘛一次全部執行完畢, 要不然就是中途發生錯誤我們可以回復(rollback)到最初始的資料狀態 , 不會有執行到一半的中間狀態。其實 FragmentTransaction 也差不多是這個意思, 我們可以交代給這個物件一系列的動作請他執行, 只是這些動作相對單純也不需要做資料回復, 所以少了 rollback 這樣的機制。</div>
<div>
<br /></div>
<div>
我們看一下 FragmentTransaction 常執行哪些動作 :</div>
<h4>
FragmentTransaction <span style="color: red;">add</span>(int containerViewId, Fragment fragment) :</h4>
將指定的 fragment 加入指定的 container 中, 這裡的 container 其實指的就是 Layout)。<br />
<h4>
FragmentTransaction <span style="color: red;">remove</span>(Fragment fragment) :</h4>
移除指定的 fragment, 此 fragment 內的所有狀態也會一併被刪除, 也就是這個 fragment 已不能再被使用了。如果這個 fragment 已經被加入某個 container 中, 則也會從該 container 中一併被清除。<br />
<h4>
FragmentTransaction <span style="color: red;">detach</span>(Fragment fragment) : </h4>
將指定的 fragment 從其所依附的 container 中移除。與 remove 方法不同的是, 該 fragment 的狀態仍會保留, 這個實體可以繼續透過 attach 方式使用。<br />
另外要注意的是, 當我們在程式中使用 detach 方式將 fragment 從畫面上拿下來, 也會一併將此 fragment 從 back stack 的階層中移除。<br />
<h4>
FragmentTransaction <span style="color: red;">attach</span>(Fragment fragment) : </h4>
將被 detach 過的 fragment 重新裝回原本的 container 中顯示, 當然也會將此動作加回 back stack 階層中。<br />
<h4>
FragmentTransaction <span style="color: red;">replace</span>(int containerViewId, Fragment fragment) :</h4>
將指定的 container 中存在的 fragment 進行 remove() 動作, 然後將指定的 fragment 透過 add() 的方式裝回該 container 中。<br />
要注意的是這裡是 remove + add, 而不是 detach + attach, 所以被移除的 fragment 是不能再繼續使用的。<br />
<h4>
FragmentTransaction <span style="color: red;">addToBackStack</span>(String name) :</h4>
將這個 Transaction 所執行的動作在 commit 後加入 back stack 階層中, 也就是當使用者按下返回鍵時, 這一連串的動作都會被復原。<br />
<br />
<h4>
FragmentTransaction <span style="color: red;">setCustomAnimations</span>(int enter, int exit) :</h4>
當開始執行這個 Transaction 時所使用的動畫效果(animation), enter 與 exit 的數字是指該動畫在 resource 中的 id 編號。<br />
<br />
<h4>
int <span style="color: red;">commit</span>() : </h4>
送交執行。但其實也不一定就會馬上被執行, 它會先去排隊, 等到主執行緒有空的時候才會執行。說是這樣說啦, 但除非主執行緒卡太多需要長時間的工作 (例如 ImageView 中指定 src 來自於一個 URL 之類的), 不然一般來說當我們呼叫 commit() 時幾乎都是會立刻反應在畫面上。<br />
<br />
在這邊附上一個範例, 可以做到兩個 Button 按下時切換不同的 Fragment, 有興趣的話可以下載回去看看<br />
<a href="https://dl.dropboxusercontent.com/u/16912340/android/samples/SimpleFragmentSample.zip">SimpleFragmentSample 範例</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy7SqB3xFlnrxoocLjVFNFaNq5hyvLLLQspqO3E_MD20VkJsueB6woTb-HAM_R8WVIQjo5dnf8FtMULS751YMQmY2AZm7TcGH9RqIRZcSPvhD1wTatGA_7kM8O4iej2coQe52wdgvG6QdP/s1600/fragment1+(Small).png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="457" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy7SqB3xFlnrxoocLjVFNFaNq5hyvLLLQspqO3E_MD20VkJsueB6woTb-HAM_R8WVIQjo5dnf8FtMULS751YMQmY2AZm7TcGH9RqIRZcSPvhD1wTatGA_7kM8O4iej2coQe52wdgvG6QdP/s640/fragment1+(Small).png" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<h2>
向前相容</h2>
<div>
向前相容總是個很讓人玩味的課題, 雖然說 Android 3.0 以後的版本到目前為止(2013年8月)已經佔了 Android 總市佔的 63.1% (<a href="http://developer.android.com/about/dashboards/index.html" target="_blank">資料來源</a>)。但工程師總是會被要求產品要想辦法支援到另外的那30%呀 .....。所以就會發現雖然有愈來愈多的 API 出來, 但大多數的專案都還是得用舊的寫法, 或者必須使用有相容的 API 在開發。</div>
<div>
<br /></div>
<div>
Google 很有善意的提供了很有名的 android-support-v4.jar 可以做到向前相容。但這裡的向前相容只是做到『本質不同, 但效果一樣』。舉例 Fragment 的例子來說, 原生的 library 中所使用的類別是 android.app.Fragment 類別, 但 support library 中所提供的卻是 android.support.v4.app.fragment 類別。這兩個類別所提供的方法幾乎一模一樣, 但兩者間並沒有任何繼承或共用的介面, 所以無法轉型。</div>
<div>
<br /></div>
<div>
在 Fragment 的向前相容部份, 其實我們只要做以下幾個小改變 : </div>
<div>
<ol>
<li>原繼承自 <b><span style="color: blue;">android.app.Fragment</span></b> 的類別, 改成繼承自 <span style="color: red;"><i><b>android.support.v4.app.Fragment</b></i></span> 類別。</li>
<li>管理 Activity 的類別, 從原本繼承自 <b><span style="color: blue;">android.app.Activity</span></b> 類別, 改成繼承自 <span style="color: red;"><b><i>android.support.v4.app.FragmentActivity</i></b></span> 類別。</li>
<li>取出 FragmentManager 的方法, 從原本使用 <span style="color: blue;"><b>getFragmentManager()</b></span>, 改成 <span style="color: red;"><i><b>getSupportFragmentManager()</b></i></span>。</li>
<li>而回傳的 <b><span style="color: blue;">android.app.FragmentManager</span></b> 類別, 改成 <b><i><span style="color: red;">android.support.v4.app.FragmentManager</span></i></b> 類別。</li>
<li>原本的<span style="color: blue;"><b> android.app.FragmentTransaction</b></span> 類別, 改成 <b><i><span style="color: red;">android.support.v4.app.FragmentTransaction</span></i></b> 類別。</li>
</ol>
</div>
<div>
以上除了 1,2,3 需要稍微動手修改之外, 改好後 4,5兩項只需按個 ctrl+shift+o 就可以改完了。一整個方便。</div>
<div>
<br /></div>
<div>
不過經驗上是, 就算 Fragment 可以往前相容了, 還是很可能遇到其它搭配的 API 讓你面臨到可能無法向前相容的窘境。例如 ActionBar .....。</div>
<div>
<br /></div>
<h2>
Fragment 的子類別</h2>
<div>
Fragment 就像是張白紙, 裡頭要塞什麼自己可以決定, 非常自由。但有許多的時候一個 Fragment 可能就只放一個控制項, 而這種情境又非常常出現 (例如一個 Fragment 裡頭只放一個 ListView), 而我們還是得設定好一個 layout 的 xml 檔。然後想辦法透過 id 把 control 取出來塞值, 既花時間又花精神。</div>
<div>
<br /></div>
<div>
所以 Android 官方就提供了幾個子類別, 讓你直接繼承這些子類別就可以使用了, 包含了 :</div>
<div>
<ol>
<li>ListFragment : 裡頭預設就有 ListView, 並提供方法與事件存取或控制 ListView。</li>
<li>DialogFragment : 包含了一個 Dialog 物件, 並提供控制 Dialog 的方法。</li>
<li>PreferenceFragment : 裡頭預設提供幾個方法讓你輕鬆存取 SharedPreference。</li>
<li>WebViewFragment : 和 ListFragment 的意思差不多, 裡頭就放了一個 WebView。</li>
</ol>
</div>
這些子類別怎麼使用在這我就不多說了, 只是提出來讓大家知道有這些東西可用就好, 有興趣的可以自己研究一下。要不然就得等到哪天我有空再寫了 .....。<br />
<br />
參考資料<br />
<a href="http://developer.android.com/training/basics/fragments/index.html">Building a Dynamic UI with Fragments</a><br />
<a href="http://developer.android.com/reference/android/app/Fragment.html">Fragment</a><br />
<a href="http://developer.android.com/reference/android/app/FragmentManager.html">FragmentManager</a><br />
<a href="http://developer.android.com/reference/android/app/FragmentTransaction.html">FragmentTransaction</a><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com1tag:blogger.com,1999:blog-6501248200092871158.post-80491147213639609822013-08-27T19:57:00.000-07:002013-08-29T01:38:47.330-07:00Fragment 介紹<br />
<h2>
<b>何謂 Fragment ?</b></h2>
用中文直譯就叫作片斷, 講白一點就是畫面的片斷, 把 Activity 畫面上指定一到多個區塊(layout), 用程式動態塞入這些我們準備好的片斷, 這就是 Fragment。<br />
<br />
其實在 Android 2.3 之前, Android 的畫面設計向來很單純, 手機畫面也不過就是 320dp, 也塞不了多少東西, 所以對於這些動切換畫面的效果一直沒有提出很好的解決方案, 大家鼻子摸一摸多寫些程式也就過去了, 反正手機嘛, 大家都是一個畫面跳另一個畫面的, 沒什麼太大的問題。<br />
<br />
但在 Android 3.0 時, Google 開始致力於發展平版裝置, 最明顯的不同就是畫面上現在可以塞進更多的東西。Google 希望能讓開發者更善用這些空間, 這時 Android 沒有像 iOS 一樣的 UserControl 的缺點就暴露出來了。 像是要在同一個畫面中切換不同的 Tab, 或是動態切換直向/橫向的操作, 開發者都要花更多的工在不同的地方去管理或重刻一樣的畫面。實在很麻煩。<br />
<br />
所以, Fragment 就是 Google 提出來的解決方案 !<br />
<br />
借用一下 google 官方網站的圖 :<br />
<img alt="" src="http://developer.android.com/images/fundamentals/fragments.png" /><br />
<br />
<br />
<a name='more'></a><br />
以這個例子來說, 如果我們希望做到在平板時畫面設計是像左邊的圖, 在手機時畫面設計是像右邊的圖。 操作上都是點選綠色部份的 ListView 後, 在藍色的部份來觀看內容, 只是在手機時因為畫面較小, 所以點選後可以跳到另一個畫面來觀看較詳細的內容。而平板時因為畫面本來就比較大, 可以在點選 ListView 後直接觀看到所選的內容, 這樣操作上更為簡便而直覺。<br />
<br />
這時我們就可以設計成兩個 Fragment , 在平板上就在一個 Activity 中去左邊放 FragmentA 右邊放 FragmentB。而在手機的畫面設計就使用兩個 Activity, 分別放入 FragmentA 與 FragmentB。<br />
<br />
這樣一來, 在平板和手機在程式撰寫時的差別, 就只在於 Activity 的操作邏輯不同, 而 FragmentA 與 FragmentB 怎麼呈現就不需要因裝置不同而進行差別處理。<br />
<b></b><br />
<b><br /></b>
<br />
<h3>
<b>還可以用在哪些地方 ?</b></h3>
<div>
如果你可以理解上面的例子, 那麼你或許也可以理解下面這些例子</div>
<div>
<br /></div>
<h4>
Navigation Drawer : </h4>
像 facebook 的拉出式選單一樣, 在選單中點選不同的選項, 而呈現不同的畫面內容, 而主畫面則是不變, 也是透過 fragment 來搞定<br />
<br />
<img height="283" src="http://developer.android.com/design/media/navigation_drawer_overview.png" width="640" /><br />
<br />
<h4>
ActionBar.Tab : </h4>
畫面上提供幾個不同的頁籤, 點選不同的頁籤顯示不同的內容, 這也是很常見的模式, 也是透過 fragment 來做到這樣的效果<br />
<img src="http://developer.android.com/design/media/action_bar_pattern_considerations.png" /><br />
<br />
Swipe Views :<br />
左右滑動畫面來切換上一頁或下一頁, 就是使用 ViewPager 控制項搭配 Fragment 來做到這樣的效果<img height="341" src="http://developer.android.com/design/media/swipe_views2.png" width="640" /><br />
<br />
當然還有很多其它地方都可以使用 Fragment, 族繁不及備載, 總之很重要就是了......<br />
<br />
<h3>
Fragment 的生命週期</h3>
Fragment 有它自己的生命週期, 只不過它的生命週期往往和它所依附的 Activity 有所相關, Google 官方的圖可以很清楚看出它生命週期的觸發順序<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5IQXvL3_p6LnV4k5M1SKveL1ou3cHles4QKehhuXo3bhYszByZorRNpOWwQpRzesvcpHmoKy85U9NcigQktS3fcExi2jADRMd_w7Q9fXd6MMe50SjMTdHy2JBk2gO6mCkIsqvpefd1dT1/s1600/fragment_lifecycle.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5IQXvL3_p6LnV4k5M1SKveL1ou3cHles4QKehhuXo3bhYszByZorRNpOWwQpRzesvcpHmoKy85U9NcigQktS3fcExi2jADRMd_w7Q9fXd6MMe50SjMTdHy2JBk2gO6mCkIsqvpefd1dT1/s640/fragment_lifecycle.png" width="574" /></a></div>
<br />
<br />
長得跟 Activity 的生命週期還蠻像的, 只是多了幾個陌生的事件, 其中有幾個比較值得注意的事件 :<br />
<h4>
onCreate() : </h4>
系統建立此 fragment 時觸發。在你實作的 Fragment 物件中, 若有什麼重要元件你希望在這個 fragment 從 pause 或 stopped 狀態回恢時保留的, 請在此初始化。<br />
<br />
<h4>
onCreateView() :</h4>
這個 fragment 第一次開始畫他的 UI 畫面時觸發。<span style="color: red;"> 如果你想插手處理畫面, 例如在 TextView 上設定文字之類的, 可以在這個事件中處理。</span><br />
回傳值是這個 fragment 根畫面的 View 物件。如果這個 fragment 沒有畫面, 則傳回 null 即可。<br />
<br />
<h4>
onActivityCreated() :</h4>
在 onCreateView() 事件之後被觸發, 表示此 fragment 所附屬的 Activity 已經準備好了。<span style="color: red;">如果需要取得 Context 物件來進行處理, 可在此事件時透過 getActivity() 方法取得利用。</span><br />
<br />
<h4>
onPause() :</h4>
Fragment 暫停事件, 這是使用者離開這個 Fragment 的第一個跡象。雖然不代表這個 Fragment 一定會被銷毀(destroyed), 但使用者也很可能從此不再使用這個 Fragment 了。所以<span style="color: red;">如果我們要儲存或保留這個 Fragment 目前的狀態的話, 通常會在這個事件中處理。</span><br />
<span style="color: red;"><br /></span>
<span style="color: red;"></span><br />
<h2>
Fragment 與 Activity 間的溝通</h2>
<h3>
Fragment 的初始化參數</h3>
<div>
曾經看過一些文章, 都會在 Fragment 的建構子中加上參數提供 Fragment 初始化其內容。但在 ADT 裡會對這種行為提出警告。這種警告在執行階段通常還是可以正確執行, 但為什麼會被警告呢 ?</div>
<div>
<br /></div>
<div>
我猜原因應該是如果我們是利用在 layout 的 xml 中描述 <fragment ...=""> 的標籤來建立 Fragment 的話, 系統只會呼叫 預設的建構子, 也就是不含任何參數的那種。如果你強制宣告一個需要傳遞參數的建構子, 那麼使用這種方式建立的 Fragment 就會因為找不到預設建構子而出錯。如果你是保留預設的建構子, 又再 overloading 帶參數的建構子, 那麼在使用標籤方式建立 fragment 後還是可能因為缺乏初始資料所需的物件而產生錯誤。所以 ADT 才會對此行為提出警告。</fragment></div>
<div>
<br /></div>
<div>
那建議的方式是什麼 ?</div>
<div>
<br /></div>
<div>
Activity 若是要傳遞參數給 Fragment, 可以<span style="color: red;">透過 fragment.setArguments(Bundle) 的方法將要傳遞的值透過 Bundle 物件傳入 Fragment 中</span>。</div>
<div>
而 Fragment 可以在 onCreate() 事件中<span style="color: red;">透過 getArguments() 方式取回 Bundle 物件</span>, 這樣就兩全齊美, ADT 也不會再提出警告囉!</div>
<div>
<br /></div>
<h3>
Fragment 與 Activity 的互動與事件處理</h3>
<div>
以這篇文章最上面那張圖來舉例, 平板電腦上我想要點選 FragmentA 中的 ListView 選項, 然後在 FragmentB 中顯示我所選的內容, 該怎麼做 ?</div>
<div>
<br /></div>
<div>
我看過一些文章和書籍中的範例, 會先在 FragmentA 的 listView.onItemClick 事件中, 先利用 getActivity() 方法取得 Activity 的實體, 然後再透過 Activity 在指定的 layout 中找出或建立 FragmentB 的實體, 然後再把所選到的項目告訴 FragmentB 讓它呈現, 模擬器一執行, 跑得又好又順, 然後宣告問題解決了!</div>
<h4>
<span style="color: blue;">問題解決了嗎 ? 這裡的作法可能會有另外兩個問題......</span></h4>
<div>
第一個問題就是 : 不夠漂亮 ! </div>
<div>
在物件導向 OO 的設計原則中, 每個物件責任是愈清楚愈好。FragmentA 的責任就是呈現表單, FragmentB 的責任就是呈現內容, 多麼乾淨俐落! 但以上面的作法來說, FragmentA 除了呈現表單外, 還要負責找出 FragmentB, 然後交代 FragmentB 選到的項目。然後 ......</div>
<div>
Activity 表示輕鬆, FragmentB 表示無壓力, FragmentA 可能因為過勞而要去看心理醫生 ....</div>
<h4>
<span style="color: blue;">當然漂不漂亮也是見仁見智, 丫就可以跑不然你咬我呀......</span></h4>
<div>
那就來看第二個情況, 這段程式可以在右邊那張圖的手機板執行嗎 ?</div>
<div>
FragmentA 一樣處理 onItemClick 事件, 然後順利透過 getActivity() 找出 Activity 的實體, 然後再透過 Activity 實體想找出 FragmentB ...... 奶奶的熊沒有 FragmentB !!</div>
<div>
<br /></div>
<div>
當然沒有, 這時的 FragmentB 是放在 ActivityB 裡頭的呀.....</div>
<div>
<br /></div>
<div>
所以我們要在 FragmentA 中再加一個判斷, 看看如果 FragmentB 不存在的話就跳 ActivityB ..... 你的 FragmentA 累不累呀 .....</div>
<div>
<br />
其實正經的來說我也懶得管 FragmentA 累也不累, 只是日後 FragmentA 的責任太重, 程式碼邏輯複雜, 維護會更加不易 .....</div>
<h4>
<span style="color: blue;">丫不然你想怎樣 !?</span></h4>
<div>
就像一開始說的, 我們應該讓每個物件的責任切割的清楚點。以這個例子來看,<br />
手機版 :<br />
<span style="color: magenta;">FragmentA : 負責呈現 ListView, 被選取後就回報給 ActivityA 已被選取項目</span><br />
ActivityA : 負責每個 Fragment 間彼此的溝通, 若 FragmentA 回報選取項目後, 開啟 ActivityB<br />
ActivityB : 負責接收 ActivityA 傳來的選取項目, 找出 FragmentB 然後塞給它<br />
<span style="color: magenta;">FragmentB : 接收選取項目, 呈現資料</span><br />
<br />
平板版 :<br />
<span style="color: magenta;">FragmentA : 負責呈現 ListView, 被選取後就回報給 ActivityA 已被選取項目</span><br />
ActivityA : 負責每個 Fragment 間彼此的溝通, 若 FragmentA 回報選取項目後, 找出 FragmentB 後塞給它<br />
<span style="color: magenta;">FragmentB : 接收選取項目, 呈現資料</span></div>
<div>
<br />
仔細看, 無論是手機還是平版, FragmentA 和 FragmentB 的責任和處理方式都是一樣的, 這樣才能達到 reuse 的效果。而 Activity 的部份, 只要分別撰寫手機版和平板版的 Actvitiy 即可將責任切割乾淨了。<br />
<br />
另外關於 Fragment 的使用方式, 請看<a href="http://lucifernet.blogspot.tw/2013/08/fragment_29.html">另一篇文章</a>。<br />
<br />
<br />
參考資料<br />
<a href="http://developer.android.com/guide/components/fragments.html" target="_blank">Android Developer</a><br />
<a href="http://www.vogella.com/articles/AndroidFragments/article.html" target="_blank">Using Fragments in Android - Tutorial</a></div>
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-44573343151650312292013-08-12T00:00:00.002-07:002013-08-27T20:28:59.227-07:00Producteev 功能介紹<span id="docs-internal-guid--755021e-713e-211b-3ecd-7a69978e014a" style="font-size: 15px; line-height: 17px; white-space: pre-wrap;"><span id="docs-internal-guid-766dc88f-7141-d47d-5f0d-1eba607ed882"></span></span><br />
<div>
<a href="http://www.producteev.com/">Producteev</a> 是一個管理團隊工作用的網路應用程式,之前用過一陣子,後來因為某些原因就慢慢的沒在用了。前陣子這個產品被 JIVE 這家公司買下,現在幾乎是完全免費的。如果瞭解裡頭的一些概念的話應該更容易使用,於是我整理了一下,有興趣的可以參考囉<br />
<br />
<ol style="margin-bottom: 0pt; margin-top: 0pt;"><span id="docs-internal-guid--755021e-713e-211b-3ecd-7a69978e014a" style="font-size: 15px; line-height: 17px; white-space: pre-wrap;"><span id="docs-internal-guid-766dc88f-7141-d47d-5f0d-1eba607ed882"><span id="docs-internal-guid-766dc88f-7145-206a-d395-69764f0a1d62">
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="line-height: 1.15;">Network : 工作網絡。</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;"><img height="203px;" src="https://lh5.googleusercontent.com/azxuf6UKO7H5BMl3g1LL3PmuplHxmi4hNr-hxZ4Y-9I5VL6hEfyAkLdL0Dc83CZyXoFmUU_Wa0w1jXNgp2xda_zfFR2BtpTSlC9vyP7ZOoV2KTW6HLs17zWj" width="317px;" /></span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-alpha; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">每個用戶可以建立多個不同的工作網絡,例如:Family Network, Office Network 等分類。用來區分不同性質的工作群體。</span><br />
<span style="vertical-align: baseline;"><br /></span></div>
</li>
<a name='more'></a>
</ol>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">People : 網絡用戶</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;"><img height="259px;" src="https://lh4.googleusercontent.com/6U0H05_p_ui2z_7Tiy94xdmnFnRTs1ZHjEIqV0p3d_naU3cI_CUGsCd4TPUULGUp5dcm0D_D_TvMYLiRjsoqsw8N_hZZnQtBfioGrs2ynCL6UhZmScUajsDb" width="284px;" /></span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-alpha; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">每個 Network 可以透過 email 邀請數個網絡用戶,邀請時可以設定該用戶是否為網絡管理人或一般成員。</span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Network Admin(s) : 管理員<span class="Apple-tab-span" style="white-space: pre;"> </span></span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">可以存取所有公開或私有的 project</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">可以編輯專案</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">可以管理訂閱設定</span></div>
</li>
</ol>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Network Member(s) : 一般成員</span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">可以建立專案</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">可以編輯自已建立的專案</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">若管理員允許,可以邀請其它人加入此 Network</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">可以存取所有公開的專案</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">若是私有專案,建立者與有被邀請的用戶可以存取。</span></div>
</li>
</ol>
</ol>
</ol>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Projects : 專案</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;"><img height="178px;" src="https://lh6.googleusercontent.com/EXLIHrpcXVhRn_LqWcGYuj9aTEUURm1zbwsmNzovXoUP8w2-K9qvU-gHc9YVYbtHpm-TvOoAM5kuHx2XjA-r3Z1BPVkw5RgV-fxnM3Y4DzVKwYuJU8ClQdPbrw" width="281px;" /></span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-alpha; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">比較像是此 Network 下的工作分類。每個用戶都可以為自已所負責的工作建立專案,並且邀請其它共同工作者。專案可分為兩種不同的性質。</span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Open : 公開專案(預設):Network 中的所有人皆可存取</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Private : 私有專案 : 僅有被邀請的特定成員才可以存取</span></div>
</li>
</ol>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-alpha; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">進階設定</span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Unlocked : 非鎖定(預設):任何人皆可編輯專案內的Task(任務)</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Locked : 鎖定:僅有任務發起人、任務負責人與專案管理員可以編輯其任務內容</span><br />
<span style="vertical-align: baseline;"><br /></span></div>
</li>
</ol>
</ol>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Tasks : 任務</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;"><img height="207px;" src="https://lh6.googleusercontent.com/PfCispEwyQ9Xnr2mytCedR-Tv0_6ZSbmTj9m0_CvzNIAL9GT-qpv9IiE14s95XAkD2EV6RWHkRqJIfKg_Vzok_WztRyP2sZDna-kXGysHA_uSebtEptvMBhO" width="361px;" /></span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-alpha; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">專案中可建立多個不同的任務</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-alpha; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">任務含有以下屬性</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;"><img height="336px;" src="https://lh6.googleusercontent.com/2Hr4_FdNEGgax0mSXjksQnrBA3R9YhNkOsIZzPIJZtYSAEV3dPlTBuhhKLroODMU75eoZTxyOxvaGhpQFikUj6ieZubo6tXEN6BgjINAiTxCiJLc_rfnqASL" width="612px;" /></span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Assignees :任務負責人, 至少一個, 可指派多個,預設會帶入任務發起人</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Due date : 到期日,可用來提醒此項工作何時應完成。</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Labels : 標籤,可貼上一到多個標籤,用來跨專案進行分類或過濾</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Followers : 追蹤人,主要是觀察這項任務的執行情況,但不負責此任務的執行。</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Subtasks : 子任務。可用來劃分任務的每個個別步驟,若完成該步驟可將該內容勾選完成</span></div>
</li>
<li dir="ltr" style="font-family: Arial; list-style-type: lower-roman; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Note : 筆記。可詳述任務內容,或者上傳附加檔案。輸入 @ + 人名可加入追蹤人。</span></div>
</li>
</ol>
</ol>
<li dir="ltr" style="font-family: Arial; list-style-type: decimal; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">Labels : 標籤</span></div>
<div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;"><img height="93px;" src="https://lh4.googleusercontent.com/invvUZt4Y4MnVS_qwkaMEt90Yp8DVCA_7t9uJtJTdwztapDTBvUdWbA9xCMbmH9zQ6kiaHGZ9NIhSoCoyA-irJjQMQnINoMANr106bWfEwy4d_9GsCaUkWPr" width="283px;" /></span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="font-family: Arial; list-style-type: lower-alpha; vertical-align: baseline;"><div dir="ltr" style="line-height: 1.15; margin-bottom: 0pt; margin-top: 0pt;">
<span style="vertical-align: baseline;">標示任務用。當任務性質可能跨不同的專案時,可以用標籤來進行過濾或分類…</span></div>
</li>
</ol>
</span></span></span></ol>
<span id="docs-internal-guid--755021e-713e-211b-3ecd-7a69978e014a" style="font-size: 15px; line-height: 17px; white-space: pre-wrap;"><span id="docs-internal-guid-766dc88f-7141-d47d-5f0d-1eba607ed882"><span id="docs-internal-guid-766dc88f-7145-206a-d395-69764f0a1d62">
<br /><span style="font-family: Arial; vertical-align: baseline;"></span></span></span></span></div>
<span id="docs-internal-guid--755021e-713e-211b-3ecd-7a69978e014a" style="font-size: 15px; line-height: 17px; white-space: pre-wrap;"><span id="docs-internal-guid-766dc88f-7141-d47d-5f0d-1eba607ed882">
<span style="font-family: Arial; vertical-align: baseline;"></span></span></span>Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-33472664936061694162013-08-09T03:11:00.001-07:002013-08-27T20:29:39.573-07:00如何在 Android 中解析 QRCode 或 BarCode在 android 中要解析 QRCode 其實很簡單,簡單到連 library 都不需要裝 XD.<br />
簡單的來說,就是直接開啟 QRCode 的掃描程式,然後等掃描程式將結果解析完後傳回來就好囉!<br />
<br />
那麼..... 如果裝置上沒安裝掃描程式怎麼辦?<br />
<br />
那就..... 直接叫他去下載不就解決了 XD<br />
<br />
我這邊使用的是 google 的 zxing 掃描程式,雖然說是 google 出的,但 android 手機並沒有預設安裝。<br />
<code>
</code>
<pre><code>Button btnShoot = (Button) this.findViewById(R.id.btnShoot);
btnShoot.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
Intent intent = new Intent("com.google.zxing.client.android.SCAN");
intent.putExtra("SCAN_MODE", "PRODUCT_MODE");
intent.putExtra("SAVE_HISTORY", false);
startActivityForResult(intent, 0);
} catch (Exception ex) {
// 如果發生錯誤,則表示應該還未安裝 ZXing 程式,幫他導到 market 下載吧
Uri marketUri = Uri.parse("market://details?id=com.google.zxing.client.android");
Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
startActivity(marketIntent);
}
}
});
</code></pre>
<code>
</code>
<br />
<br />
<a name='more'></a><br /><br />
然後在 onActivityResult 事件中等結果
<code>
</code><br />
<pre><code>@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0) {
if (resultCode == RESULT_OK) {
String contents = data.getStringExtra("SCAN_RESULT");
Toast.makeText(this, contents, Toast.LENGTH_SHORT);
} else if (resultCode == RESULT_CANCELED) {
// Handle cancel
}
}
}
</code></pre>
<code>
</code>
有興趣的可以直接到 zxing 的官方網站去研究研究<br />
<a href="https://code.google.com/p/zxing/">https://code.google.com/p/zxing/</a><br />
<br />
我一開始是看了<a href="https://code.google.com/p/zxing/wiki/GettingStarted">這篇</a> ,<br />
看完後害我嚇壞了。<br />
居然還要搞一堆有的沒的 .... <br />
想嚇唬誰呀~<br />
<br />Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com1tag:blogger.com,1999:blog-6501248200092871158.post-36188170602142852332012-05-15T15:41:00.002-07:002012-05-15T15:41:30.941-07:00Diablo3 終於購買 !!<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3zCPpxrrTFS1Ojst7JEsbzAAJ5Zpms3MHGIISPuYtFcNnvix1lYV3CM9RWKMgm4Eyc-1VgOSPEsl7o2AtkXFr4BMkwZXFRflV_6vtq8LBmKcHDhQQeWOdx1enTC8PL0JlkUXlf-PY-gAh/s1600/%E6%93%B7%E5%8F%96.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="386" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3zCPpxrrTFS1Ojst7JEsbzAAJ5Zpms3MHGIISPuYtFcNnvix1lYV3CM9RWKMgm4Eyc-1VgOSPEsl7o2AtkXFr4BMkwZXFRflV_6vtq8LBmKcHDhQQeWOdx1enTC8PL0JlkUXlf-PY-gAh/s640/%E6%93%B7%E5%8F%96.PNG" width="640" /></a></div>
<br />
不經一番寒徹骨, 焉得梅花撲鼻香, 昨天想購買實體遊戲結果失敗, 今天早上終於成功在 MyCard 經過四次複雜的認證機制後成功買足了 1500 點戰網幣(450*3, 150*1), 總算可以如願了 .....<br />
<br />
可惜的是這個版本只支援 windows 版, 我的小白不能安裝(可能也跑不太動吧 XD), 至於只有繁中沒有其它語系...... 我只能說我是中文系的所以也不太介意呀 XD<br />
<br />
至於盒子和說明書 .... 管它的, 少砍一顆樹也好。總之買到了, 晚上應該可以好好磨磨刀了, 嘿嘿嘿 ......Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-11504749984183397242012-05-15T05:30:00.000-07:002012-05-15T05:30:06.912-07:00Diablo 3 變調開箱文<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyrGo4roTbs14s82Kri8i0mGurFabYZslhKtOkVTt1KnfWShbWtoGNAt8UxtoUG-RLzNB1uXfpbQ9lLVxMjUcdmvZBaHWRZakdJMBBTYxcEITmalcNPtA8ISVWza98iCodHjYJojCsCrXX/s1600/%E6%93%B7%E5%8F%96.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="379" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyrGo4roTbs14s82Kri8i0mGurFabYZslhKtOkVTt1KnfWShbWtoGNAt8UxtoUG-RLzNB1uXfpbQ9lLVxMjUcdmvZBaHWRZakdJMBBTYxcEITmalcNPtA8ISVWza98iCodHjYJojCsCrXX/s640/%E6%93%B7%E5%8F%96.PNG" width="640" /></a></div>
<br />
自從2000年 Diablo 2 發表之後, 隔了12年終於等到它的下一代了 ...... 這款遊戲實在太有名了, 我應該不用再多寫什麼, 但12年呀 ..... 2代發表時出生的小孩, 現在國小都快畢業了耶.....<br />
<br />
原本想寫個開箱文的, 奈何下班後在新竹跑了8間便利商店, 2間燦坤, 全都沒貨, 想說放棄開箱, 直接購買線上版的, 又要先儲值戰網幣, 然後很自然的戰網幣的線上購買網站也等同於完全癱瘓。所以這篇從開箱文變成抱怨文,他奶奶的這些線上遊戲購物網站核心怎麼不改用 DSA 呀, 爛死了爛透了 !!<br />
<br />
抱怨完, 決定去拖個地板發洩我不滿的情緒 ......Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-50185526568054159862012-05-14T06:32:00.000-07:002012-05-14T06:32:04.921-07:00凋萎<a href="http://www.flickr.com/photos/luciferworld/7195882032/" title="Flickr 上 luciferworld 的 DSC_2689"><img alt="DSC_2689" src="http://farm6.staticflickr.com/5039/7195882032_061989f67c_b.jpg" width="75%" /></a><br />
<br />
恁憑自以為的細心呵護<br />
而綠意放縱的玉蘭<br />
早在斷根的瞬間<br />
就已決定了命運<br />
<br />Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-85871260340170503462012-05-09T07:02:00.004-07:002012-05-09T07:02:52.039-07:00九陽豆漿機 DJ13M-D08D 開箱之前曾經試過用家裡那台超威猛果汁機來自己煮豆漿, 但要磨要煮要揉要濾的一堆工程, 弄好一鍋要花2個小時左右, 所以後來還是很識相的直接買現成的罐裝豆漿來喝。但現在 7-11 什麼都在趁火打劫的年代, 想說能省一點是一點。 聽很多人說豆漿機方便又好用, 對每天都要做早餐的我來說應該非常實用, 所以去抱了一台來玩看看囉!<br />
<br />
抱回來的就是這一台, 九陽豆漿機 DJ13M-D08D<br />
<a href="http://www.flickr.com/photos/luciferworld/7164394756/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2449"><img alt="DSC_2449" src="http://farm8.staticflickr.com/7098/7164394756_87cd007225_b.jpg" width="75%" /></a><br />
<br />
開箱後有豆漿機, 電線, 說明書和保證書, 量杯和濾網, 重點是還幫我準備了一條綠色菜瓜布 XD<br />
<a href="http://www.flickr.com/photos/luciferworld/7164393720/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2452"><img alt="DSC_2452" src="http://farm8.staticflickr.com/7092/7164393720_7a33d94340_b.jpg" width="100%" /></a><br />
<br />
<a name='more'></a><br /><br />
要做豆漿當然要準備黃豆, 因為時間不早了, 只能去頂好買貴鬆鬆的非基因改造黃豆, 500g 65元實在很搶錢, 去傳統市場買一斤600g 只要 40元。至於是不是基因改造的食品其實我並不會很在意,非基改的黃豆抗性差, 需要使用大量農藥來抵抗病蟲害, 我並不覺得這樣對環境和食用者比較好 XD<br />
<a href="http://www.flickr.com/photos/luciferworld/7164392432/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2456"><img alt="DSC_2456" src="http://farm8.staticflickr.com/7243/7164392432_a7255ac6b5_b.jpg" width="75%" /></a><br />
<br />
機頂來一張, 看起來除了豆漿外還能做很多東西呀<br />
<a href="http://www.flickr.com/photos/luciferworld/7164391116/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2458"><img alt="DSC_2458" src="http://farm8.staticflickr.com/7242/7164391116_3402d98747_b.jpg" width="75%" /></a><br />
<br />
要磨豆子要加熱就靠這根了<br />
<a href="http://www.flickr.com/photos/luciferworld/7164389794/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2460"><img alt="DSC_2460" src="http://farm9.staticflickr.com/8142/7164389794_94483658d1_b.jpg" width="75%" /></a><br />
<br />
手柄上有個像抽座一樣的洞就是供電給磨豆柱用的, 而機身本身就只是個大保溫杯<br />
<a href="http://www.flickr.com/photos/luciferworld/7164388496/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2464"><img alt="DSC_2464" src="http://farm8.staticflickr.com/7242/7164388496_11fff4b471_b.jpg" width="100%" /></a><br />
<br />
來實戰囉, 要煮豆漿當然要有黃豆和水, 水大約用1300ml, 黃豆用他附的量杯一杯半左右, 想喝濃點就多加點<br />
<a href="http://www.flickr.com/photos/luciferworld/7164387190/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2667"><img alt="DSC_2667" src="http://farm6.staticflickr.com/5315/7164387190_c0ce1d1c9a_b.jpg" width="100%" /></a><br />
<br />
倒進壺中<br />
<a href="http://www.flickr.com/photos/luciferworld/7164385704/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2668"><img alt="DSC_2668" src="http://farm9.staticflickr.com/8020/7164385704_2a831ff2d5_b.jpg" width="100%" /></a><br />
<br />
一般煮豆漿黃豆要先泡軟, 這台和我的果汁機一樣可以直接磨乾豆, 省去泡豆子的時間, 直接選乾豆模式開啟即可<br />
<a href="http://www.flickr.com/photos/luciferworld/7164384074/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2670"><img alt="DSC_2670" src="http://farm8.staticflickr.com/7097/7164384074_b4ae827c14_b.jpg" width="100%" /></a><br />
<br />
20分鐘左右就機器就會自動停了, 就可以起鍋囉<br />
<a href="http://www.flickr.com/photos/luciferworld/7164382748/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2671"><img alt="DSC_2671" src="http://farm6.staticflickr.com/5326/7164382748_b8da0b6f5d_b.jpg" width="100%" /></a><br />
<br />
起鍋後可以先把豆漿放涼, 一則是先讓豆渣沉澱之後會比較好濾, 再者要擠豆渣時比較不會這麼燙手<br />
<a href="http://www.flickr.com/photos/luciferworld/7164381442/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2674"><img alt="DSC_2674" src="http://farm9.staticflickr.com/8163/7164381442_de57026dba_b.jpg" width="100%" /></a><br />
<br />
濾豆渣, 基本上如果直接使用他送的濾網濾, 會喝到不少豆渣 ...... 還是自己去雜貨店買豆漿濾布來濾可以濾的比較乾淨<br />
<a href="http://www.flickr.com/photos/luciferworld/7164378958/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2676"><img alt="DSC_2676" src="http://farm8.staticflickr.com/7075/7164378958_88298588a9_b.jpg" width="100%" /></a> <br />
<br />
濾到最後就是想儘辦法把最後的原汁擠出來囉, 精華都在最後呀~<a href="http://www.flickr.com/photos/luciferworld/7164377664/" style="clear: right; display: inline !important; margin-bottom: 1em; margin-left: 1em;" target="_blank" title="Flickr 上 luciferworld 的 DSC_2681"><img alt="DSC_2681" src="http://farm8.staticflickr.com/7075/7164377664_9098d4b670_b.jpg" width="100%" /></a><br />
<br />
濾完的乾豆渣, 有人說可以拿去煎蛋, 有人說可以拿去做煎餅, 我曾經加了一點在我從沒施肥就快枯死的辣椒樹上當有機肥, 馬上就立竿見影起死回生, 可惜的是幾日後豆渣就臭掉了, 受不了那個腐臭味最後整株都丟掉了 .... 囧rz。黃豆本身屬高普磷的東西, 有痛風的人還是不要捨不得這些渣好了 ......<a href="http://www.flickr.com/photos/luciferworld/7164376282/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2685"><img alt="DSC_2685" src="http://farm6.staticflickr.com/5330/7164376282_01e3f1fb8c_b.jpg" width="100%" /></a><br />
<br />
最後的成品, 一鍋豆漿 XD<br />
<a href="http://www.flickr.com/photos/luciferworld/7164374436/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2686"><img alt="DSC_2686" src="http://farm8.staticflickr.com/7227/7164374436_8b3073cfca_b.jpg" width="100%" /></a><br />
<br />
最後總結一下感想好了<br />
優點 :<br />
做豆漿便得很方便, 要喝隨時都可以自己做, 也不用擔心喝進太多添加物<br />
一斤黃豆大約可煮8公升, 成本不到外頭賣的 1/3<br />
煮出來的口感還 OK<br />
<br />
缺點 :<br />
自動清洗並沒有辦法清的很乾淨, 還是自己清理比較實在<br />
一台快5000的價格來說, 我覺得算蠻貴的<br />
濾豆渣才是煮豆漿最麻煩的地方, 但這台機器沒辦法自動處理<br />
無法自己設定煮的時間, 感覺再多煮一下味道應該更能出來Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-8518606920280129042012-05-03T06:57:00.000-07:002012-05-03T06:58:43.790-07:00十二寮 水濂洞 北埔一日遊<br />
非常久沒更新這個部落格了, 一則是最近比較少出去, 工作也比較煩心, 趁著五一勞動節的假期晴朗的天氣, 和幾個朋友一同去苗栗找找桐花拍拍照囉~<br />
<br />
第一站是到十二寮, 這邊的桐花算多, 大致就一條路上能看到而已, 不過掉在地上的桐花也被踩平得差不多了, 水溝裡的還有一些保留較完整的。另外這裡的空氣有種腐壞的味道, 並不是很好聞 XD<br />
<br />
這張照片是將 D700 伸到水溝中盲拍, 不過 F4 的光圈感覺太大了, 景深過淺, 看起來有點頭暈的感覺 XD<br />
<a href="http://www.flickr.com/photos/luciferworld/6991632832/" title="Flickr 上 luciferworld 的 DSC_2525"><img alt="DSC_2525" src="http://farm9.staticflickr.com/8155/6991632832_1018792230_b.jpg" width="75%" /></a><br />
<br />
十二寮步道其實還不長, 可以輕鬆走完<br />
<a href="http://www.flickr.com/photos/luciferworld/6991635818/" title="Flickr 上 luciferworld 的 DSC_2583"><img alt="DSC_2583" src="http://farm9.staticflickr.com/8149/6991635818_3254eb36e1_b.jpg" width="100%" /></a><br />
<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6991633032/" title="Flickr 上 luciferworld 的 DSC_2542"><img alt="DSC_2542" src="http://farm8.staticflickr.com/7231/6991633032_4a3aefd1c2_b.jpg" width="75%" /></a><br />
<br />
<br />
<a name='more'></a><br /><br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/7137715199/" title="Flickr 上 luciferworld 的 DSC_2543"><img alt="DSC_2543" src="http://farm8.staticflickr.com/7091/7137715199_7502e43376_b.jpg" width="75%" /></a><br />
<br />
前陣子的天氣實在糟到可怕, 難得抓到個晴天, 可以拍拍藍天白雲, 這張的藍天是用調控檔調出來深到不自然的藍色, 但更能和雲的潔白對應出張力<br />
<a href="http://www.flickr.com/photos/luciferworld/7137717457/" title="Flickr 上 luciferworld 的 DSC_2588"><img alt="DSC_2588" src="http://farm8.staticflickr.com/7134/7137717457_0b34784652_b.jpg" width="100%" /></a><br />
<br />
接下來去了水濂洞, 這裡沒有桐花, 但有一縷陽光照進幽暗的山谷裡<br />
<a href="http://www.flickr.com/photos/luciferworld/6991637486/" title="Flickr 上 luciferworld 的 DSC_2618"><img alt="DSC_2618" src="http://farm8.staticflickr.com/7040/6991637486_ac36bf55de_b.jpg" width="75%" /></a><br />
<br />
有時只要有最簡單的光線和影子, 就是個很好的主題了, 而黑白照片更容易突顯光和影的交錯<br />
<a href="http://www.flickr.com/photos/luciferworld/6991637686/" title="Flickr 上 luciferworld 的 DSC_2624"><img alt="DSC_2624" src="http://farm8.staticflickr.com/7123/6991637686_2742aef0e4_b.jpg" width="75%" /></a><br />
<br />
光和影的搭配更顯階梯的古樸<br />
<a href="http://www.flickr.com/photos/luciferworld/6991638088/" title="Flickr 上 luciferworld 的 DSC_2625"><img alt="DSC_2625" src="http://farm8.staticflickr.com/7198/6991638088_649a38ecba_b.jpg" width="75%" /></a><br />
<br />
獅頭山的涼亭, 藉由樹葉的遮蔽來拍攝太陽星芒, 看來 sigma 24-70mm F2.8 這顆鏡頭還不錯, 只有少許的光斑<br />
<a href="http://www.flickr.com/photos/luciferworld/7137718039/" title="Flickr 上 luciferworld 的 DSC_2599"><img alt="DSC_2599" src="http://farm9.staticflickr.com/8166/7137718039_21e706fb9a_b.jpg" width="100%" /></a><br />
<br />
來了好幾次的北埔老街, 招牌地標的廟宇<br />
<a href="http://www.flickr.com/photos/luciferworld/7137720149/" title="Flickr 上 luciferworld 的 DSC_2633"><img alt="DSC_2633" src="http://farm8.staticflickr.com/7116/7137720149_e2a86582fb_b.jpg" width="75%" /></a><br />
<br />
老街的古老的建築和狹小的街道<br />
<a href="http://www.flickr.com/photos/luciferworld/6991640278/" title="Flickr 上 luciferworld 的 DSC_2637"><img alt="DSC_2637" src="http://farm8.staticflickr.com/7089/6991640278_42f178ddb2_b.jpg" width="75%" /></a><br />
<br />
其實很久沒拍照了, 這次出門幾乎都是開光圈先決後把光圈定在 F4 後就一直無腦拍下去了, 沒花太多的腦袋去思考拍照這件事情, 該好好反省了 XDAnonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-91822796153050155372012-03-20T05:55:00.000-07:002012-03-20T05:55:17.786-07:00霧<a href="http://www.flickr.com/photos/luciferworld/6999803749/" title="Flickr 上 luciferworld 的 DSC_2075"><img alt="DSC_2075" src="http://farm7.staticflickr.com/6054/6999803749_0ba6080928_b.jpg" width="100%" /></a><br />
<br />
拍攝地 : 苗栗縣賽夏族民俗博物館<br />
<br />
這天霧起得很大, 基本上很難拍出什麼好照片, 索性套用調控檔, 玩玩不同的風格Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-12759118424332788022012-03-03T06:56:00.000-08:002012-03-03T06:56:00.489-08:00桃園復興鄉角板山公園<a href="http://www.flickr.com/photos/luciferworld/6949090101/" title="Flickr 上 luciferworld 的 DSC_2324"><img alt="DSC_2324" src="http://farm8.staticflickr.com/7064/6949090101_e4eb63e94c_b.jpg" width="100%" /></a>
<a href="http://www.flickr.com/photos/luciferworld/6802976684/" title="Flickr 上 luciferworld 的 DSC_2287"><img alt="DSC_2287" src="http://farm8.staticflickr.com/7209/6802976684_22f3c6c68f_b.jpg" width="100%" /></a>
<a href="http://www.flickr.com/photos/luciferworld/6949086091/" title="Flickr 上 luciferworld 的 DSC_2279"><img alt="DSC_2279" src="http://farm8.staticflickr.com/7197/6949086091_91f0768dd3_b.jpg" width="100%" /></a><br />
<br />
<a name='more'></a><br /><br />
<a href="http://www.flickr.com/photos/luciferworld/6949087983/" title="Flickr 上 luciferworld 的 DSC_2301"><img alt="DSC_2301" src="http://farm8.staticflickr.com/7206/6949087983_4083533933_b.jpg" width="75%" /></a>
<a href="http://www.flickr.com/photos/luciferworld/6802974532/" title="Flickr 上 luciferworld 的 DSC_2262"><img alt="DSC_2262" src="http://farm8.staticflickr.com/7058/6802974532_df39e3734a_b.jpg" width="100%" /></a>
<a href="http://www.flickr.com/photos/luciferworld/6802976084/" title="Flickr 上 luciferworld 的 DSC_2286"><img alt="DSC_2286" src="http://farm8.staticflickr.com/7187/6802976084_1e9e8cdfdb_b.jpg" width="100%" /></a>
<a href="http://www.flickr.com/photos/luciferworld/6949085375/" title="Flickr 上 luciferworld 的 DSC_2265"><img alt="DSC_2265" src="http://farm8.staticflickr.com/7177/6949085375_cc623cae46_b.jpg" width="100%" /></a>
<a href="http://www.flickr.com/photos/luciferworld/6949093173/" title="Flickr 上 luciferworld 的 DSC_2354"><img alt="DSC_2354" src="http://farm8.staticflickr.com/7184/6949093173_5bc682a0fd_b.jpg" width="100%" /></a>
<a href="http://www.flickr.com/photos/luciferworld/6949092657/" title="Flickr 上 luciferworld 的 DSC_2346"><img alt="DSC_2346" src="http://farm8.staticflickr.com/7064/6949092657_9310caf7b1_b.jpg" width="100%" /></a>
<a href="http://www.flickr.com/photos/luciferworld/6802980768/" title="Flickr 上 luciferworld 的 DSC_2345"><img alt="DSC_2345" src="http://farm8.staticflickr.com/7067/6802980768_a89c512dc8_b.jpg" width="75%" /></a>Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-67399092307512589852012-03-01T05:40:00.003-08:002012-03-01T05:41:30.089-08:00Philips HD9220 健康氣炸鍋開箱前一陣子, 看到朋友的部落格裡分享一篇 <a href="http://charliewlmblog.blogspot.com/2012/01/philips-hd9220.html">PHILIPS 健康氣炸鍋 HD9220</a> 的文章, 感覺蠻不錯用的, 不過又貴又搶手, 新竹似乎沒什麼地方買得到。後來終於在 Yahoo 超級商城中看到有商家有現貨,心一橫 7900 就灑出去囉~<br />
<br />
入學照來一張<br />
<a href="http://www.flickr.com/photos/luciferworld/6943657447/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2185"><img alt="DSC_2185" src="http://farm8.staticflickr.com/7047/6943657447_2ef17d043a_b.jpg" width="75%" /></a>
<br />
<br />
<a name='more'></a><br /><br />
底鍋和內網<br />
<a href="http://www.flickr.com/photos/luciferworld/6943657209/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2187"><img alt="DSC_2187" src="http://farm8.staticflickr.com/7064/6943657209_96ac94f6aa_b.jpg" width="100%" /></a>
<br />
<br />
當然要找些東西來試炸囉, 來些頂好的雞塊和 costco 的無骨香酥雞試鍋<br />
<a href="http://www.flickr.com/photos/luciferworld/6797541064/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2190"><img alt="DSC_2190" src="http://farm8.staticflickr.com/7193/6797541064_4189dccca8_b.jpg" width="100%" /></a>
<br />
<br />
雖然簡易食譜上寫200度6-10分鐘, 但實際試了一下大約16分鐘才比較 OK, 不然會太軟。<br />
<a href="http://www.flickr.com/photos/luciferworld/6943656569/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2191"><img alt="DSC_2191" src="http://farm8.staticflickr.com/7058/6943656569_cc99c55c3a_b.jpg" width="100%" /></a><br />
<br />
<br />
被油切下來的油,好像沒有想像中的多,不過至少比用油炸的少了非常多油了。<br />
<a href="http://www.flickr.com/photos/luciferworld/6943656209/" target="_blank" title="Flickr 上 luciferworld 的 DSC_2201"><img alt="DSC_2201" src="http://farm8.staticflickr.com/7050/6943656209_907c947863_b.jpg" width="100%" /></a><br />
<br />
<br />
還想試看看更多炸物的效果 ?<br />
可惜的是減肥中, 不想一次吃太多 XD<br />
<br />
優點隨便 google 一下應該就一堆了, 作法也是, 所以懶得寫了 .....<br />
缺點 ......<br />
1. 那個油網實在不太好清, 底鍋的死角清潔時也要多花點功夫。<br />
2. 隨機附的食譜上有很多語言, 但為什麼中文只有殘體字 ......<br />
3. 炸東西變簡單了, 以後要控制體重就更難了 ......Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-81795293068356309992012-02-24T04:22:00.005-08:002012-02-24T04:24:26.899-08:00大湖草莓每年春節過後,就是草莓的盛產期囉~<br />
去年228時去大湖採草莓,外型和顏色都還不錯,但回家一吃,感覺沒什麼味道,今年的希望好一點囉。<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6902038169/" title="Flickr 上 luciferworld 的 DSC_2168"><img alt="DSC_2168" src="http://farm8.staticflickr.com/7045/6902038169_b86188cf5c_b.jpg" width="100%" /></a><br />
<br />
<br />
每株草莓生長出來的結果也都不太一樣,俗話説一樣農藥養百養草莓呀 ......<br />
<a href="http://www.flickr.com/photos/luciferworld/6902041923/" title="Flickr 上 luciferworld 的 DSC_2097"><img alt="DSC_2097" src="http://farm8.staticflickr.com/7206/6902041923_7067a330bc_b.jpg" width="100%" /></a><br />
<br />
<br />
<a name='more'></a><br /><br />
<br />
整片的草莓園,偶爾出來走走爬爬還是不錯的<br />
<a href="http://www.flickr.com/photos/luciferworld/6902044811/" title="Flickr 上 luciferworld 的 DSC_2086"><img alt="DSC_2086" src="http://farm8.staticflickr.com/7038/6902044811_b7a0c54e77_b.jpg" width="100%" /></a><br />
<br />
<br />
<br />
有好的草莓一定要趕快把它消滅掉<br />
<a href="http://www.flickr.com/photos/luciferworld/6902041341/" title="Flickr 上 luciferworld 的 DSC_2082"><img alt="DSC_2082" src="http://farm8.staticflickr.com/7196/6902041341_c7f296b5da_b.jpg" width="75%" /></a>
<br />
<br />
草莓園邊居然也有種菜耶,完全在呼喚我的胃呀~<br />
<a href="http://www.flickr.com/photos/luciferworld/6902039789/" title="Flickr 上 luciferworld 的 DSC_2124"><img alt="DSC_2124" src="http://farm8.staticflickr.com/7050/6902039789_0b8c3fd195_b.jpg" width="100%" /></a><br />
<br />
<br />
採收的成果,不過只有最小籃的是我們的 XD<br />
<a href="http://www.flickr.com/photos/luciferworld/6902043169/" title="Flickr 上 luciferworld 的 DSC_2126"><img alt="DSC_2126" src="http://farm8.staticflickr.com/7068/6902043169_9eb71a6fd4_b.jpg" width="100%" /></a><br />
<br />
<br />
回家清洗過後,淋上煉乳就算全破囉~<br />
<a href="http://www.flickr.com/photos/luciferworld/6902037183/" title="Flickr 上 luciferworld 的 DSC_2166"><img alt="DSC_2166" src="http://farm8.staticflickr.com/7066/6902037183_f71cfa2bb1_b.jpg" width="100%" /></a>Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-47474515741735659202012-02-12T06:25:00.000-08:002012-02-12T06:25:58.445-08:00越南下龍灣之旅開宗明義, 嗯 .... 想當然爾, 不是我去的 XD
上週爸媽去了一趟越南旅遊, 其實很好奇怎麼會想去越南, 感覺那裡並不是什麼著名的旅遊盛地才是。
不過剛好不久前看了英國 BBC 的電視節目 Top Gear 第十二季第八集中, 就是去越南拍攝, 看他們三個主持人在越南搞笑外, 對那邊的景緻風情倒是覺得蠻有意思的呀 XD<br />
<br />
以下是爸媽帶回來給我們的伴手禮, 越南咖啡和腰果
<a href="http://www.flickr.com/photos/luciferworld/6862511689/" target="_blank" title="Flickr 上 luciferworld 的 DSC_1916"><img alt="DSC_1916" src="http://farm8.staticflickr.com/7180/6862511689_283ef34462_b.jpg" width="75%" /></a>
<br />
<br />
<br />
以下是老爸回來後自己編製的影片, 用 GF3 拍攝的, 看來 GF3 在錄影上的效果還蠻 OK 的呀<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/6lTPykLfTrw?feature=player_embedded' frameborder='0'></iframe></div>Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-40763600152939943652012-02-04T21:17:00.000-08:002012-02-04T21:18:49.812-08:00久違的藍天經過好一陣子的陰雨天, 這兩天終於出現久違的藍天了呢, 真想出門拍拍照呀 .......
<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6820809791/" target="_blank" title="Flickr 上 luciferworld 的 DSC_1909_01"><img alt="DSC_1909_01" src="http://farm8.staticflickr.com/7141/6820809791_6aa78c219b_b.jpg" width="100%" /></a><br />
<br />
<br />
<a name='more'></a><br /><br />
話說, 怎麼把天空拍得很藍, 其實網路上隨便一查都可以找到一堆秘技。簡單來說有幾種方式<br />
1. 天空本來就是藍色的 (不然想無中生有 ?)<br />
2. 拍攝時間最好為早上 8-10點, 或下午3-5點左右 (建議值, 但不是絕對值)<br />
3. 順光拍攝 (逆光拍容易一片死白呀)<br />
4. 使用偏光鏡 (殘念, 我沒多的預算買了)<br />
5. 拍攝時 -EV 值 (讓天空比較不會那麼亮, 可以拍得更藍, 但其它東西也會變暗, 甚至有點髒髒的)<br />
6. 使用較高 K 數的白平衡 (但其它被攝物也會變得偏藍)<br />
6. 拍攝時使用鮮艷模式, 風景模式, 套用調控檔或機身特殊功能(算放大絕吧)<br />
7. 後製軟體 (把拍照當作畫囉)<br />
<br />
上面這張照片, 其實是正中午時(12:07) 從我家窗戶往外拍, 當然先決要件也是天空真得是藍色的。沒偏光鏡,拍攝時是存 RAW 檔。<br />
<br />
運氣好的是,大概下了幾天的雨,空氣中比較不會有都會區天空慣有的那種髒髒的灰色。不過這張照片仍是被處理過的。原始相機直出圖如下 :<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6820786983/" target="_blank" title="Flickr 上 luciferworld 的 DSC_1909"><img alt="DSC_1909" src="http://farm8.staticflickr.com/7144/6820786983_a6f7eb7e0d_b.jpg" width="100%" /></a><br />
光圈 : F16<br />
ISO : L 1.0 (100)<br />
快門 : 1/250<br />
EV : -0.7<br />
色調 : 標準色調 (SD)<br />
白平衡 : 自動 (5500k)<br />
<br />
其實這樣的天空夠藍了, 只是不是藍得這麼有張力 (當然每個人的偏好有所不同), 因為拍攝的是 RAW 檔, 所以有足夠的彈性可以調整。調整成上面那張圖做了幾項調整<br />
白平衡 : 6000k<br />
EV : + 0.7<br />
色調 : 黑麵 bluesky 調控檔<br />
<br />
為什麼要將 EV 值調高, 主因是黑麵的 bluesky 調控會將這樣的天空調成相當重口味的靛藍色, 同時下方建築顏色也會變得相當深沉, 將 EV 值調高, 整體照片變得較亮, 再套用調控檔後的藍色反而比較鮮艷。<br />
<br />
那為什麼不直接拉曲線就好 ?<br />
<br />
因為我還不會呀 囧rz ......Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-33399681798841592182012-01-31T05:20:00.000-08:002012-01-31T05:20:14.844-08:00十年<a href="http://www.flickr.com/photos/luciferworld/6795357585/" title="Flickr 上 luciferworld 的 DSC_1890"><img alt="DSC_1890" src="http://farm8.staticflickr.com/7145/6795357585_5e0d9e5134_b.jpg" width="100%" /></a><br />
十年之前, 我不認識你, 你不屬於我 ..........<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6795356555/" title="Flickr 上 luciferworld 的 DSC_1894"><img alt="DSC_1894" src="http://farm8.staticflickr.com/7160/6795356555_2395074b13_b.jpg" width="100%" /></a><br />
十年之後, 我們是朋友, 還可以問侯, 只是那種溫柔 ......<br />
<br />
<br />
<a name='more'></a><br />
<br />
今年可是 2012 年了, 十年前的現在, 我不過是個在家啃老的假學究, 經過一連串的誤打誤撞進入資訊業,一轉眼十年就這麼過去了呢 .......<br />
<br />
人說上天自有安排, 生命總會自己找到出路, 而我願相信一抹淺笑 ......<br />
<br />
問我為何以路西華為名 ? 只因神話中的祂, 也在上帝的玩笑中找到了路........<br />
<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-18092370138969392112012-01-30T05:29:00.000-08:002012-01-30T05:29:32.381-08:00來自老闆的卡片過年前, 老闆很感性的用上個世紀曾流行過的手寫卡片寫給每個同事一張卡片, 不論字跡是否優美, 在這個電腦爆炸的時代, 肯用筆尖爬滿整張卡片總是真誠 ......<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6789100887/" target="_blank" title="Flickr 上 luciferworld 的 DSC_1886"><img alt="DSC_1886" src="http://farm8.staticflickr.com/7173/6789100887_8e9e904b55_b.jpg" width="100%" /></a><br />
<br />
老闆的誠意我收到了, 拍張照片紀念一下 .......<br />
<br />
對了, 老闆, 下次可以改用支票, 簽個名就好, 數字的部份我可以自己來, 這樣的誠意肯定能更直接的撼動人心, 畢竟我們是工程師, 您說是吧 ......Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-74943132357483497012012-01-11T07:15:00.000-08:002012-01-11T07:15:21.857-08:00曝光模式之亂談記得三年前第一次買單眼相機前, 常在網路上看到很多人說, 玩單眼如果不會測光, 那麼拍出來的照片會比全自動的 DC 還慘。所以在搞不懂 SMAP 等模式中不斷的猶豫舉足不前,後來朋友給了我一劑解藥 : 先不要管那些東西, 反正切到 P 模式或 auto, 其它的就跟 DC 沒什麼差別了。有了這句保證後,就開始掉進了一連串錢井深的無底洞中 ...... 。<br />
<br />
但從那之後,我就一直覺得 Auto 或 P 模式是剛入門菜鳥在用的曝光模式。所以在 D5000 的那個年代,讀完了 <a href="http://www.books.com.tw/exep/prod/booksfile.php?item=0010321482">DSLR 數位單眼相機聖經</a> 這本書後。對這些曝光原理有了初步的認識之後,忽然間感覺自己功力大增,再也不是那個拿了一台 DSLR 在外面招搖撞騙的紙老虎了。於是 A 模式幾乎就常駐在我的曝光模式之中。反正光圈調到最大就可以拍到很酷的散景( 這可是 DC 拍不出來的, 專業吧!! ),如果快門太長的話就調高 ISO。不但夠簡單,也可以避免被人瞄到自己拿著一台單眼結果從頭 Auto 拍到尾的那種尷尬場面。<br />
<br />
其實這樣應該就能混的不錯了,只要再瞭解一點測光模式基本上不會有太大的問題。一直到前陣子換了一台 D700, 鏡頭換了一顆 24-70 F2.8 後,一樣的套路出去打,但回家整理照片的時候就發現:失敗的照片變多了 !<br />
<br />
所謂失敗的照片,並不是指換了 D700 後曝光變得不正確,而是指發現手震的照片變多了不少。當然我可以回頭反省自己是不是咖啡喝太多所以手抖的很厲害,人體三腳架的基本功夫練得不夠紮實,或者過度依賴防手震功能等等。回過頭再檢視這些失敗的照片,其實在縮圖下手震看起來並不明顯,但用大螢幕或放大到 100% 來看後就會發現手震的痕跡。拍照的當時可能並不會注意到,但震了就是震了,再怎麼樣都很難救得回來。<br />
<br />
以下面這張照片為例,在這樣的縮圖下看起來似乎沒什麼太大的問題,但開啟 EXIF 仔細看,快門值已達 1/3 秒,焦段70mm, 開啟原始大小就不難發現其實已經手震了,相當可惜,現在也沒辦法再回峇里島補拍了。<br />
<a href="http://www.flickr.com/photos/luciferworld/6425348721/" target="_blank" title="Flickr 上 luciferworld 的 DSC_0425"><img alt="DSC_0425" src="http://farm8.staticflickr.com/7024/6425348721_7befe832ce_b.jpg" width="100%" /></a>
<br />
<br />
之所以會手震,其實有點概念的人大多知道是沒保持在安全快門值(1/焦段 秒)。如果練得穩一點應該還可以再讓快門慢一點來爭取多一點的曝光時間。那怎麼會換了 D700 後這個問題才浮現出來呢 ? 其實不是 D700 本身的問題,而是在使用 D5000 時最常用的鏡頭 16-85mm 有著效果很不錯的 VR 功能, 而 24-70mm F2.8 這顆鏡頭本身並沒有防手震。一般開光圈先決時等於我們讓相機自己決定快門值,假設我們使用焦段在 60mm, 固定光圈值為 F8 時, 相機測得快門值為 1/30 秒。這種情況在有防手震加持的鏡頭下是相當安全的 (二級防手震加持 60mm 的安全快門可以到 1/15 秒)。所以被慣壞後容易養成開 A 模式忽略安全快門值的習慣。而在沒有防手震鏡頭時在同樣條件的情況下則結果可能會變成輕微的手震現象。但在相機那小小3吋的 LCD 上, 除非很有空每張照片按下快門後都放大到 100% 來檢視是否有合焦或手震的現象,不然這張照片極有可能變成回家後的遺憾 ......。<br />
<br />
所以現在知道了,在每次按下快門前都要記得注意目前的焦段和安全快門值。未達安全快門值的時候我們也知道可以調高 ISO 來增加照片曝光度,一切聽起來很 OK,但並不是這樣就足夠的,還有天殺的測光模式問題。什麼時候要開矩陣測光,什麼時候要開中央重點測光,什麼時候要開點測光,接下來還有是否逆光拍攝和測光對象的顏色反射率的問題。簡單來說只要拍攝角度一換,拍攝主題變更,甚至變更對焦點都有可能讓測得的 EV 值改變,也就是當我持續使用 A 模式時,三不五時就得注意一下曝光效果和安全快門值。那既然除了光圈值之外幾乎什麼都要注意了。那麼何不乾脆考慮使用 M 模式來拍攝好了 ?<br />
<br />
仔細比較一下, A 模式和 M 模式到底誰比較方便 ? A 模式可以讓相機幫你決定快門值,但不保證在安全快門值內。但要多注意測光模式;M 模式必須要注意快門值 (那就順便維持在安全快門值內吧 ),曝光結果 (需要好一點的心算技巧來腦內換算一下所需的光圈/快門/ISO 所影響的曝光級數)。所以下一個不太有道理的結論 : 兩者要關心的項目其實半斤八兩。<br />
<br />
為何不太有道理 ? 其實只是因為人算不如電腦算,兩者計算曝光級數的速度實在差很多。所以大部份的人都會選擇讓電腦完成這部份的工作。而人腦用來決定測光模式( 3選1, 比起計算一堆數字再怎麼樣都輕鬆多了)。所以結論會變成在沒有 VR 的鏡頭下, 請多注意安全快門值 ? 打了這麼多字其實也只是在同一個問題中打轉而已 ?<br />
<br />
那換個角度來說, 沒有 VR 的鏡頭是不是使用快門先決模式就能有效解決安全快門的問題 ? 感覺上這似乎又不太對我的胃。原因很簡單,光圈的大小會影響成像的銳利度和景深。尤其是景深問題,基本上就能算是整個構圖的重要環節之一。為了正確的曝光和安全快門而影響構圖 ? 似乎又不太理智。在經驗上,除非是要強調速度感或需要較長時間曝光來呈現的時候 ( 如瀑布、夜景) 我才會使用 S 模式。<br />
<br />
以上論述,其實都是在拍攝環境條件排除在外的條件下的推論。但如果加入環境因素呢 ? 那就會發現不同的環境條件很可能會有完全不同的結論。例如在戶外的出遊照,可能沒走幾步就是完全不同的測光值(太陽下、樹蔭下、逆光、忽然一朵飄過的烏雲),使用 M 模式很可能讓老婆已經走到前方50公尺後我才調到目前環境應有的測光值。但如果是在室內的場合中,室內燈光通常很固定,那麼調整好一次後其實就可以一直拍下去,最多微調一下就 OK 了,不用再一直關注什麼測光模式和什麼顏色反射率會影響測光結果等問題了。<br />
<br />
如果室內掛上閃燈呢 ? 那麼似乎 M 模式更簡單一些。先調整光圈到自己想要的景深, 調整快門在 1/200 - 1/250 (很安全的快門吧),ISO 調到 200 左右。重點來了,買一顆支援 TTL 的閃燈,開 TTL 模式,然後快門一直按就 OK 了 XD !! 如果覺得太亮或太暗,再自行微調閃光燈的出力即可。真是比 Auto 還無腦的大絕招呀 XD,不過閃燈打的方向和角度是一定要注意就是了,像我的副廠閃光燈似乎就不知道我現在是打跳燈還是直閃,當然也不會知道我有沒有裝柔光罩。<br />
<br />
寫了這麼多,其實只是一些經驗和心得。重點只在兩個:<br />
1. M 模式不是什麼遙不可及的專家或職業模式,不用這麼敬而遠之。<br />
2. 在不同的時機搭配正確的模式,就可以有效的提昇拍攝的速度和效果 !<br />
<br />
報告完畢~!<br />
<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0tag:blogger.com,1999:blog-6501248200092871158.post-36382875160835319612012-01-03T05:16:00.000-08:002012-01-03T16:59:32.460-08:00DSLR 與 DC 光圈比較測試現在新出的類單眼開出來的規格有愈來愈驚人的趨勢, 拿我手邊的 Olympus XZ-1 來說, 標榜著光圈值有 F1.8(28mm 端)-F2.5(112mm 端), 簡單來說可以當成 28-112mm 恆定光圈 F2.5 來用。也就是說, 這樣的焦段和光圈比起 DSLR 的三元鏡 24-70mm F2.8 來說有過之而無不及,到底有沒有這麼嚇人呀?<br />
<br />
後來在 mobile01 上爬文, 發現有些人說在同樣光圈值下, DSLR 的鏡頭進光量會比 DC 的大很多, 拍出來的照片也會更為明亮。 聽起來似乎很有道理, 畢竟用看的也知道,DSLR 鏡頭那麼大一支,進光量一定多很多嘛 !!<br />
<br />
不過回過頭來想,既然這樣的話,DC 上的那些光圈值是什麼意思,標示那麼漂亮但是照片曝光度還不如 DSLR 的鏡頭 ? 其實這個問題我好奇很久了,畢竟我對這些機械構造光學原理的東西沒這麼有研究,乾脆來做個實驗比一下就知道了!<br />
<br />
測試環境:<br />
A 組 : Olympus xz-1<br />
B 組 : D700, sigma 24-70mm F2.8<br />
<br />
<br />
測試設定 : 兩組皆為 ISO 200, 快門 1/30, 焦段 28mm ( 但因 sigma 24-70mm 最近對焦距離較遠, 拍出來主體物較小, 故有裁圖 )<br />
<br />
現場光為自然光燈炮(色溫4000k), D700 設定白平衡為 4000k, XZ-1 使用自動白平衡, 故顏色的部份有所出入。<br />
<br />
A1 : xz-1 F2.8<br />
<a href="http://www.flickr.com/photos/luciferworld/6627548053/" target="_blank" title="Flickr 上 luciferworld 的 P1031161"><img alt="P1031161" src="http://farm8.staticflickr.com/7014/6627548053_c53c5814ac_b.jpg" width="100%" /></a><br />
<br />
B1 : D700 F2.8<a href="http://www.flickr.com/photos/luciferworld/6627552701/" target="_blank" title="Flickr 上 luciferworld 的 DSC_1878"><img alt="DSC_1878" src="http://farm8.staticflickr.com/7022/6627552701_d2a5a567c2_b.jpg" width="100%" /></a><br />
<br />
<a name='more'></a><br /><br />
A2 : XZ-1 F4<br />
<a href="http://www.flickr.com/photos/luciferworld/6627549637/" target="_blank" title="Flickr 上 luciferworld 的 P1031162"><img alt="P1031162" src="http://farm8.staticflickr.com/7035/6627549637_2c97fa97e3_b.jpg" width="100%" /></a><br />
<br />
B2 : D700 F4<br />
<a href="http://www.flickr.com/photos/luciferworld/6627552875/" target="_blank" title="Flickr 上 luciferworld 的 DSC_1879"><img alt="DSC_1879" src="http://farm8.staticflickr.com/7151/6627552875_924b264135_b.jpg" width="100%" /></a><br />
<br />
A3 : xz-1 F5.6<br />
<a href="http://www.flickr.com/photos/luciferworld/6627551113/" target="_blank" title="Flickr 上 luciferworld 的 P1031163"><img alt="P1031163" src="http://farm8.staticflickr.com/7161/6627551113_3c5169cc38_b.jpg" width="100%" /></a><br />
<br />
B3 : D700 F5.6<br />
<a href="http://www.flickr.com/photos/luciferworld/6627553077/" target="_blank" title="Flickr 上 luciferworld 的 DSC_1880"><img alt="DSC_1880" src="http://farm8.staticflickr.com/7174/6627553077_59f1c507e0_b.jpg" width="100%" /></a><br />
<br />
A4 : XZ-1 F8<br />
<a href="http://www.flickr.com/photos/luciferworld/6627552557/" target="_blank" title="Flickr 上 luciferworld 的 P1031164"><img alt="P1031164" src="http://farm8.staticflickr.com/7141/6627552557_3ae1e3cc15_b.jpg" width="100%" /></a><br />
<br />
B4 : D700 F8<br />
<a href="http://www.flickr.com/photos/luciferworld/6627553239/" target="_blank" title="Flickr 上 luciferworld 的 DSC_1881"><img alt="DSC_1881" src="http://farm8.staticflickr.com/7017/6627553239_d8f14c75bf_b.jpg" width="100%" /></a><br />
<br />
以目測的感覺上來說, 在同樣光圈值的情況下, 其實影響的照片曝光程度似乎是差不多的, 也就是 F1.8 真得是 F1.8, 貢丸湯裡真得有米田共。這下我們就可以大聲的對全世界宣告,大根的不見得中用呀 XD !!<br />
<br />
但仔細的再去看看, D700 那個雖然裁過圖了(裁成 1280x960), 不過在光源較暗的照片中細節的部份仍是比 XZ-1 好上許多, 這當然有許多原因啦, 兩者感光元件大小差這麼多, 鏡頭搞得這麼複雜就是要把影像處理得更好,所以說囉 .........<br />
<br />
還好我的很大根呀 A_AAnonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com2tag:blogger.com,1999:blog-6501248200092871158.post-26608213512431360872011-12-24T00:47:00.000-08:002011-12-24T00:47:51.743-08:00聖誕禮物之- AGAit 易管家 EC01 開箱今天是聖誕節, 不免俗的要送個禮物慰勞老婆平時的辛勞, 所以決定買一台掃地機器人來讓老婆輕鬆點, 又能維持家裡的乾淨, 真是一兼二顧, 摸蛤仔兼洗褲的好選擇呀 XD<br />
<br />
最後選定這台-易管家 EC01, 也沒什麼特殊理由, 第一是因為這台比較便宜, 我還不用當掉太多件褲子就能買了, 再來是因為老哥家也有一台, 聽他讚不絕口的推薦那就相信他一下囉。<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6563174275/" title="Flickr 上 luciferworld 的 DSC_1731"><img alt="DSC_1731" src="http://farm8.staticflickr.com/7012/6563174275_37e01c8072_b.jpg" width="100%" /></a><br />
<div style="text-align: center;">
外盒包裝,看那中英文名字實在是長到很ooxx .....</div>
<br />
<a href="http://www.flickr.com/photos/luciferworld/6563175123/" title="Flickr 上 luciferworld 的 DSC_1735"><img alt="DSC_1735" src="http://farm8.staticflickr.com/7024/6563175123_c9dc060c4b_b.jpg" width="100%" /></a><br />
<div style="text-align: center;">
開箱囉, 圓型的主機 、座充、搖控器和電池</div>
<br />
<a name='more'></a><br /><br />
<a href="http://www.flickr.com/photos/luciferworld/6563175495/" title="Flickr 上 luciferworld 的 DSC_1742"><img alt="DSC_1742" src="http://farm8.staticflickr.com/7166/6563175495_270fabba53_b.jpg" width="100%" /></a><br />
<div style="text-align: center;">
座充和變壓器, 機器人沒電或打掃完成後會自動回到這個位置</div>
<br />
<a href="http://www.flickr.com/photos/luciferworld/6563176281/" title="Flickr 上 luciferworld 的 DSC_1749"><img alt="DSC_1749" src="http://farm8.staticflickr.com/7009/6563176281_4ea55fe919_b.jpg" width="100%" /></a><br />
<div style="text-align: center;">
搖控器, 主要功能都在上面, 不過如果是要用這個來把機器人當搖控玩具汽車來打掃房子的話, 我覺得使用吸塵器還乾脆一點</div>
<br />
<a href="http://www.flickr.com/photos/luciferworld/6563177161/" title="Flickr 上 luciferworld 的 DSC_1752"><img alt="DSC_1752" src="http://farm8.staticflickr.com/7142/6563177161_accce92f99_b.jpg" width="100%" /></a><br />
<div style="text-align: center;">
開機打掃十分鐘後打開機蓋, 集塵盒裡已經有一大堆灰塵了, 上頭還附個小刷子讓你清理集塵盒時可以使用</div>
<br />
<a href="http://www.flickr.com/photos/luciferworld/6563176707/" title="Flickr 上 luciferworld 的 DSC_1751"><img alt="DSC_1751" src="http://farm8.staticflickr.com/7033/6563176707_785164d7f2_b.jpg" width="100%" /></a><br />
<div style="text-align: center;">
取出集塵盒, 看起來打掃的相當有效率呀。不過不是因為我地板太髒, 而是因為這台的高度(9cm) 可以鑽進我平常掃不到的沙發底下, 所以清出了不少灰塵和毛髮, 看來以後不用三不五時就來個大搬風來清理沙發下長年累積的灰塵了。</div>
<br />
<a href="http://www.flickr.com/photos/luciferworld/6563177669/" title="Flickr 上 luciferworld 的 DSC_1753"><img alt="DSC_1753" src="http://farm8.staticflickr.com/7175/6563177669_52637643ae_b.jpg" width="75%" /></a><br />
<div style="text-align: center;">
打掃完後機器人會自動歸位, 看他歸位的樣子很像個菜鳥在開車, 倒車撞個幾次後才能喬進去呀, 相當有趣!!</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
這台不管怎麼掃, 似乎都不會有自己動手打掃來得乾淨, 吸力方面也不如我的吸塵器強。不過, 如果你平常很懶得打掃又想維持地板的基本清潔, 一台掃地機器人應該是蠻好的選擇的!</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
最後, 祝大家聖誕週末愉快囉~</div>Anonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com1tag:blogger.com,1999:blog-6501248200092871158.post-38782827423663418112011-12-21T07:07:00.000-08:002011-12-24T05:27:18.515-08:00新玩具-德國桌遊(動物園)兩個禮拜前, 和老婆的朋友們一起聚餐時, 認識了一個老婆的大學同學。當時她帶了幾種很有趣的牌和大家一起同樂,她說這種牌叫德國桌遊 (我一直想德國豬腳很有名, 德國的豬油也不錯嗎 ?)。玩了一個晚上大家都很盡興, 這種紙牌也相當有趣,於是老婆也就上網去買了這附牌回來玩囉。<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6548920189/" title="Flickr 上 luciferworld 的 DSC_1690"><img alt="DSC_1690" height="1024" src="http://farm8.staticflickr.com/7143/6548920189_72e39fb371_b.jpg" width="768" /></a><br />
盒裝封面,有中文版和英文版,基本上只是差在說明書不同而已,英文版的大概是進口的關係反而更貴。<br />
<br />
<a name='more'></a><br />
<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6548928107/" title="Flickr 上 luciferworld 的 DSC_1708"><img alt="DSC_1708" height="1024" src="http://farm8.staticflickr.com/7147/6548928107_59d3b7b810_b.jpg" width="768" /></a><br />
有送專用的紙牌保護套,其實就是一張張合身的透明塑膠袋,裝上去後洗牌還蠻難洗的......<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6548924295/" title="Flickr 上 luciferworld 的 DSC_1696"><img alt="DSC_1696" height="1024" src="http://farm8.staticflickr.com/7029/6548924295_b1c948666a_b.jpg" width="768" /></a><br />
裡頭的所有動物牌,他們頭上的是他們的剋星,不要誤以為是牠們想吃什麼 ......<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6548929889/" title="Flickr 上 luciferworld 的 DSC_1710"><img alt="DSC_1710" height="1024" src="http://farm8.staticflickr.com/7014/6548929889_cb189884b1_b.jpg" width="768" /></a><br />
鬼牌, 可以變成任何牌型<br />
<br />
<a href="http://www.flickr.com/photos/luciferworld/6548926345/" title="Flickr 上 luciferworld 的 DSC_1705"><img alt="DSC_1705" src="http://farm8.staticflickr.com/7023/6548926345_dde31302d9_b.jpg" width="100%" /></a><br />
牌種的相生相剋對照表, 其實玩牌時看牌上應該就知道了<br />
<br />
其實遊戲規則很簡單, 有點類似大老二<br />
1. 一次最多打一張牌, 最多四張牌<br />
2. 下家的牌必須是上家的剋星, 張數相同; 或者是上家的相同牌, 但張數多一張(例如上家打2張獅子, 那麼我必須打2隻大象, 或者3隻獅子, 否則 PASS)<br />
3. 蚊子配上大象, 可以當成大象來使用<br />
4. 鬼牌可當成任何牌使用<br />
<br />
有興趣的可以搜尋德國桌遊應該就能找到囉, 不用特地跑去德國買 XDAnonymoushttp://www.blogger.com/profile/06452863952441435943noreply@blogger.com0