2011年9月28日 星期三

Android UI 元件整理 (二)


Spinner

下拉式選單的用法與之前所提到的 Button 或是 CheckBox 等方式略有不同,下拉式選單可以利用 Adapter 將選項與物件綁在一起,而 Adapter 本身的角色則提供了一系列的操作,可以方便於選項的增刪選擇,此處所使用的是 ArrayAdapter ,與之綁定的資料為字串陣列,最後再將此 Adapter 設定給下拉式選單,即完成了 Spinner、Adapter與資料三者的綁定。此範例的功能為選擇每一個顏色後,即在上方的 TextView上顯示。

原始碼

package nchu.test;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

public class test extends Activity {
private static final String[] m_colors ={"red","blue","white"};

private TextView m_textview;
private Spinner m_spinner;
private ArrayAdapter adapter;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

m_textview = (TextView)findViewById(R.id.textView1);
m_spinner = (Spinner)findViewById(R.id.spinner1);
adapter = new ArrayAdapter (this,android.R.layout.simple_spinner_dropdown_item,m_colors);
m_spinner.setAdapter(adapter);

m_spinner.setOnItemSelectedListener(new Spinner.OnItemSelectedListener(){

@Override
public void onItemSelected(AdapterView arg0, View arg1,
int arg2, long arg3) {
m_textview.setText("顏色:" + m_colors[arg2]);

}

@Override
public void onNothingSelected(AdapterView arg0) {
// TODO Auto-generated method stub

}

});

}
}




AutoCompleteTextView、MultiAutoCompleteTextView

自動完成的部分,Android預設提供兩種,一種是單一的自動完成,意思是一次只能完成一個字詞;另一種就是在一行內可以完成多次的字詞選擇。

自動完成也可以利用 ArrayAdapter 將預設的字詞與功能做綁定,在這裡的範例所使用的自動完成字詞有 apple、car、mouse 和 fish。比較需要注意的是 MultiAutoCompleteTextView 需要設定分隔符號才能正常運作。

原始碼

package nchu.test;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.MultiAutoCompleteTextView;

public class test extends Activity {

private static final String[] autostr = new String []{
"apple","car","mouse","fish"
};
private AutoCompleteTextView m_autoCT;
private MultiAutoCompleteTextView m_mautoCT;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_dropdown_item_1line,autostr);

m_autoCT = (AutoCompleteTextView)findViewById(R.id.autoCompleteTextView1);
m_mautoCT = (MultiAutoCompleteTextView)findViewById(R.id.multiAutoCompleteTextView1);

m_autoCT.setAdapter(adapter);
m_mautoCT.setAdapter(adapter);
m_mautoCT.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());

}
}





DatePicker, TimePicker
Menu
Dialog
ImageView
ImageButton
Gallery
ImageSwitcher


ProgressBar
SeekBar


GridView
ScrollView
ListView

2011年8月17日 星期三

2011-08-17 UCampus 總整理

非常感謝陳老師讓我進來執行行動服務的專案,原本自己的打算是在三個月內可以看到整個系統執行的雛形,年底可以上線做完整的測試,不過似乎學校方面的變動速度大過於預期,經過許多考量,我還是決定離開這個位置,讓現任組長找尋更適合人選以支援原本的校務系統,所以本篇應該是我在此留下的最後一篇文章,內容主要是說明現在整個系統的概貌,以及當初的設計概念,以方便未來如果有人需要這部分的程式時,可以較為清楚的方式去尋找,甚至同意我的理念的話還可以繼續下去。

目前在我的電腦裡面存在著最主要的三個專案,以及其他零碎測試用的小專案,主要的三個專案分別是 UCampus, UCampusMap, mediaFlickr。
  • UCampus : 為最主要的專案,也就是整個校園應用的主體,原本規畫是包含著各個功能的連結,並提供建構需求的工具,目前包含著四個 Package 分別為 UCampus, UCampusFun, UCampusMap, UCampusNews。
    • UCampus :程式主要的進入點及各部分功能的連結。
    • UCampusFun : 目前僅包含提供連結至 AP Server 及要求資料的功能類別。
    • UCampusMap : 這個 Package目前和 UCampusMap 專案架構基本上一樣,不過已經把連結資料庫及 AP Server 的部分,改寫到 UCampusFun 這個 Package。
    • UCampusNews : 包含新聞發佈的相關類別。 

  • UCampusMap : 地圖開發都是放在此處,可參考2011-07-27 UCampusMap 功能畫面
  • mediaFlickr : 對於社交活動中的 Flickr 放置在這個部分,基礎的開發方式可以參考搜尋Flickr上相關照片
這些源碼都已經同步至本機上的 SVN Server。
而程式中所使用的圖片,都存放在本機上我的圖片裡面,並依照功能分類,地圖上有用 GIMP 做設計的圖片及素材都放在 我的圖片 > map 。

