LZ阅读的是中文翻译版本:http://hukai.me/android-training-course-in-chinese/basics/activity-lifecycle/recreating.html,试验机系统版本7.1。
1.5 使用Fragment建立动态的UI
1.5.1 创建Fragment
创建Fragment要继承自Fragment,一般,为了兼容低版本,要继承v4包中的Fragment。
有两种方式向Activity的视图中添加Fragment:
1、直接在Activity的布局xml文件中通过标签(fragment)的方式,通过android:name属性指明引用的是哪个Fragment,其值是要引用的Fragment的全类名。
2、在Activity的代码中通过FragmentManager添加。
1.5.2 建立灵活动态的UI
在小屏幕的设备上,如手机,通常需要替换Fragment来达到较好的user experience,而大屏幕,如平板,或者是横屏的设备上,则可以一次横放两个,甚至更能多的Fragment。
所以,如果一个App要适配不同尺寸的设备,就要通过 Android 官网Train阅读记录——1提到的各种适配方法来适配了。
如果一个新闻app想同时运行在手机和平板上,并保持很好的用户体验,达到在手机上单屏单窗,在平板上单屏双窗的效果,就需要为同一个Activity建立两个xml布局文件,例如:
其中在手机上的布局文件名为res/layout/news.xml,而在平板上的布局文件名为res/layout-large/news.xml。
在前一个布局文件中可以只有一个FrameLayout作为动态替换Fragment的容器,而在后面的文件中,可以固定的添加两个<Fragment>标签。
在Activity的代码中可以动态的通过查询FrameLayout容器是否存在,判断该设备是小屏幕还是大屏幕,这样在不同的布局中做不同的操作,就可以达到在手机上单屏单窗,在平板上单屏双窗的效果。
如果要让Fragment想Activity一样存入backstack,必须手动调用addToBackStack方法,此方法由FragmentTransaction所有,并且需要在FragmentTransaction调用commit之前调用才行。
1.5.3 Fragments之间以及Fragment和Activity之间的交互
Fragments之间不提倡直接进行交互,这样耦合性严重,可复用性差。所以,Fragments之间的交互通常通过Fragment与Activity之间的交互来完成。
Fragment和Activity之间的交互:
Activity可以通过Fragment的setArguments方法向Fragment传递消息。
Fragment可以通过自定义回调接口,让包含它的Activity实现,来完成向Activity传递消息。Fragment通过onAttach方法或者getActivity方法获取Activity的引用然后转换成相应的接口对象,这样在Fragment中调用接口的方法,通过向该方法传递参数,以达到向Activity传递消息的目的。
Fragment可以向ActionBar中添加Menu Item,实现方式为:复写onCreateOptionsMenu方法,并在onCreate方法中调用setHasOptionsMenu(true),来达到目的,然后通过复写onOptionsItemSelected方法监听按钮的点击事件。
1.6 数据保存
1.6.2 保存到文件
数据可以保存到internal storage或者external storage,保存到external storage需要WRITE_EXTERNAL_STORAGE权限。
保存到internal可以
File file = new File(context.getFilesDir(), filename);
或者
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
保存到internal的文件在app卸载的时候,会被系统随之移除。
保存到external之前需要验证external storage是否可用
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
保存到external分两种:
不想让文件随app卸载而被系统移除可以
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
其中DIRECTORY_PICTURES表示该目录存放的是照片,其他还有音乐,铃声等等类型,具体可以自行查阅。
要让文件随app卸载而被系统移除可以
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
若文件是保存在internal storage的,可以用下面的方式删除文件
myContext.deleteFile(fileName);
1.6.3 保存到数据库
通过实现BaseColumns接口,创建包含表名和列名的类,其他的关于表的创建、增删改查操作使用该类即可
public final class FeedReaderContract {
// To prevent someone from accidentally instantiating the contract class,
// give it an empty constructor.
public FeedReaderContract() {}
/* Inner class that defines the table contents */
public static abstract class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "entry";
public static final String COLUMN_NAME_ENTRY_ID = "entryid";
public static final String COLUMN_NAME_TITLE = "title";
public static final String COLUMN_NAME_SUBTITLE = "subtitle";
...
}
}
1.7 与其他应用的交互
1.7.1 Intent的发送
拨号
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
看网页
Uri webpage = Uri.parse("http://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
创建日历事件
Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);
Calendar beginTime = Calendar.getInstance().set(2012, 0, 19, 7, 30);
Calendar endTime = Calendar.getInstance().set(2012, 0, 19, 10, 30);
calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis());
calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis());
calendarIntent.putExtra(Events.TITLE, "Ninja class");
calendarIntent.putExtra(Events.EVENT_LOCATION, "Secret dojo");
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isIntentSafe = activities.size() > 0;
如果接受Intent的app不止一个,强制用户选择
Intent intent = new Intent(Intent.ACTION_SEND);
...
// Always use string resources for UI text. This says something like "Share this photo with"
String title = getResources().getText(R.string.chooser_title);
// Create and start the chooser
Intent chooser = Intent.createChooser(intent, title);
startActivity(chooser);
1.7.3 Intent过滤
如果要让其他app能够调用我们自己写的app,我们需要为被调用的Activity添加Intent-filter。若Activity中的intent-filter满足以下Intent对象的标注,系统就能够把特定的Intent发送给Activity:
Action:一个想要执行的动作的名称。通常是系统已经定义好的值,如ACTION_SEND或ACTION_VIEW。在Intent-filter中通过<action>标签指定它的值,值的类型必须为字符串。
Data:Intent附带数据的描述。在Intent-filter中通过<data>指定它的值,可以使用一个或者多个属性,我们可以只定义MIME type或者是只指定URI prefix,也可以只定义一个URI scheme,或者是它们综合使用。
Category:提供一个附加的方法来标识这个Activity能够handle的Intent。通常与用户的手势或者是启动位置有关。系统有支持几种不同的Category,但是大多数都很少用到。而且,所有的implicit intents都默认是CATEGORY_DEFAULT类型的。在Intent-filter中用<category>指定它的值。
例子
<activity android:name="ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
一个Intent-filter可以有多个action、category或者data。一个Activity可以有多个Intent-filter。
在Activity中Handle发送过来的Intent
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get the intent that started this activity
Intent intent = getIntent();
Uri data = intent.getData();
// Figure out what to do based on the intent type
if (intent.getType().indexOf("image/") != -1) {
// Handle intents with image data ...
} else if (intent.getType().equals("text/plain")) {
// Handle intents with text ...
}
}
返回Result
// Create intent to deliver some kind of result data
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri");
setResult(Activity.RESULT_OK, result);
finish();