在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ iOS 開發(fā)者的 Android 第一課
與四軸無人機的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應用數(shù)據(jù)腳本化
一個完整的 Core Data 應用
插件
字符串
為 iOS 建立 Travis CI
先進的自動布局工具箱
動畫
為 iOS 7 重新設計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡應用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應式 Android 應用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學習
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學習的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應式視圖
Square Register 中的擴張
DTrace
基礎集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設計的藝術(shù)
導航應用
線程安全類的設計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標簽
同步案例學習
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機工作原理
Build 過程

iOS 開發(fā)者的 Android 第一課

隨著移動軟件工業(yè)的發(fā)展,一個移動產(chǎn)品只局限于 iOS 系統(tǒng)變得越來越不切實際。 Android 目前占有近 80% 的智能手機份額1,它能給一個產(chǎn)品帶來的潛在用戶量實在不能再被忽略了。

在本文中,我會在 iOS 的開發(fā)范圍內(nèi)介紹 Android 開發(fā)的核心內(nèi)容。 Android 和 iOS 處理類似的問題集,但在大部分問題上,它們都有不同的解決方式。通過本文,我會使用一個配套項目(在 GitHub 上)來說明如何在兩個平臺上開發(fā)以完成相同的任務。

除了 iOS 開發(fā)的相關知識,我假設你在 Java 上也有一定經(jīng)驗,能夠安裝和使用ADT(Android Development Tools)。此外,如果你最近才開始 Android 開發(fā),讀一遍 Google 編寫的關于創(chuàng)建你的第一個應用的教程會很有幫助。

UI設計概要

本文不會深入到介紹 iOS 和 Android 在用戶體驗和設計模式上的不同。然而,了解一些當今 Android 上使用的關鍵 UI 范式,比如 Action Bar、Overflow Menu、Back Button、Share Action 等,還是會很有好處的。如果你正在認真考慮 Android 開發(fā),我推薦你從 Google Play Store 買個 Nexus 5,將它作為你的主要設備,用滿一周,強迫自己最大程度的去體驗這個操作系統(tǒng)。一個開發(fā)者若不清楚要為之開發(fā)的操作系的關鍵使用模式,就那是對產(chǎn)品的不負責任。

語言應用結(jié)構(gòu)

Java

Objective-C 和 Java 之間有很多不同,雖然若能將 Objective-C 的方式帶入 Java 可能會很有誘惑力,但這樣做很可能導致代碼庫與驅(qū)動它的主要框架產(chǎn)生沖突??傊幸恍┬枰岱赖叵葳澹?/p>

  • 類前綴就留在 Objective-C 里不要帶過來了。Java 有實在的命名空間和包管理,所以不再需要類前綴。
  • 實例變量的前綴是 m,不是 _。盡可能多的在代碼里使用JavaDoc來寫方法和類描述,它能讓你和其他人更舒服些。
  • Null 檢查!Objective-C能妥善處理向nil發(fā)送消息,但Java不行。
  • 向?qū)傩哉f再見。如果你想要 setter 和 getter,你只能實際地創(chuàng)建一個 getVariableName()方法,并顯式的調(diào)用它。使用 this.object 不會調(diào)用你自定義地getter,你必須使用 this.getObjct。
  • 同樣的,給方法名加上 getset 前綴來更好的識別 getter 和 setter 。Java 方法通常寫為動作和查詢,例如 getCell(),而不是 cellForRowAtIndexPath:

項目結(jié)構(gòu)

Android 應用主要分為兩個部分,第一部分是 Java 源代碼。源代碼通過 Java 包的方式進行組織,所以可按照你的喜好來決定。然而一個常見的實踐是為 Activity、Fragment、View、Adapter 和 Data(模型和管理器)使用頂層的類別(top-level categories)。

第二個主要部分是 res 文件夾,也就是資源文件夾。res 文件夾包含有圖像、 XML 布局文件,以及 XML 值文件,它們構(gòu)成了大部分非代碼資源。在 iOS 上,圖像可有 @2x 版本,但在 Android 上有好幾種不同的屏幕密度文件夾要考慮2。Android 使用文件夾來組織文件、字符串以及其他與屏幕密度相關的值。res 文件夾還包含有 XML 布局文件,就像 xib 文件一樣。最后,還有其他 XML 文件存儲了字符串、整數(shù)和樣式資源。