Server 端是利用 php 作開發,主要的是 dbSetup.inc 及 dbFun.php 這兩隻檔案,檔案位於 inc 資料夾中,dbSetup.inc 主要存放所有資料庫的初始設定,而 dbFun.php 則是規畫提供所有可能的需求資訊,不過這部分的功能目前未開發完,現階段只有提供一些基礎的建物資料等等。

這個 dbFun 計畫上將會轉換成 Web API 的方式呈現,並提供足夠的校園資訊,如此一來可以方便提供給各單位處所自行研發所需要的程式,不過這個部份就得交給未來有興趣的人繼續執行了。

其餘的檔案功能可以參考Android 利用 PHP 連結 MySQL 即可知道每個檔案負責的位置為何。

2011年8月16日 星期二

Mobile Service 資料分類及資料庫存放位置

Mobile Service 是利用 Drupal 所開發出來的平台,Mobile Service 提供後台讓各單位人員可以註冊以及登入至系統發佈新聞等訊息,並且將這些訊息在前台顯示出來,除此之外也提供了校園單位簡介地理位置等資訊。

既然平台是利用 Drupal 所開發,所以要拉取資料庫的資料前,必須先來看一下各個資料存放於哪些資料表中。基本上與之前介紹的位置相似,可參關於Drupal新增content時的資料表變動Map 功能用到的 Drupal 資料 此兩篇介紹。


首先來看一下我對於 Mobile Service 所作的資料分類,目前的資料分類是依照新聞發佈、行事曆 以及部分地圖功能所做規劃。( 這裡為什麼說是部分的地圖功能,原因在於這部分只是存放發佈訊息及活動的位置,完整的地圖功能將包含了各類型單位、公車資訊等 )。




Type
Field
功能說明
building
(
建築物)


name
建物名稱
Floor
建物樓層
introduction
建物簡介
latitude
緯度
longitude
精度
units
(
單位或院所)


atBuilding
所在建物
name
單位名稱
atFloor
所在樓層
introduction
單位簡介
number
單位室碼
phone
單位電話
fax
單位傳真
web
單位網址
type
單位類型(學術、行政)
hasGroup
是否有組別
group
(
組別或系所)
atBuilding
所在建物
name
組別名稱
atFloor
所在樓層
atUnits
所在組別
introduction
組別簡介
number
組別室碼
phone
組別電話
fax
組別傳真
web
組別網址
member
(
成員)


atUnits
所屬單位或組別
name
成員姓名
title
職稱
phone
成員電話(分機)
news
(
新聞)


pUnits
發佈單位或組別
pMember
發佈成員
title
新聞標題
content
新聞內容
date
發佈日期
time
發佈時間
mediaUrl
多媒體檔連結位置
mSource
多媒體檔來源
mediaType
多媒體檔類型
events
(
事件 行事曆用)


pMember
發佈人員
pUnits
發佈單位或組別
title
事件標題
content
事件內容
pDate
發佈日期
pTime
發佈時間
aDate
活動日期
aTime
活動時間
aPlace
活動地點
alatitude
活動地點座標緯度
alongitude
活動地點座標精度
mediaUrl
多媒體檔連結位置
mSource
多媒體檔來源
mediaType
多媒體檔類型


此表為目前規畫在 Drupal 上的資料分類 (請注意這並非資料庫中實際的資料表或欄位 ),目的是為了新聞發佈及行事曆兩項功能,基本的功能都已經在表上有描述,此處將不再敘述,接下來將記錄實際在 Drupal 上的資料庫位置。

目前 Mobile Service 上共有 130 個資料表,同樣的這些資料表大多數是 Drupal 所必須使用;而此處因為有特別為了可以直接拉取資料庫而做分類與設計,所以在位置上較不會分散在各處。

常用到的資料表有 node, node_revisions, node_type, users, users_roles, role, permission, content_type_* 。

users, users_roles, role, permission 這四個資料表可以讓我們取得註冊者與系統權限及 uid 等關聯,並且在資料分類中設計了 member 的類型後,更可以利用 content_type_member.nid 來將各單位的人員與系統帳號做綁定的動作,如此一來,雖然發佈新聞或是其他訊息時系統看的是 uid 以及 系統權限 rid ,但是在前台或是其他自行由資料庫抓取資料的平台時,可以清楚顯示發佈者是哪個單位的成員,並且利用綁定的動作來做行動裝置上的權限控管,方便未來行動裝置上發佈功能的開發。

至於上面表中的各個 type 對應到資料庫中的資料表分別為 content_type_building, content_type_units, content_type_group, content_type_member, content_type_news, content_type_events。而這邊的設計概念十分簡單,由大至小,大的包含數個小的。
建築物 > 單位院所 > 組別系所 > 成員 > 新聞、行事曆
我們不管取得哪個部份的資訊,都可以經由 at* 或是 pMember 這兩個 field 取得更上面層級的資料,經由此步驟就可以一路的追尋回去,取得所在位置、樓層或是相關簡介等,如此也可以在地圖上標示此訊息的發佈位置;當活動資訊內無活動地點的經緯度時,也可以利用活動地點 aPlace 這個 field 來向上追朔取得地標。


