在移动应用飞速发展的今天,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会出现什么呢?
[ 转载必须在正文中标注并保留原文链接、译文链接和译者等信息。]