最后一個與項目結(jié)構(gòu)相關的是 AndroidManifest.xml 文件。這個文件等同于 iOS 上的 Project-Info.plist 文件,它存儲著 Activity 信息、應用名字,并設置應用能處理的 Intent 3(系統(tǒng)級事件)。關于 Intent 的更多信息,繼續(xù)閱讀本文,或者閱讀 Intents 這篇文章。

Activity

Activity 是 Android 應用的基本顯示單元,就像 UIViewController 是iOS的基本顯示組件一樣。作為 UINavigationController 的替代,Android 由系統(tǒng)來維護一個 Activity 棧。當應用完成加載,系統(tǒng)將應用的主 Activity(main activity)壓到棧上。注意你也可以加載其他應用的 Activity 并將它們放在棧里。默認,Android 上的返回(back)按鈕將從系統(tǒng)的 Activity 棧中彈出 Activity,所以當用戶不停地按下返回時,他可以見到多個曾經(jīng)加載過的應用。

通過使用包含有額外的數(shù)據(jù) Intent,Activity 同樣可以初始化其他 Activity 。通過 Intent 啟動 Activity 類似于通過自定義的 init 方法創(chuàng)建一個 UIViewController。因為最常見的加載新 Activity 的方法是創(chuàng)建一個有數(shù)據(jù)的 Intent,在 Android 上暴露自定義初始化方法的一個非常棒的方式是創(chuàng)建一個靜態(tài) Intent getter 方法。Activity 同樣能在完成時返回結(jié)果(再見,modal 代理),當其完成時在 Intent 上放置額外數(shù)據(jù)即可。

Android 和 iOS 的一大差別是任何 Activity 都可以作為你應用的入口,只要它們在 AndroidManifest文件里正確注冊即可。在 AndroidManifest.xml 文件中,為一個 Activity 設置一個 media intent 的 Intent 過濾器的話,就能讓系統(tǒng)知道這個 Activity 可以作為包含有媒體數(shù)據(jù)的 Intent 的入口。一個不錯的例子是相片編輯 Activity ,它打開一個相片,修改它,并在 Activity 完成時返回修改后的圖片。

作為一個旁注,如果你想在 Activity 和 Fragment 之間發(fā)送模型對象的話,它們必須實現(xiàn) Parcelable 接口。實現(xiàn) Parcelable 接口很類似于 iOS 上實現(xiàn) <NSCopying> 協(xié)議。同樣值得一提的是,Parcelable 對象可以存儲在 activity 或者 fragment 的 savedInstanceState 里,這是為了能更容易地在它們被銷毀后恢復它們的狀態(tài)。

接下來就看看一個 Activity 啟動另一個 Activity,同時能在第二個 Activity 完成時做出響應。

加載另一個Activity并得到結(jié)果

// request code 是為返回 activities 所設置的特定值
private static final int REQUEST_CODE_NEXT_ACTIVITY = 1234;

protected void startNextActivity() {
    // Intents 需要一個 context, 所以將當前的 activity 作為 context 給入
    Intent nextActivityIntent = new Intent(this, NextActivity.class);
       startActivityForResult(nextActivityResult, REQUEST_CODE_NEXT_ACTIVITY);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case REQUEST_CODE_NEXT_ACTIVITY:
        if (resultCode == RESULT_OK) {
            // 這表示我們的 activity 成功返回了。現(xiàn)在顯示一段提示文字
            // 這里在屏幕上創(chuàng)建了一個簡單的 pop-up 消息框
                Toast.makeText(this, "Result OK!", Toast.LENGTH_SHORT).show();
            }
            return;
        }    
        super.onActivityResult(requestCode, resultCode, data);
}

在 Activity finish() 上返回結(jié)果

public static final String activityResultString = "activityResultString";

/*
 * 結(jié)束時調(diào)用, 在 intent 上設置 object ID 并調(diào)用成功結(jié)束
 * @param returnObject 是要處理的對象
 */
private void onActivityResult(Object returnObject) {
        Intent data = new Intent();
        if (returnObject != null) {
            data.putExtra(activityResultString, returnObject.uniqueId);
        }

        setResult(RESULT_OK, data);
        finish();        
}

Fragments