和之前所使用的生活資訊平台相比較,經由這樣簡單的設計可以減少建構資料時必須輸入大量重複的資料,也讓使用者也就是訊息發佈者可以減少輸入的資料量,同時也兼顧了部分的關聯問題,以減少需要特定資料時多餘的運算步驟。


至於要如何利用 Drupal 所建立的資料庫,要求特定的資料這邊就不再贅述了,可以參考關於Drupal新增content時的資料表變動Map 功能用到的 Drupal 資料以及配合此表的介紹就可以編寫出需要的 SQL 指令。

Map 功能用到的 Drupal 資料

Map 功能是最先開發的部分,所以資料庫的部分是沿用一開始明宗與漢卿他們所建立的生活資訊平台,目前此平台的資料表共有121個,但實際上在拉需求的資料時,會使用到的資料表並不多,而且位置有固定的模式,這個部分請參考 關於Drupal新增content時的資料表變動

node_revisions :
  • nid : 應該是 node id ,值通常與 vid 相同,經過一段時間觀察,大部分所需擷取的資料都可以使用 nid 當成唯一鍵。(此部分為觀察結果後的判斷,並沒有細看 drupal 的原碼)
  • uid :此部分為 Drupal 的使用者 id ,如果需要使用者資料與 uid 的關聯,可以參考 users 資料表,至於身份的部分可以參考 users_roles 以及 role 此兩個資料表;users_roles 負責 uid 與 rid 的對應,role 資料表可查詢到 rid 所對應到的使用者權限身份;而實際上每個使用者權限身份可以執行的動作則位於 permission 資料表。
  • title : 每個 PO 文的標題。因為整個系統的開發過程與一般先規畫資料庫再寫 AP 這樣的過程稍微不同。雖然在規畫時同樣需要定義出需求哪些資料,但是這邊所規畫出來的資料,未來會放在資料庫的哪個位置或是如何命名則是由 drupal 所決定;這裡所規畫出來的資料就是直接呈現於畫面上的資料。或許這樣解釋不是很清楚,這邊舉個例子,假設在平台上顯示了【系所 : 資訊工程學系】,那這個資訊工程學系的字串資料就是存在於 title 這個欄位,而資訊工程學系的詳細介紹就會存放在 body 欄位。
node :
  • type : 此處標示著每個發佈的文章是屬於哪種類型,類型可以是建築物、單位、餐廳等,而這些類型必須在規劃資料時盡量的分類清楚,因為 drupal 上的這些類型並無法向規劃資料庫一樣直接給它們關聯,所以一不小心可能會照成大量的資料重覆存在於資料庫中,或是需要利用非常大量的 sql 語法才能取出資料;雖然 drupal 利用了 nid 將每個資料給予唯一的值,但是重覆的資料過多會導致開發者從不個地方取得資料,也會產生難以維護的問題。至於每個類型內包含著哪些資料則是存放在另外的資料表中。
content_* : 以 content 開頭的資料表則是存放著不同類型資料的內容。
  • content_type_unitsmap : 在這邊包含了學校內所有單位等相關資料。此處並不包含單位名稱,單位名稱須利用這裡的 nid 與 node.nid 做比對之後,再取 node.title 才是單位名稱。
    • field_um_seat_value : 單位位於哪棟大樓
    • field_um_phone_value : 電話
    • field_um_fax_value : 傳真
    • field_um_mail_value : e-mail
    • field_um_member_value : 包含成員及部分單位簡介也顯示於此
    • field_um_floor_value : 位於幾樓
    • field_um_room_value :單位房間號碼
    • field_um_link_url : 網頁網址
  • content_type_sightseeing : 包含景點簡介等資訊,基本上與 content_type_unitsmap 類似。
  • content_type_foodnews : 包含餐廳的資訊,基本上與 content_type_unitsmap 類似。
location : 地圖所需要的經緯度存放於此資料表中,所以當利用 content_type_unitsmap 取得單位資料時,也必須利用 nid 與此資料表的 nid 做比對,取出單位的經緯度。

Map 功能所用到的資料,大致上存放在上面介紹的資料表中,當然這個平台還提供了許多其他的資訊可以取用,不過目前只使用了這些資料。除此之外,這個平台當初規劃並非為了 UCampus做準備,所以有許多的分類顯得雜亂,如需要取得特定資訊必須額外再做判斷,這將使得運算資源白白浪費。所以之後規畫了另一個平台 Mobile Service,平台內資訊分類較為詳盡,除此之外也可以提供後台讓未來的各單位發佈新聞活動等訊息 (此部分由 漢卿 所建構,著實感謝);而對於 Mobile Service 的資料分類將於另外一篇做記錄,以避免資訊混雜。

