天天看点

iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点

在移动应用飞速发展的今天,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的生命周期,会有些细微的差别。

iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点

Fragment是实现Android结构化的一种新的方式,就像IOS中的不用<code>UITableview</code>而用<code>UICollectionView</code>实现列表数据结构化。因为只使用Activity而不用Fragment的话,会简单一些。不过,之后你会遇到麻烦。如果不使用Fragment代替全盘使用Activity的话,在后面需要利用intent和进行多屏幕支持的时候就会遇到困难。

下面看一个<code>UITableViewController</code>的例子和一个<code>ListFragment</code>的地铁时刻表示例。

iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点

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>

iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点

<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&lt;Prediction&gt; {</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的生命周期文档开始:

iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点

本质上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的很像,但是有一些区别:

iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点

还有一个问题就是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形式存储。

地铁列表布局

iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点

<code>&lt;</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>&gt;</code>

<code>    </code><code>&lt;</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>/&gt;</code>

<code>    </code><code>&lt;</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>/&gt;</code>

<code>&lt;/</code><code>RelativeLayout</code><code>&gt;</code>

下面这个是IOS上用UITableView和UIButton来制作的类似效果:

iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点
iOS程序员必须知道的Android要点 iOS程序员必须知道的Android要点

可以发现,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会出现什么呢?

[ 转载必须在正文中标注并保留原文链接、译文链接和译者等信息。]

继续阅读