在移動應用飛速發展的今天,APP隻針對IOS平台進行開發已經不夠了,如今Android在移動裝置占有近80%的市場,如此大量的潛在使用者怎麼能被忽略掉呢。
在這篇文章中,本人會介紹在IOS開發中,怎麼學習一些Android開發的理念,Android和IOS功能上本身有一定的相似之處,但是具體實作的方式各異,是以這篇文章會使用一個項目例子進行對比,說明怎麼在這兩個平台上分别去實作這個任務。
本文不會深入研究關于IOS和Android兩個平台之間的使用者體驗或者設計模式之間的差異,不過如果能夠了解Android上的一些優秀的UI範例也很有幫助:ActionBar、Overflow menu、back button share action等等。假如你很想嘗試Android開發,那麼強烈推薦你去Google Play Store上購置一台Nexus5,然後把它作為你日常使用的裝置使用一周,然後嘗試仔細了解這個作業系統的各種功能和擴充特性,如果開發者連作業系統的各種使用規則都不了解,那麼做出來的産品一定有問題。
Objective-C和Java之間有很多不同之處,如果把Objective-C的程式設計風格帶到Java裡面的話,很多代碼也許會和底層的應用架構沖突。簡單地說,就是需要注意一些差別:
去掉Objective-C裡面的類字首,因為Java裡有顯式的命名空間和包結構,是以就沒必要用類字首了。
執行個體變量的字首用“m”,不用“_”,在寫代碼的過程中要多利用JavaDoc文檔。這樣能使代碼更清晰,更适合團隊合作。
注意檢查<code>NULL</code>值,Objective-C對空值檢查做的很好,不過Java沒有。
不直接使用屬性,如果需要<code>setter</code>和<code>getter</code>,需要建立一個<code>getVariableName()</code>方法,然後顯式調用它。如果直接使用“this.object”不會調用自定義的<code>getter</code>方法,你必須使用<code>this.getObject</code>這樣的方法。
同樣的,方法命名時帶有<code>get</code>和<code>set</code>字首來标示它是<code>getter</code>和<code>setter</code>方法,Java的方法很喜歡寫成<code>actions</code>或者<code>queries</code>等,比如Java會使用<code>getCell()</code>,而不用<code>cellForRowAtIndexPath</code>。
Android應用程式主要分為兩部分。第一部分是Java源代碼,以Java包結構排布,也可以根據自己的喜好進行結構排布。最基本的結構就是分為這幾個頂層目錄:activities、fragments、views、adapters和data(models和managers)。
第二部分是<code>res</code>檔案夾,就是“resource”的簡稱,<code>res</code>目錄存放的是圖檔、xml布局檔案,還有其它xml值檔案,是非代碼資源的一部分。在IOS上,圖檔隻需要比對兩個尺寸,而在Android上有很多種螢幕尺寸需要考慮,Android上用檔案夾來管理管理圖檔、字元串,還有其它的螢幕配置數值等。<code>res</code>檔案夾裡也含有類似IOS中<code>xib</code>檔案的<code>xml</code>檔案,還有存儲字元串資源、整數值,以及樣式的xml檔案。
Activities是Android APP最基本的可視單元,就像UIViewControllers是IOS最基本的顯示元件一樣。Android系統使用一個<code>Activity</code>棧來管理<code>Activity</code>,而IOS使用<code>UINavigationController</code>進行管理。當APP啟動的時候,Android系統會把<code>Main Activity</code>壓棧,值得注意的是這是還可以再運作别的APP Activity,然後把它放到Activity棧中。傳回鍵預設會從Activity棧進行pop操作,是以如果使用者按下傳回鍵,就可以切換運作已運作的App了。
Activities還可以用Intent元件初始化别的Activity,初始化時可攜帶資料。啟動一個新的Activity類似于IOS上建立一個<code>UIViewController</code>。最基本的啟動一個新的Activity的方式就是建立一個帶有data的Intent元件。Android上實作自定義Intent初始化器的最好方法就是寫一個靜态<code>getter</code>方法。在Activity結束的時候也可以傳回資料,在Activity結束的時候可以往Intent裡面放置額外的資料。
IOS和Android的一個大的差別是,任何一個在<code>AndroidManifest</code>檔案中注冊的Activity都可以作為程式的入口,為Activity設定一個<code>intent filter</code>屬性比如<code>“media intent”</code>,就可以處理系統的媒體檔案了。最好的例子就是編輯照片Activity。它可以打開一張照片,然後進行修改,最後在Activity結束時傳回修改後的照片。
附加提醒:要想在Activity和Fragment之間傳遞對象,必須要實作<code>Parcelable</code>接口,就像在IOS裡需要遵循協定一樣。還有,<code>Parcelable</code>對象可以存在于Activity或者Fragment的<code>savedInstanceState</code>裡,這樣在它們被銷毀後可以更容易重建它們的狀态。
下面就來看看怎麼在一個Activity中啟動另一個Activity,然後在第二個Activity結束時進行傳回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<code>// A request code is a unique value for returning activities</code>
<code>private</code>
<code>static</code> <code>final</code> <code>int</code> <code>REQUEST_CODE_NEXT_ACTIVITY =</code><code>1234</code><code>;</code>
<code>protected</code>
<code>void</code> <code>startNextActivity() {</code>
<code> </code><code>// Intents need a context, so give this current activity as the context</code>
<code> </code><code>Intent nextActivityIntent =</code><code>new</code>
<code>Intent(</code><code>this</code><code>, NextActivity.</code><code>class</code><code>);</code>
<code> </code><code>startActivityForResult(nextActivityResult, REQUEST_CODE_NEXT_ACTIVITY);</code>
<code>}</code>
<code>@Override</code>
<code>void</code> <code>onActivityResult(</code><code>int</code>
<code>requestCode,</code><code>int</code>
<code>resultCode, Intent data) {</code>
<code> </code><code>switch</code>
<code>(requestCode) {</code>
<code> </code><code>case</code>
<code>REQUEST_CODE_NEXT_ACTIVITY:</code>
<code> </code><code>if</code>
<code>(resultCode == RESULT_OK) {</code>
<code> </code><code>// This means our Activity returned successfully. For now, Toast this text. </code>
<code> </code><code>// This just creates a simple pop-up message on the screen.</code>
<code> </code><code>Toast.makeText(</code><code>this</code><code>,</code><code>"Result OK!"</code><code>, Toast.LENGTH_SHORT).show();</code>
<code> </code><code>}</code>
<code> </code><code>return</code><code>;</code>
<code> </code><code>} </code>
<code> </code><code>super</code><code>.onActivityResult(requestCode, resultCode, data);</code>
<code>public</code>
<code>static</code> <code>final</code> <code>String activityResultString =</code><code>"activityResultString"</code><code>;</code>
<code>/*</code>
<code> </code><code>* On completion, place the object ID in the intent and finish with OK.</code>
<code> </code><code>* @param returnObject that was processed</code>
<code> </code><code>*/</code>
<code>void</code> <code>onActivityResult(Object returnObject) {</code>
<code> </code><code>Intent data =</code><code>new</code>
<code>Intent();</code>
<code>(returnObject !=</code><code>null</code><code>) {</code>
<code> </code><code>data.putExtra(activityResultString, returnObject.uniqueId);</code>
<code> </code><code>}</code>
<code> </code>
<code> </code><code>setResult(RESULT_OK, data);</code>
<code> </code><code>finish(); </code>
Fragment的概念在Android上比較獨特,從Android3.0開始引入。Fragment是一個迷你版的控制器,可以顯示在Activity上。它有自己的狀态和邏輯,同時在一個螢幕上支援多個Fragment同時顯示。Activity充當Fragment的控制器,Fragment沒有自己的上下文環境,隻能依賴Activity存在。
使用Fragment最好的例子就是在平闆上的應用。可以在螢幕左邊放一個fragment清單,然後在螢幕的右邊放fragment的詳細資訊。Fragment可以把螢幕分成可重複利用的小塊,分别控制管理。不過要注意Fragment的生命周期,會有些細微的差别。
Fragment是實作Android結構化的一種新的方式,就像IOS中的不用<code>UITableview</code>而用<code>UICollectionView</code>實作清單資料結構化。因為隻使用Activity而不用Fragment的話,會簡單一些。不過,之後你會遇到麻煩。如果不使用Fragment代替全盤使用Activity的話,在後面需要利用intent和進行多螢幕支援的時候就會遇到困難。
下面看一個<code>UITableViewController</code>的例子和一個<code>ListFragment</code>的地鐵時刻表示例。
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<code>@interface MBTASubwayTripTableTableViewController ()</code>
<code>@property (assign, nonatomic) MBTATrip *trip;</code>
<code>@end</code>
<code>@implementation MBTASubwayTripTableTableViewController</code>
<code>-(instancetype)initWithTrip:(MBTATrip *)trip</code>
<code>{</code>
<code> </code><code>self = [super initWithStyle:UITableViewStylePlain];</code>
<code> </code><code>if (self) {</code>
<code> </code><code>_trip = trip;</code>
<code> </code><code>[self setTitle:trip.destination];</code>
<code> </code><code>}</code>
<code> </code><code>return self;</code>
<code>-(void)viewDidLoad</code>
<code> </code><code>[super viewDidLoad];</code>
<code> </code><code>[self.tableView registerClass:[MBTAPredictionCell class] forCellReuseIdentifier:[MBTAPredictionCell reuseId]];</code>
<code> </code><code>[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([MBTATripHeaderView class]) bundle:nil] forHeaderFooterViewReuseIdentifier:[MBTATripHeaderView reuseId]];</code>
<code>#pragma mark - UITableViewDataSource</code>
<code>-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView</code>
<code> </code><code>return 1;</code>
<code>-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section</code>
<code> </code><code>return [self.trip.predictions count];</code>
<code>#pragma mark - UITableViewDelegate</code>
<code>-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section</code>
<code> </code><code>return [MBTATripHeaderView heightWithTrip:self.trip];</code>
<code>-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section</code>
<code> </code><code>MBTATripHeaderView *headerView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:[MBTATripHeaderView reuseId]];</code>
<code> </code><code>[headerView setFromTrip:self.trip];</code>
<code> </code><code>return headerView;</code>
<code>-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath</code>
<code> </code><code>UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MBTAPredictionCell reuseId] forIndexPath:indexPath];</code>
<code> </code><code>MBTAPrediction *prediction = [self.trip.predictions objectAtIndex:indexPath.row];</code>
<code> </code><code>[(MBTAPredictionCell *)cell setFromPrediction:prediction];</code>
<code> </code><code>return cell;</code>
<code>-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath</code>
<code> </code><code>return NO;</code>
<code>- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath</code>
<code> </code><code>[tableView deselectRowAtIndexPath:indexPath animated:YES];</code>
<code>class</code> <code>TripDetailFragment</code><code>extends</code>
<code>ListFragment {</code>
<code> </code><code>/**</code>
<code> </code><code>* The configuration flags for the Trip Detail Fragment.</code>
<code> </code><code>*/</code>
<code> </code><code>public</code>
<code>static</code> <code>final</code> <code>class</code> <code>TripDetailFragmentState {</code>
<code> </code><code>public</code>
<code>static</code> <code>final</code> <code>String KEY_FRAGMENT_TRIP_DETAIL =</code><code>"KEY_FRAGMENT_TRIP_DETAIL"</code><code>;</code>
<code> </code><code>protected</code>
<code>Trip mTrip;</code>
<code> </code><code>* Use this factory method to create a new instance of</code>
<code> </code><code>* this fragment using the provided parameters.</code>
<code> </code><code>*</code>
<code> </code><code>* @param trip the trip to show details</code>
<code> </code><code>* @return A new instance of fragment TripDetailFragment.</code>
<code>static</code> <code>TripDetailFragment newInstance(Trip trip) {</code>
<code> </code><code>TripDetailFragment fragment =</code><code>new</code>
<code>TripDetailFragment();</code>
<code> </code><code>Bundle args =</code><code>new</code>
<code>Bundle();</code>
<code> </code><code>args.putParcelable(TripDetailFragmentState.KEY_FRAGMENT_TRIP_DETAIL, trip);</code>
<code> </code><code>fragment.setArguments(args);</code>
<code> </code><code>return</code>
<code>fragment;</code>
<code>TripDetailFragment() { }</code>
<code> </code><code>@Override</code>
<code>View onCreateView(LayoutInflater inflater, ViewGroup container,</code>
<code> </code><code>Bundle savedInstanceState) {</code>
<code> </code><code>Prediction[] predictions= mTrip.predictions.toArray(</code><code>new</code>
<code>Prediction[mTrip.predictions.size()]);</code>
<code> </code><code>PredictionArrayAdapter predictionArrayAdapter =</code><code>new</code>
<code>PredictionArrayAdapter(getActivity(), predictions);</code>
<code> </code><code>setListAdapter(predictionArrayAdapter);</code>
<code>super</code><code>.onCreateView(inflater,container, savedInstanceState);</code>
<code>void</code> <code>onViewCreated(View view, Bundle savedInstanceState) {</code>
<code> </code><code>super</code><code>.onViewCreated(view, savedInstanceState);</code>
<code> </code><code>TripDetailsView headerView =</code><code>new</code>
<code>TripDetailsView(getActivity());</code>
<code> </code><code>headerView.updateFromTripObject(mTrip);</code>
<code> </code><code>getListView().addHeaderView(headerView);</code>
下面,我們來分析Android上特有的一些元件。
<code>ListView</code>和IOS的<code>UITableView</code>最像,也是使用最頻繁的元件之一。類似于UITableView的<code>UITableViewController</code>,<code>ListView</code>也有一個<code>ListActivity</code>,還有<code>ListFragment</code>。這些元件會更好地處理一些布局問題,也為操作資料擴充卡提供了便利,這個接下來會說到。下面這個例子就是使用<code>ListFragment</code>來展示資料,類似<code>TableView</code>的<code>datasource</code>。
關于datasource,Android上沒有datasource和delegate,隻有Adapter。Adapter有很多種形式,主要功能其實就是為了把datasource和delegate合在一起。Adapter拿到資料然後填充到Listview中,在ListView中初始化響應的元件并顯示出來,下面是arrayAdapter的使用:
<code>class</code> <code>PredictionArrayAdapter</code><code>extends</code>
<code>ArrayAdapter<Prediction> {</code>
<code> </code><code>int</code>
<code>LAYOUT_RESOURCE_ID = R.layout.view_three_item_list_view;</code>
<code>PredictionArrayAdapter(Context context) {</code>
<code> </code><code>super</code><code>(context, R.layout.view_three_item_list_view);</code>
<code>PredictionArrayAdapter(Context context, Prediction[] objects) {</code>
<code> </code><code>super</code><code>(context, R.layout.view_three_item_list_view, objects);</code>
<code>View getView(</code><code>int</code>
<code>position, View convertView, ViewGroup parent)</code>
<code> </code><code>{</code>
<code> </code><code>Prediction prediction =</code><code>this</code><code>.getItem(position);</code>
<code> </code><code>View inflatedView = convertView;</code>
<code> </code><code>if</code><code>(convertView==</code><code>null</code><code>)</code>
<code> </code><code>{</code>
<code> </code><code>LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);</code>
<code> </code><code>inflatedView = inflater.inflate(LAYOUT_RESOURCE_ID, parent,</code><code>false</code><code>);</code>
<code> </code><code>TextView stopNameTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_left_text_view);</code>
<code> </code><code>TextView middleTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_middle_text_view);</code>
<code> </code><code>TextView stopSecondsTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_right_text_view);</code>
<code> </code><code>stopNameTextView.setText(prediction.stopName);</code>
<code> </code><code>middleTextView.setText(</code><code>""</code><code>);</code>
<code> </code><code>stopSecondsTextView.setText(prediction.stopSeconds.toString());</code>
<code>inflatedView;</code>
可以看到,adapter裡面有一個很重要的方法叫getView,和IOS的<code>cellForRowAtIndexPath</code>方法一樣。還有一個相似之處就是循環利用的政策,和IOS6上的實作很相似。在Android和IOS上循環利用View都很重要,事實上它對清單的實作有很大幫助。這個adapter很簡單,使用了一個内建的類<code>ArrayAdapter</code>來存放資料,也解釋了怎麼把資料填入<code>ListView</code>中。
IOS開發者在寫Android的過程中還要注意的就是Android的生命周期。可以先從Activity的生命周期文檔開始:
本質上Activity的生命周期很像UIViewController的生命周期,主要差別在于Android上可以任意銷毀Activity,是以保證Activity的資料和狀态很重要,如果在<code>onCreate()</code>中儲存了的話,可以在saved state中恢複Activity的狀态。最好的方法就是使用<code>saveInstanceState</code>來存儲bundled資料,例如下面的<code>TripListActivity</code>是示例工程的一部分,用來儲存目前顯示的資料:
<code>static</code> <code>Intent getTripListActivityIntent(Context context, TripList.LineType lineType) {</code>
<code> </code><code>Intent intent =</code><code>new</code>
<code>Intent(context, TripListActivity.</code><code>class</code><code>);</code>
<code> </code><code>intent.putExtra(TripListActivityState.KEY_ACTIVITY_TRIP_LIST_LINE_TYPE, lineType.getLineName());</code>
<code> </code><code>return</code>
<code>intent;</code>
<code>static</code> <code>final</code> <code>class</code> <code>TripListActivityState {</code>
<code>static</code> <code>final</code> <code>String KEY_ACTIVITY_TRIP_LIST_LINE_TYPE =</code><code>"KEY_ACTIVITY_TRIP_LIST_LINE_TYPE"</code><code>;</code>
<code>TripList.LineType mLineType; </code>
<code>void</code> <code>onCreate(Bundle savedInstanceState) {</code>
<code> </code><code>super</code><code>.onCreate(savedInstanceState);</code>
<code> </code><code>mLineType = TripList.LineType.getLineType(getIntent().getStringExtra(TripListActivityState.KEY_ACTIVITY_TRIP_LIST_LINE_TYPE));</code>
<code>} </code>
還有一個要注意的地方就是螢幕旋轉:如果螢幕發生旋轉,會改變Activity的生命周期。也就是說,Activity會先被銷毀,然後再重建。如果已經儲存了資料和狀态,Activity可以重建原來的狀态,實作無縫重建。很多APP開發者在遇到APP旋轉時會出現問題,因為Activity沒有處理旋轉的改變。注意不要用鎖定螢幕的方向來解決這個問題,因為這樣會存在一個隐含的生命周期的bug,在某些情況下還是可能發生的。
Fragment的生命周期和Activity的很像,但是有一些差別:
還有一個問題就是Fragment和Activity通信的問題。需要注意的是<code>onAttach()</code>方法在<code>onActivityCreated()</code>方法之前被調用,這就意味着在fragment建立完成後Activity還不能保證已經存在。如果需要為父Activity設定接口或者代理,則需要在<code>onActivityCreated()</code>方法調用之後。
Fragment也有可能會在系統需要的時候被建立和銷毀。如果要儲存它的狀态,那麼也要像Activity一樣進行處理。下面這個是示例項目中的一個小例子,<code>trip</code>清單Fragment會記錄相應的資料,和上面的地鐵時間示例一樣:
<code>/**</code>
<code> </code><code>* The configuration flags for the Trip List Fragment.</code>
<code>static</code> <code>final</code> <code>class</code> <code>TripListFragmentState {</code>
<code>static</code> <code>final</code> <code>String KEY_FRAGMENT_TRIP_LIST_LINE_TYPE =</code><code>"KEY_FRAGMENT_TRIP_LIST_LINE_TYPE"</code><code>;</code>
<code>static</code> <code>final</code> <code>String KEY_FRAGMENT_TRIP_LIST_DATA =</code><code>"KEY_FRAGMENT_TRIP_LIST_DATA"</code><code>;</code>
<code> </code><code>* Use this factory method to create a new instance of</code>
<code> </code><code>* this fragment using the provided parameters.</code>
<code> </code><code>*</code>
<code> </code><code>* @param lineType the subway line to show trips for.</code>
<code> </code><code>* @return A new instance of fragment TripListFragment.</code>
<code>static</code> <code>TripListFragment newInstance(TripList.LineType lineType) {</code>
<code> </code><code>TripListFragment fragment =</code><code>new</code>
<code>TripListFragment();</code>
<code> </code><code>Bundle args =</code><code>new</code>
<code> </code><code>args.putString(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_LINE_TYPE, lineType.getLineName());</code>
<code> </code><code>fragment.setArguments(args);</code>
<code>TripList mTripList;</code>
<code>void</code> <code>setTripList(TripList tripList) {</code>
<code> </code><code>Bundle arguments =</code><code>this</code><code>.getArguments();</code>
<code> </code><code>arguments.putParcelable(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_DATA, tripList);</code>
<code> </code><code>mTripList = tripList;</code>
<code> </code><code>if</code>
<code>(mTripArrayAdapter !=</code><code>null</code><code>) {</code>
<code> </code><code>mTripArrayAdapter.clear();</code>
<code> </code><code>mTripArrayAdapter.addAll(mTripList.trips);</code>
<code> </code><code>super</code><code>.onCreate(savedInstanceState);</code>
<code>(getArguments() !=</code><code>null</code><code>) {</code>
<code> </code><code>mLineType = TripList.LineType.getLineType(getArguments().getString(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_LINE_TYPE));</code>
<code> </code><code>mTripList = getArguments().getParcelable(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_DATA);</code>
還要注意的是,Fragment經常會在<code>onCreate</code>方法中利用<code>bundled</code>參數重建自己的狀态。而自定義的Trip清單模型類相關的<code>setter</code>方法也會把對象添加到<code>bundled</code>參數中。這樣就可以保證在Fragment被銷毀或者重建時,比如螢幕旋轉後,可以利用最新的資料去重建狀态。
和Android上其它部分的開發工作一樣,指定布局檔案也有自己的優缺點。Android上的布局檔案都存放在<code>res/layouts</code>檔案夾中,以易讀的xml形式存儲。
地鐵清單布局
<code><</code><code>RelativeLayout</code>
<code>xmlns:android</code><code>=</code><code>"http://schemas.android.com/apk/res/android"</code>
<code> </code><code>xmlns:tools</code><code>=</code><code>"http://schemas.android.com/tools"</code>
<code> </code><code>android:layout_width</code><code>=</code><code>"match_parent"</code>
<code> </code><code>android:layout_height</code><code>=</code><code>"match_parent"</code>
<code> </code><code>tools:context</code><code>=</code><code>"com.example.androidforios.app.activities.MainActivity$PlaceholderFragment"</code><code>></code>
<code> </code><code><</code><code>ListView</code>
<code> </code><code>android:id</code><code>=</code><code>"@+id/fragment_subway_list_listview"</code>
<code> </code><code>android:layout_width</code><code>=</code><code>"match_parent"</code>
<code> </code><code>android:layout_height</code><code>=</code><code>"match_parent"</code>
<code> </code><code>android:paddingBottom</code><code>=</code><code>"@dimen/Button.Default.Height"</code><code>/></code>
<code> </code><code><</code><code>Button</code>
<code> </code><code>android:id</code><code>=</code><code>"@+id/fragment_subway_list_Button"</code>
<code> </code><code>android:layout_height</code><code>=</code><code>"@dimen/Button.Default.Height"</code>
<code> </code><code>android:minHeight</code><code>=</code><code>"@dimen/Button.Default.Height"</code>
<code> </code><code>android:background</code><code>=</code><code>"@drawable/button_red_selector"</code>
<code> </code><code>android:text</code><code>=</code><code>"@string/hello_world"</code>
<code> </code><code>android:textColor</code><code>=</code><code>"@color/Button.Text"</code>
<code> </code><code>android:layout_alignParentBottom</code><code>=</code><code>"true"</code>
<code> </code><code>android:gravity</code><code>=</code><code>"center"</code><code>/></code>
<code></</code><code>RelativeLayout</code><code>></code>
下面這個是IOS上用UITableView和UIButton來制作的類似效果:
可以發現,Android的布局檔案更容易閱讀和了解,而且提供了多種布局方式,我們隻介紹了其中的一小部分。
通常來說,我們接觸的最基本的UI結構就是<code>ViewGroup</code>的子類,RelativeLayout、LinearLayout、FrameLayout是最常用的。這些ViewGroup的子類可以容納别的View,并包含了一些排布控件的屬性。
一個很好的例子就是上面用到的<code>RelativeLayout</code>,在裡面可以使用<code>android:layout_alignParentBottom="true"</code>來把按鈕定位到布局底部。
最後,如果要在Fragment或者Activity中使用這些控件的話,可以在<code>onCreateView()</code>方法中使用布局的資源ID:
<code>View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {</code>
<code>inflater.inflate(R.layout.fragment_subway_listview, container,</code><code>false</code><code>);</code>
請使用dp(density-independent pixels),不直接使用dx(pixels);
不要在可視化編輯器中移動布局元件——通常來說可視化編輯器在你調好高和寬後,會為元件添加一些多餘的像素,是以最好就是直接操作xml檔案;
如果在布局的<code>height</code>和<code>width</code>看到有用<code>fill_parent</code>這個屬性的話,你會發現在API
8的時候這個屬性就已經被限制了,改用<code>match_parent</code>替換。
Android上的資料存儲也和IOS上差不多:
SharedPreferences、NSUserDefaults;
記憶體存儲對象;
internal、external檔案讀寫document directory檔案讀寫;
SQLite資料庫存儲Core Data形式資料庫存儲。
之前已經讨論的東西隻是描述了Android的大概 ,要想好好利用Android上的更多的特性,本人建議你看看下面的這些概念:
ActionBar,Overflow Menu,還有Menu Button;
跨應用間資料共享;
響應系統actions;
好好學習Java的特性:泛型、抽象方法和抽象類等等;
看看Google的低版本相容庫;
我們可以學到更多的解決問題的技巧和方式。因為兩平台的實作細節各不相同,也許了解Android的工作原理可以對IOS的下一個版本的開發工作有所幫助。系統之間有很多相似的地方,誰知道下個版本的IOS會出現什麼呢?
[ 轉載必須在正文中标注并保留原文連結、譯文連結和譯者等資訊。]