2011年8月9日 星期二

debug - 自訂 ArrayAdapter 的 View

今天在編寫 news 部分的功能,因為需要在標題列後方加入一些圖片用來顯示是否有多媒體檔,這部分打算使用自己設計的 view,而負責存取資料的部分則是繼承原本的 ArrayAdapter ,製作一個新的 newsAdapter 類別,並且覆寫 getview() 這個方法。

在創建 inflater 物件後的初始一直有問題。getLayoutInflater()這個方法一直是未定義的,找到的解決方法為 : ((Activity)mContext).getLayoutInflater();


2011年8月2日 星期二

不同 Package 間的 activity 切換

今天將 Map 功能移入 UCampus 主程式時一直無法正常執行,UCampus 是應用的入口和 Map 為不同的 Package,功能是當我進入應用時,UCampus 會顯示一些按鈕,接著按下 Map 功能的按鈕後將會起始 Map 功能。

不同 Package 間 activity 切換,與相同 Package 中的做法是大同小異的,但是要注意的是 ,在 AndroidManifest 裡 android name 必須填寫完整 package 名稱,同樣的 intent.setClass 也必須將呼叫的 class 完整名稱寫出。

UCampus.java
intent.setClass(UCampus.this,nchu.UCampusMap.UCampusMap.class);
AndroidManifest.xml
<activity android:name="nchu.UCampusMap.UCampusMap"></activity>

2011年7月28日 星期四

在 MapView 中新增一個 View

當點擊地標後,如果需要顯示一個按鈕,那就需要另外加入一個包含著按鈕的 View ,而在本篇則會記錄一下如何利用 Layout 產生一個客製化的 View ,並加入至 MapView 中顯示。

Layout 部分 pop.xml 原始碼 :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:layout_height="wrap_content" android:text="TextView" android:layout_width="wrap_content" android:id="@+id/textView1"></TextView>
<Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

</LinearLayout>
首先必須先建立一個 Layout ,之後將在主程式內製成一個客製化的 View ,以用來點擊地標後顯示;這個 Layout 可以直接利用 XML 編寫,或是利用 Eclipse 圖形介面做設計。




exMap 原始碼 :
public class exMap extends MapActivity {
/** Called when the activity is first created. */
GeoPoint mGeoPoint01,mGeoPoint02;
ArrayList<OverlayItem> items = new ArrayList<OverlayItem>();
Context mContext;
View popView;
MapController mMapController;
MapView mMapView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

CreatPopView();

mMapView = (MapView)findViewById(R.id.mapview);
mMapView.addView(popView,new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT,MapView.LayoutParams.WRAP_CONTENT, null, MapView.LayoutParams.BOTTOM_CENTER));
mMapView.setBuiltInZoomControls(true);
mMapView.setTraffic(false);
mMapController = mMapView.getController();
mMapController.setZoom(17);
mGeoPoint01 = new GeoPoint(24121194,120675526);
mGeoPoint02 = new GeoPoint(24123689,120672358);
mMapController.animateTo(mGeoPoint01);


List<Overlay> list = mMapView.getOverlays();
MyOverlay mOverlay = new MyOverlay(getResources().getDrawable(R.drawable.icon),this);

OverlayItem mOverlayItem01 = new OverlayItem(mGeoPoint01,"這裡是地標01","點擊地標");
OverlayItem mOverlayItem02 = new OverlayItem(mGeoPoint02,"這裡是地標02","點擊地標");
mOverlay.addItem(mOverlayItem01);
mOverlay.addItem(mOverlayItem02);
list.add(mOverlay);
}

@Override
protected boolean isRouteDisplayed() {
// TODO Auto-generated method stub
return false;
}

public void CreatPopView(){
popView = LayoutInflater.from(this).inflate(R.layout.pop, null);
Button btn = (Button) popView.findViewById(R.id.button1);
btn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
popView.setVisibility(View.GONE);
}
});
popView.setVisibility(View.GONE);
}