Fragment 的概念是 Android 獨有的,它最近才隨著 Android 3.0 的問世而出現(xiàn)。Fragment 是一種迷你控制器,能夠被實例化來填充 Activity。它們可以存儲狀態(tài)信息,還有可能包含視圖邏輯,但區(qū)別 Activity 和 Fragment 的最大不同在于。同一時間里屏幕上可能有多個 Fragment。同時注意 Fragment 自身沒有上下文,它們嚴重依賴 Activity 來將它們和應用的狀態(tài)聯(lián)系起來。

平板是使用 Fragment 的絕好場景:你可以在左邊放一個列表 Fragment,右邊放一個詳細信息 Fragment。4Fragment 能讓你將 UI 和控制器邏輯分割到更小、可重用的層面上。但要當心,F(xiàn)ragment 的生命周期有不少細微差別,我們會在后面詳細談到。

http://wiki.jikexueyuan.com/project/objc/images/11-1.png" alt="" />

Fragment 是實現(xiàn) App 的新方式,就像在 iOS 上 UICollectionView 是可取代 UITableView 的構(gòu)造列表數(shù)據(jù)的新方式。5 雖然在一開始避開 Fragment 而使用 Activity 會比較容易,但你之后可能會為之后悔。然而,我們也要抗拒那種想完全放棄 Activity,轉(zhuǎn)而只在單個 Activity 上使用 Fragment 的沖動,因為如果那么做了,那么當你想獲得 Intent 的好處且想在同一個 Activity 上使用多個 Fragment 時,你將陷入困境。

現(xiàn)在來看一個例子,UITableViewControllerListFragment 是如何分別顯示一個地鐵行程預測時刻表,數(shù)據(jù)由 MBTA 所提供。

Table View Controller 實現(xiàn)

http://wiki.jikexueyuan.com/project/objc/images/11-2.png" alt="" />

@interface MBTASubwayTripTableTableViewController ()

@property (assign, nonatomic) MBTATrip *trip;

@end

@implementation MBTASubwayTripTableTableViewController

- (instancetype)initWithTrip:(MBTATrip *)trip
{
    self = [super initWithStyle:UITableViewStylePlain];
    if (self) {
        _trip = trip;
        [self setTitle:trip.destination];
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.tableView registerClass:[MBTAPredictionCell class] forCellReuseIdentifier:[MBTAPredictionCell reuseId]];
    [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([MBTATripHeaderView class]) bundle:nil] forHeaderFooterViewReuseIdentifier:[MBTATripHeaderView reuseId]];
}

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.trip.predictions count];
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return [MBTATripHeaderView heightWithTrip:self.trip];
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    MBTATripHeaderView *headerView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:[MBTATripHeaderView reuseId]];
    [headerView setFromTrip:self.trip];
    return headerView;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MBTAPredictionCell reuseId] forIndexPath:indexPath];

    MBTAPrediction *prediction = [self.trip.predictions objectAtIndex:indexPath.row];
    [(MBTAPredictionCell *)cell setFromPrediction:prediction];

    return cell;
}

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    return NO;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

@end

List Fragment 實現(xiàn)

http://wiki.jikexueyuan.com/project/objc/images/11-3.png" alt="" />

public class TripDetailFragment extends ListFragment {

    /**
     * Trip Detail Fragment的配置標識.
     */
    public static final class TripDetailFragmentState {
        public static final String KEY_FRAGMENT_TRIP_DETAIL = "KEY_FRAGMENT_TRIP_DETAIL";
    }

    protected Trip mTrip;

    /**
     * 根據(jù)提供的參數(shù)使用這個工廠方法來創(chuàng)建 fragment 的新的實例
     *
     * @param trip trip的詳細信息
     * @return fragment TripDetailFragment 的新實例.
     */
    public static TripDetailFragment newInstance(Trip trip) {
        TripDetailFragment fragment = new TripDetailFragment();
        Bundle args = new Bundle();
        args.putParcelable(TripDetailFragmentState.KEY_FRAGMENT_TRIP_DETAIL, trip);
        fragment.setArguments(args);
        return fragment;
    }