class MyOverlay extends ItemizedOverlay<OverlayItem>{
public MyOverlay(Drawable defaultMarker,Context context) {
super(boundCenterBottom(defaultMarker));
mContext = context;
}

@Override
protected OverlayItem createItem(int i) {
// TODO Auto-generated method stub
return items.get(i);
}

@Override
public int size() {
// TODO Auto-generated method stub
return items.size();
}

@Override
protected boolean onTap(int index) {
popView.setVisibility(View.VISIBLE);
MapView.LayoutParams geoLP = (MapView.LayoutParams)popView.getLayoutParams();
Button btn = (Button)popView.findViewById(R.id.button1);
TextView text = (TextView)popView.findViewById(R.id.textView1);

geoLP.point = items.get(index).getPoint();
geoLP.x=15;
geoLP.y=-40;
btn.setText("PUSH");
text.setText(items.get(index).getTitle());
text.setTextColor(Color.MAGENTA);
mMapController.animateTo(items.get(index).getPoint());
mMapView.updateViewLayout(popView, geoLP);
return true;
}

public void addItem(OverlayItem item){
items.add(item);
populate();
}
}
}
在主程式這邊,首先要把剛剛的 Layout 新建為一個自訂的 View ;做法則是利用 LayoutInflater 類別,用法如下程式碼,from(this) 是指定目前使用的這個 context,inflate() 方法將 reslurce 中的 pop.xml 這個 layout 格式製成一個 View , 指定的 root View 為 null ,代表使用這個製成的 View 做為 root。
popView = LayoutInflater.from(this).inflate(R.layout.pop, null);

接著就可以取得 popView 裡的物件來使用,並監聽與設定按鈕按下時該做的動作,在這邊設定為將按鈕按下後即不顯示 popView。在程式初始後沒多久,就可以靠著呼叫 CreatPopView() 這個自訂方法來創建 popView 物件,不過 popView 物件雖然已經建立,但是在創建的初始值中,是將這個 View 設為不顯示,所以在程式執行後是看不到這個建立的 View,直到點擊地標後才會顯示。

為了在點擊地標後能夠控制顯示在 MapView 中的位置,所以在創建 MapView 後還必須將 popView 物件加入,並加入 MapView 相關 Layout 參數,之後才能夠在地圖上指定顯示位置。


接著就需要來看點擊地標後的執行動作,較為重要的是取得 MapView 的 LayoutParams ,利用取得的 LayoutParams 就可以設定顯示在地圖上的哪個位置,設定的方式可以利用經緯度也可以利用 GeoPoint ,這邊的例子使用的是 GeoPoint。

點擊地標後可以利用 getPoint() 取得點擊的地標位置,並回傳給 geoLP 這個 LayoutParams 物件的 point ,之後可以用 geoLP.x 和 geoLP.y 設定其 x、y座標的位移,達成控制顯示在螢幕上的位置。

在這順便記錄一下 Android 的繪圖座標系統,螢幕左上角為原點 (0,0) ,往右 X 值增加,往下 Y 值增加,以剛剛所提到的 geoLP 為例,當取得地標的 point 位置時,如果要將 PopView 放置在地標上方,則 Y 值必須減少,又因為 geoLP.x 和 geoLP.y 為 popView 與 MapView 的相對位置,所以只需要填寫差距值即可。


執行畫面 :

X 與 Y 皆無設定的狀態


X 與 Y 皆設為 0 ,與上圖比較可看出預設值即為0


當 X 為正值,Y為負值時,View則往右上角移動

如果要改變點擊地標產生的畫面,可以輕易的改變 Layout 來完成

2011年7月27日 星期三

2011-07-27 UCampusMap 功能畫面

本篇僅是目前地圖功能的畫面與記錄,因為未來的資料庫會有變動,所以暫時將目前的狀況做一個簡單的記錄,以供之後做參考。目前程式撰寫的部分著重在未來擴充性及維護的設計面上,而非功能性或畫面美觀,所以在這個版本與前一個版本的功能上差異並不大。

Map 基礎物件簡介

目前 Map 的架構下,在新增不同類型的地標時比之前更為容易,除了原本負責向 DB 要資料的 queryDB 這個類別外,還有 MarkerOverlay 類別負責建立地標圖層和 MarkerPoint 類別負責存取地標資訊。


queryDB 類別
接收到 query 的位址或是包含需求欄位與判斷值的要求,向遠端的 AP 做查詢的要求,並取回 JSON 格式資料,此 JSON 資料將存在於 queryDB 物件中,經由操作 queryDB 物件可以取出查詢到的資料甚至於將 queryDB 中的 JSON 物件取出。

MarkerOverlay 類別
在建構 MarkerOverlay 物件時,可以依照不同類別的資料創建,代表著不同類別的資料分別放在不同的圖層中,方便未來的維護;而在點擊地標時可將地標移至畫面中央,並跳出一個可點擊的按鈕,經由點擊此按鈕得到更多相關的資訊;點擊按鈕後的相關資訊由另外的類別作維護,將建構地標的資訊以及地標本身所包含的資訊做分割,好處在於可以需要的時候再向 DB 作要求,一方面可以減少 query 時傳遞的資料量,另一方面也可以降低行動裝置內存在著許多不需要的資訊。


MarkerPoint 類別
繼承自 GeoPoint 類別,並包含了建構地標時所需要的資料;像是地標的位置或是需要顯示的標題等等,都可以經由操作此類別物件而取得。目前的版本因為資料庫還不確定,所以取得資料的操作還需要將所需欄位名稱傳入當參數,未來等資料庫確定後,將改成直接取得資料的方式而不需要再傳欄位名稱,而不同類型所產生的物件將可取得不同的資料。可做為類似功能快速開發的基礎類別。


PointInformation 類別
此類別負責地標的完整資訊,而非建構資訊,物件的建立是在點擊地標後再點擊所產生的按鈕時才產生,而不是程式執行時就建立,物件建立後會再建立一個 queryDB 物件,並經由此物件取得地標的完整訊息,並將訊息呈現於螢幕上;目前此類別是將資訊與呈現方式包在一起,未來將此兩項分開,以達到更大的彈性。

Map 功能畫面
目前畫面上會顯示所有類型的地標,藍色水滴狀的地標為餐廳類型,小的招牌形狀地標為行政單位類型;畫面右上角有一個較大類似招牌的圖示按鈕,目前功能撰寫中,將規劃為類別按鈕,點擊此按鈕後可以叫出地標類型選單以供選擇,且未來在畫面上並不會顯示所有類型的地標,而是依照使用者選擇的類型做變動。

這個圖示按鈕並不是由地圖圖層所產生,如果由圖層所繪製,則地圖移動時按鈕也會跟著移動;這邊的做法是利用 Layout 去做規劃,所以這是個圖示按鈕,也因為如此規劃才能在點擊後同時在地圖畫面上顯示一個類型列表。

另外還有一個外面包覆一圈白色環狀的小藍點,這個是由 MyLocationOverlay 這個類別所產生,藍點的位置為目前定位的位置,外面的藍色範圍為誤差範圍,誤差範圍依照不同的定位方式而有所不同,如果有 WIFI 訊號可以利用則較 3G 訊號準確,GPS 只能在室外使用。


WIFI 及 GPS 關閉,利用 3G 訊號定位,誤差範圍較大。



同時開啟 WIFI 及 3G 時 ,定位自動採取 WIFI 訊號,誤差範圍縮小,定位位置也靠近資科大樓。


點選行政單位地標,地標移至畫面中央,顯示地標標題按鈕,並可點擊按鈕


點擊按鈕之後,跳至下一個頁面,頁面為單位相關資料


返回上一頁,並點擊餐廳地標,同樣的顯示地標標題按鈕,並可點擊


依據不同類型的地標,顯示不同的地標資料,目前餐廳資料只存有電話號碼



將地圖縮小可以很清楚的看到因為比例尺的不一樣,所有地標幾乎合在一起,但右上角的類別按鈕卻同樣的保持在螢幕的右上角。

Google Map ItemizedOverlay 使用

這個部分會來簡介一下如何使用 ItemizedOverlay 來達到放至地標在地圖上,並且點選地標可以顯示相關訊息的功能。

ItemizedOverlay

ItemizedOverlay 是繼承自 Overlay 的抽象物件,所以也必須要先實做,當實做 ItemizedOverlay 時,除了建構子外有兩個必須要覆寫的方法,一個是 creamItem() 另一個是 size() ,如果需要對地標做點擊的動作,那就需要再覆寫 onTap() 這個方法,首先來看一下完整的原始碼 (此處把 import 的部分刪除) :
public class exMap extends MapActivity {
/** Called when the activity is first created. */
GeoPoint mGeoPoint01,mGeoPoint02;
ArrayList<OverlayItem> items = new ArrayList<OverlayItem>();
Context mContext;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

MapView mMapView = (MapView)findViewById(R.id.mapview);
mMapView.setBuiltInZoomControls(true);
mMapView.setTraffic(false);
MapController mMapController = mMapView.getController();
mMapController.setZoom(17);
mGeoPoint01 = new GeoPoint(24121194,120675526);
mGeoPoint02 = new GeoPoint(24121190,120675520);
mMapController.animateTo(mGeoPoint01);


List<Overlay> list = mMapView.getOverlays();
MyOverlay mOverlay = new MyOverlay(getResources().getDrawable(R.drawable.icon),this);

OverlayItem mOverlayItem01 = new OverlayItem(mGeoPoint01,"這裡是地標01","點擊地標");
OverlayItem mOverlayItem02 = new OverlayItem(mGeoPoint02,"這裡是地標02","點擊地標");
mOverlay.addItem(mOverlayItem01);
mOverlay.addItem(mOverlayItem02);
list.add(mOverlay);
}

@Override
protected boolean isRouteDisplayed() {
// TODO Auto-generated method stub
return false;
}