    public TripDetailFragment() { }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Prediction[] predictions= mTrip.predictions.toArray(new Prediction[mTrip.predictions.size()]);
        PredictionArrayAdapter predictionArrayAdapter = new PredictionArrayAdapter(getActivity(), predictions);
        setListAdapter(predictionArrayAdapter);
        return super.onCreateView(inflater,container, savedInstanceState);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TripDetailsView headerView = new TripDetailsView(getActivity());
        headerView.updateFromTripObject(mTrip);
        getListView().addHeaderView(headerView);
    }
}

下一節(jié),我們將研究一些 Android 獨有的組件。

通用Android組件

列表視圖與適配器

ListView 是 Android 上 UITableView 的近似物,也是最常使用的一種組件。就像 UITableView 有一個助手 View Controller UITableViewController 那樣,ListView 也有一個助手 Activity 叫做 ListActivity,它還有一個助手 Fragment 叫做 ListFragment。同UITableViewController類似,這些助手為你處理布局(類似 xib)并提供管理適配器(下面將討論)能夠使用的簡便方法。上面的例子使用一個 ListFragment 來顯示來自一個 Prediction 模型對象列表的數(shù)據(jù),類比一下,其實就相當于 UITableView 的 datasource 提供了一個 Prediction 模型對象數(shù)組,并用它來填充 ListView。

說到 datasource,在 Android 上,我們沒有用于 ListView 的 datasource 和 delegate。作為代替,我們有適配器 (adapters)。適配器有多種形式,但它們的主要目標類似于將 datasource 和 delegate 合二為一。適配器拿到數(shù)據(jù)并通過實例化視圖適配它去填充 ListView,這樣 ListView 就會顯示出來了。讓我們來看看上面使用的數(shù)組適配器:

public class PredictionArrayAdapter extends ArrayAdapter<Prediction> {

    int LAYOUT_RESOURCE_ID = R.layout.view_three_item_list_view;

    public PredictionArrayAdapter(Context context) {
        super(context, R.layout.view_three_item_list_view);
    }

    public PredictionArrayAdapter(Context context, Prediction[] objects) {
        super(context, R.layout.view_three_item_list_view, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        Prediction prediction = this.getItem(position);
        View inflatedView = convertView;
        if(convertView==null)
        {
            LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            inflatedView = inflater.inflate(LAYOUT_RESOURCE_ID, parent, false);
        }

        TextView stopNameTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_left_text_view);
        TextView middleTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_middle_text_view);
        TextView stopSecondsTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_right_text_view);

        stopNameTextView.setText(prediction.stopName);
        middleTextView.setText("");
        stopSecondsTextView.setText(prediction.stopSeconds.toString());

        return inflatedView;
    }
}

你會注意到此適配器有一個叫做 getView 的重要方法,它非常類似于 cellForRowAtIndexPath: 。另一個易被發(fā)現(xiàn)的相似點是一個重用視圖的模式,類似于 iOS 6。視圖重用同在 iOS 上的情況一樣重要,因為它能非常顯著地提高性能!這個適配器有點兒簡單,因為它使用了內(nèi)建的父類 ArrayAdapter<T> ,用于數(shù)組數(shù)據(jù),但它依然說明了如何用一個數(shù)據(jù)集來填充一個 ListView。

AsyncTasks

作為 iOS 上 GCD(Grand Central Dispatch)的替代,Android 上可以使用 AsyncTasks。AsyncTasks 是一個以更加友好的方式處理異步的工具。AsyncTasks有點超出本文的范圍,但我強烈建議你閱讀相關文檔。

Activity 生命周期

從 iOS 開發(fā)轉(zhuǎn)過來時候,我們需要注意的首要事情之一是安卓的生命周期。讓我們從查看 Activity 生命周期文檔 開始:

http://wiki.jikexueyuan.com/project/objc/images/11-4.png" alt="" />

從本質(zhì)上看來,Activity 生命周期近似于 UIViewController 生命周期。主要的不同是 Android 系統(tǒng)在銷毀 Activity 上比較無情,因此保證數(shù)據(jù)和 Activity 的狀態(tài)的保存是非常重要的,因此只有這樣它們才能在 onCreate() 中從被保存的狀態(tài)里恢復。做到這個的最好方式是使用綁定數(shù)據(jù)(bundled data)并從 savedInstanceState 和/或 Intents 中恢復。例如,下面是來自我們示例項目中 TripListActivity 的部分代碼,它能跟蹤當前顯示的地鐵線路:

public static Intent getTripListActivityIntent(Context context, TripList.LineType lineType) {
    Intent intent = new Intent(context, TripListActivity.class);
    intent.putExtra(TripListActivityState.KEY_ACTIVITY_TRIP_LIST_LINE_TYPE, lineType.getLineName());
    return intent;
}

public static final class TripListActivityState {
    public static final String KEY_ACTIVITY_TRIP_LIST_LINE_TYPE = "KEY_ACTIVITY_TRIP_LIST_LINE_TYPE";
}

TripList.LineType mLineType;    

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   mLineType = TripList.LineType.getLineType(getIntent().getStringExtra(TripListActivityState.KEY_ACTIVITY_TRIP_LIST_LINE_TYPE));
}    

注意旋轉(zhuǎn):在旋轉(zhuǎn)時,生命周期會被完全重設。就是說,在旋轉(zhuǎn)發(fā)生時,你的 Activity 將被摧毀并重建。如果數(shù)據(jù)被正確保存在 savedInstanceState 里,而且 Activity 能在重新創(chuàng)建后正確恢復的話,那么旋轉(zhuǎn)看起來就會無縫平滑。許多應用開發(fā)者開發(fā)的應用因為 Activity 沒有正確處理狀態(tài)的改變,導致在旋轉(zhuǎn)時出現(xiàn)穩(wěn)定性問題。小心!不要通過鎖定屏幕旋轉(zhuǎn)來避免這種問題,這樣做只會掩蓋生命周期 bug,它們依然會在 Activity 被系統(tǒng)摧毀的時候冒出來。

Fragment 生命周期

Fragment 生命周期相似于 Activity 生命周期,但有些附加的東西:

http://wiki.jikexueyuan.com/project/objc/images/11-5.png" alt="" />

能讓開發(fā)者措手不及的問題之一是 Fragment 和 Activity 之間的通信問題。注意 onAttach() 先于 onActivityCreated() 調(diào)用。這就意味著 Activity 不能保證在 Fragment 被創(chuàng)建前存在。onActivityCreated() 方法應該在有必要的時候用于將 interface(delegate)設置到父親 Activity 上。

Fragment 同樣被操作系統(tǒng)積極地創(chuàng)建和銷毀,為了保存它們的狀態(tài),需要同 Activity 一樣多的勞動量。下面是來自示例項目的一個例子,此處的旅程列表 Fragment 一直追蹤TripList數(shù)據(jù),以及地鐵線路類型:

/**
 * Trip List Fragment 的配置標識.
 */
public static final class TripListFragmentState {
    public static final String KEY_FRAGMENT_TRIP_LIST_LINE_TYPE = "KEY_FRAGMENT_TRIP_LIST_LINE_TYPE";
    public static final String KEY_FRAGMENT_TRIP_LIST_DATA = "KEY_FRAGMENT_TRIP_LIST_DATA";
}

/**
 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param lineType the subway line to show trips for.
 * @return A new instance of fragment TripListFragment.
 */
public static TripListFragment newInstance(TripList.LineType lineType) {
    TripListFragment fragment = new TripListFragment();
    Bundle args = new Bundle();
    args.putString(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_LINE_TYPE, lineType.getLineName());
    fragment.setArguments(args);
    return fragment;
}

protected TripList mTripList;
protected void setTripList(TripList tripList) {
    Bundle arguments = this.getArguments();
    arguments.putParcelable(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_DATA, tripList);
    mTripList = tripList;
    if (mTripArrayAdapter != null) {
        mTripArrayAdapter.clear();
        mTripArrayAdapter.addAll(mTripList.trips);
    }
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        mLineType = TripList.LineType.getLineType(getArguments().getString(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_LINE_TYPE));
        mTripList = getArguments().getParcelable(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_DATA);
    }
}    

注意到 Fragment 總是用 onCreate 里的綁定參數(shù)(bundled arguments)來恢復它的狀態(tài),用于 TripList 模型對象的自定義的 setter 會將對象添加到綁定參數(shù)中去。這就保證了如果 Fragment 在例如設備被旋轉(zhuǎn)時被銷毀并重建的話,F(xiàn)ragment 總是有最新的數(shù)據(jù)并從中恢復。

布局