class MyOverlay extends ItemizedOverlay<OverlayItem>{
public MyOverlay(Drawable defaultMarker,Context context) {
super(boundCenterBottom(defaultMarker));
mContext = context;
}

@Override
protected OverlayItem createItem(int i) {
// TODO Auto-generated method stub
return items.get(i);
}

@Override
public int size() {
// TODO Auto-generated method stub
return items.size();
}

@Override
protected boolean onTap(int index) {
OverlayItem item = items.get(index);
AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
dialog.setTitle(item.getTitle());
dialog.setMessage(item.getSnippet());
dialog.show();
return true;
}

public void addItem(OverlayItem item){
items.add(item);
populate();
}
}
}
先來看一下 ItemizedOverlay 在此處的執行流程 :
  1. 實做 ItemizedOverlay 類別,覆寫 createItem() 與 size() 兩個方法。
  2. 新增 addItem() 用來將地標加入
  3. 覆寫 onTap() 方法,可執行暗地標所需動作
  4. 在 MapActivity 中新增地圖
  5. 接著取得 MapView 中存放圖層的 List
  6. 建立 ItemizedOverlay 物件,並傳入地標圖示
  7. 建立地標點
  8. 將地標點加入至 ItemizedOverlay 物件中
  9. 將 ItemizedOverlay 物件加入至存放圖層的 List

建立 ItemizedOverlay 類別

這裡實做 ItemizedOverlay 需要傳入兩個參數,一個是地標的圖示另一個是要使用的 Context,這個 Context 是之後要用來顯示點選地標時所顯示的對話框,createItem() 在這裡雖然沒有直接被呼叫,但是在 addItem 中的 populate() 會去呼叫 createItem() ,所以當我們使用 addItem() 將地標加入圖層時,也同時可以利用 createItem() 做初始化的動作。當必須要使用到點選地標的功能時,則需要覆寫 onTap() 方法,這個方法可以讀入點選的地標號碼並取得 OverlayItem 物件,取得後即可作物件的相關操作。


建立與使用 ItemizedOverlay 物件
除了要建立 ItemizedOverlay 物件外,對於地標物件的建立也是必須的,基本的地標物件包含了 GeoPoint 物件、標題以及簡短內文;地標必須加入至圖層,圖層在繪製的時候才會有此地標,並且這個圖層也必須加入至 MapView 中存放圖層的 List 中,才會真正的被繪出。



實際執行畫面


兩個地標




點選中間第一個座標




點選左上角第二個座標

2011年7月26日 星期二

Google Map Overlay 使用

當使用 Map 時需要在地圖上使用一些座標圖示或是繪圖等功能,這時候就需要在原本的地圖上加上圖層,利用圖層就可以達成並且可以針對不同的圖層給予不同的功能,方便於管理與設計。

Overlay

Overlay是一個抽象類別,所以必須在使用前,先建立一個繼承自此抽象類別的自訂圖層類別,並將需求的功能實做,這裡用一個簡單的例子來說明如何使用圖層,範例功能將利用圖示顯示 animateTo(GeoPoint) 所設定的座標。

原始碼 :

package com.nchu.exMap;

import java.util.ArrayList;
import java.util.List;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.Bundle;

public class exMap extends MapActivity {
/** Called when the activity is first created. */
GeoPoint mGeoPoint;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

MapView mMapView = (MapView)findViewById(R.id.mapview);
mMapView.setBuiltInZoomControls(true);
mMapView.setTraffic(false);
MapController mMapController = mMapView.getController();
mMapController.setZoom(17);
mGeoPoint = new GeoPoint(24121194,120675526);
mMapController.animateTo(mGeoPoint);

MyOverlay mOverlay = new MyOverlay();
List<Overlay> list = mMapView.getOverlays();
list.add(mOverlay);
}

@Override
protected boolean isRouteDisplayed() {
// TODO Auto-generated method stub
return false;
}

class MyOverlay extends Overlay{
public void draw(Canvas canvas,MapView mapView,boolean shadow){
Paint mPaint = new Paint();
Point mScreenCoords = new Point();
mapView.getProjection().toPixels(mGeoPoint, mScreenCoords);
mPaint.setStrokeWidth(1);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
canvas.drawBitmap(bmp, mScreenCoords.x, mScreenCoords.y,mPaint);
canvas.drawText("地圖中心點", mScreenCoords.x, mScreenCoords.y, mPaint);
}
}
}
首先來看一下 MyOverlay 這個類別,繼承自 Overlay 這個抽象類別,並實作 draw() 這個方法, Paint 為畫筆物件,利用此物件可以設定筆觸的大小或式風格等;而 Point 物件則是螢幕的點,接著利用 getProjection() 所取得的 projection 物件做經緯度與螢幕位置的轉換;我們所要貼的圖可以事先放入 Resource 中,再經由 BitmapFactory 的 decodeResource 取得,最後利用drawBitmap 或是 drawText 將圖或文字繪出。