類似于 Android 開發(fā)的其他部分,Android 對比 iOS,在指定布局這里同樣有優(yōu)點和缺點。布局被存儲為人類可讀的 XML 文件,放在 res/layouts 文件夾中。

地鐵列表視圖布局

http://wiki.jikexueyuan.com/project/objc/images/11-6.png" alt="" />

<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"
    tools:context="com.example.androidforios.app.activities.MainActivity$PlaceholderFragment">

    <ListView
        android:id="@+id/fragment_subway_list_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/Button.Default.Height"/>

    <Button
        android:id="@+id/fragment_subway_list_Button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/Button.Default.Height"
        android:minHeight="@dimen/Button.Default.Height"
        android:background="@drawable/button_red_selector"
        android:text="@string/hello_world"
        android:textColor="@color/Button.Text"
        android:layout_alignParentBottom="true"
        android:gravity="center"/>

</RelativeLayout>

下面是 iOS 上在 Interface Builder 中用UITableView和一個通過 Auto Layout 釘在底部的 UIButton 實現(xiàn)的同一個視圖:

http://wiki.jikexueyuan.com/project/objc/images/11-7.png" alt="" />

http://wiki.jikexueyuan.com/project/objc/images/11-8.png" alt="" />

你會注意到 Android 布局文件更易閱讀和理解。Android 中的布局視圖有許多不同的部分,但這里我們只會覆蓋到少數(shù)幾個重要的部分。

你需要處理的主要結(jié)構(gòu)就是 ViewGroup 的子類,比如 RelativeLayoutLinearLayout,以及 FrameLayout,這些就是最常見的。這些 ViewGroup 包含其他視圖并暴露屬性來在屏幕上安排它們。

一個不錯的例子是使用上面提到的 RelativeLayout,一個相對布局允許我們在布局中使用 android:layout_alignParentBottom="true" 這樣的語句來將按鈕釘在底部。

最后,要將布局連接到 Fragment 或 Activity,只需要簡單地在 onCreateView 上使用布局的資源 ID 即可:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_subway_listview, container, false);
}

布局小貼士

  • 總是去處理 dp(密度無關的像素)的情況,而不是直接使用像素。
  • 不要在可視化編輯器里去調(diào)整的部件 - 通常來說可視化編輯器會在部件之間加間隔,而不是像你所期望的那樣去調(diào)整高寬。最佳選擇應該是直接在 XML 文件中進行編輯。
  • 如果你曾看到 fill_parent 值用于布局的高或?qū)?,這個值在好幾年前的 API 8 中就被廢棄并被 match_parent 取代了。

查看看這篇響應式 Android 應用的文章能得到更多布局小貼士。

數(shù)據(jù)

Android 上可用的數(shù)據(jù)存儲選項同樣類似于 iOS 上可用的:

  • Shared Preferences <-> NSUserDefaults
  • 內(nèi)存對象
  • 通過內(nèi)部外部文件存儲將數(shù)據(jù)保存到文件結(jié)構(gòu)或是從文件結(jié)構(gòu)獲取數(shù)據(jù) <-> 保存數(shù)據(jù)到 documents 目錄
  • SQLite <-> Core Data

主要的不同是缺少 Core Data,作為替代,Android 提供了直接訪問 SQLite 數(shù)據(jù)庫的方式,并返回一個游標 (cursor) 對象作為結(jié)果。請看這篇在 Android 上使用 SQLite 的文章獲取更多此問題的細節(jié)。

Android 家庭作業(yè)

我們目前為止討論的只是一些皮毛而已。要真正從一些 Android 特有的事物里獲取好處,我建議關注以下這些特性:

編者注 關于模擬器,個人推薦GenyMotion的相關解決方案

最后的話

本文中我們討論的大部分都實現(xiàn)在 MBTA 地鐵交通示例項目中,并放在了Github上。創(chuàng)建這個項目的目的是在兩個平臺上展示應用的結(jié)構(gòu)、綁定數(shù)據(jù)、構(gòu)建 UI 等相似內(nèi)容的方式。

雖然在 Android 上一些純粹的實現(xiàn)細節(jié)非常不同,但將從 iOS 學來的問題解決技能和模式用于實踐依然非常容易。也許懂得一些 Android 的工作方式可能可以讓你準備好面對下一版的 iOS,誰又知道會怎樣呢?