接著來看如何使用 MyOverlay 類別,建立 MyOverlay 物件的方法跟一般物件建立方法沒什麼兩樣,但是建立出來的圖層必須加入到目前所使用的 MapView 中,不然的話就只是一個毫無用途的物件,首先我們必須取得 MapView 中存放 Overlay 的容器,放在這個容器內的 Overlay 才會被繪出,方法為
List<Overlay> list = mMapView.getOverlays();
要注意的是此處 getOverlays() 所回傳的是 List<Overlay< 所以無法使用 ArrayList 做為代替,最後只要將建立的 Overlay 物件嫁入至此容器中即可。

以下為執行畫面,可以看到中心點已經繪出紅色的 "地圖中心點" 五字,並且在下方貼了一張圖。

Android 上使用 Google Map API

當新建一個 Android 的專案時,可以選擇要使用的 API 版本,要注意的是一般的 Android API 版本並不包含 Google Map API,所以當需要使用到 Google Map API 時則需要選擇 Target name 為 Google APIs 的選項,並配合所需要的版本,使用前還必須申請一個 Android Map API Key。



申請 Android Map API Key

申請 Key 必須要有系統的證明書以及一個 Google 帳號,測試用的可以利用 Debug 的證書,不過未來如果需要發佈的話,則需要一份利用所要發佈的程式產生的 MD5 證書,並利用此證明書去申請 Key 之後才能發佈。
詳細申請步驟可以參考 Google 說明頁面 : Obtaining a Maps API Key

Debug 證書的路徑在 Eclipse 中選擇 Windows > Preference > Android > Build,其中的 Default debug keystore 的值就是 debug.keystore 的路徑。得知此路徑後就可以利用 keytool 指令取得MD5值,指令為 keytool -list -keystore debug.keysotre

接著利用剛剛取得的 MD5 碼,可以至 Google Map API key 申請頁面上申請 key,只要將取得的 MD5 碼填入並按下產生即可取得。



Google Map API 簡介

Google Map API 中有幾個重要也常用到的功能,分別為:
  • MapActivity : 一般來說 Android 程式的頁面都會繼承 Activity 類別,但是要使用 Google Map 的功能時,則必須繼承這個 MapActivity 類別,並且在 onCreate() 中實做一個 MapView 實例。
  • MapView : 用於顯示地圖的 View 元件。
  • MapController : 用來控制地圖的縮放移動等動作。
  • Overlay : 用來顯示地圖上可繪製的物件。
  • GeoPoint : 包含了經緯度的物件。


建立基本地圖

因為使用 Google Map API 必須使用到網路,所以必須在 AndroidManifest.xml 中增加網路存取的權限,並且須定義使用 com.google.android.maps 這個 package,如下原始碼。
<uses-library android:name="com.google.android.maps" />
因為顯示為 MapView ,所以可以在 main.xml 將這個 MapView 加入,並將取得的 key 填入,基本的 main.xml 如下 :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mainlayout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<com.google.android.maps.MapView
android:id="@+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:apiKey="07MiAWWKO9Na0Aa8sf9hsd6uhZNXjrRwZU2zyGQ"
/>
</RelativeLayout>
或是直接利用程式碼建立 MapView。
MapView mMapView = new MapView(this, "API Key");
在建立 MapView之前,必須先建立起 MapActivity,再經由覆寫 MapActivity的 onCreat()方法建立MapView,之後就可以做一些地圖的設定,像是透過 setBuiltInZoomControls 設定地圖是否可以縮放,還有當地圖的顯示方式可以有三種,交通模式、衛星模式還有街景模式,分別為 setTraffic、setSatellite 以及 setStreeView 三個方法;最後再建立 MapController,而建立 MapController 物件可以利用下面方式產生 :
mMapController = mMapView.getController();
MapController 除了可以控制地圖縮放倍數外,也可以利用 animateTo(GeoPoint)的方法將地圖定位至 GeoPoint 的位置。

以下為完整範例原始碼 :

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nchu.exMap"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10" />
<uses-permission android:name="android.permission.INTERNET" />

<application android:icon="@drawable/icon" android:label="@string/app_name">
<uses-library android:name="com.google.android.maps" />
<activity android:name=".exMap"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
</manifest>


main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.google.android.maps.MapView
android:id="@+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:apiKey="07MiAWWKO9Na0Aa8sf9hsd6uhZNXjrRwZU2zyGQ"
/>
</LinearLayout>


exMap.java

package com.nchu.exMap;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;

import android.os.Bundle;

public class exMap extends MapActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

MapView mMapView = (MapView)findViewById(R.id.mapview);
mMapView.setBuiltInZoomControls(true);
mMapView.setTraffic(false);
MapController mMapController = mMapView.getController();
mMapController.setZoom(17);
GeoPoint mGeoPoint = new GeoPoint(24121194,120675526);
mMapController.animateTo(mGeoPoint);
}

@Override
protected boolean isRouteDisplayed() {
// TODO Auto-generated method stub
return false;
}
}



實際執行畫面


一般地圖



移動




縮放

Twitter Delicious Facebook Digg Stumbleupon Favorites More

 
Powered by Blogger