Android-Fragments的使用
请转原文学习:Using Fragmenys in Android – Tutorial
where I found this resource:干干货分享——Android开发中学习资源大集合(译)
很素的翻译开始:
2. Fragments
2.1. 什么是fragments?
fragments是一个能够应用于Activity中的独立组件,他封装了功能,所以在activitys和layouts中更容易被复用。
fragment在activity的上下文环境中运行,但是他有自己的生命周期和自己的用户界面,定义一个fragmengts,我们也可以不需要用户界面,也就是无头fragments(headless fragments).
fragments可以被动态或者静态地添加到一个Activity中。
2.2. 使用fragments的好处
Fragments使得在不同的布局文件中复用组件变得更容易,你可以给手机构建单窗格布局,给平板构建多窗格布局。这并不仅限于用在平板上;例如,你也可以在智能手机上使用fragments去支持横屏和竖屏的布局。
一个典型的例子就是在Activity中项目(items)列表。在平板电脑上,如果你点某个列表项,就能立即在同屏幕右侧看到对应的详细内容(detail),而在智能手机上,你要跳转到一个新的详情页面(detail),下面是图形描述:
下面的讨论将假定你有两个fragments(main和detail),但是你也可以有更多个。我们也会有一个,main activity和一个detailed activity。在平板电脑上main activity包含了这两个Fragments,而在手机上只包含了main fragments。
下面的屏幕截图展示了这种用法:
2.3. 如何使用Fragments
用Fragments创建不同的布局,我们可以:
- 用一个activity,在平板上显示两个Fragments,在手机上显示一个fragemtns。在这种情况下,必要的时候要转换Fragments。这要求the fragment不能在布局文件layout file中被声明,同样地Fragments不能在运行时被移除。
- 在手机上使用不同的activities来 host每个fragment。例如,当平板上的UI实现是:在一个activity中使用了两个Fragments,在手机上使用同一个activity,但是提供另一种只包含一个fragment的布局。当你需要切换Fragments时, 需要调用另一个带有frgment的activity。(个人理解就是,在手机上一个activity用一个fragmen
第二种方法是最灵活,也是使用Fragments一般来说更可取的方法。在这种情况下,main activity检测如果detail fragment在布局文件可用。如果存在detailed fragment,the main activity通知fragment该更新自己的fragment。如果没有detail fragment, the main activity 启动 detailed activity。
3. Fragments 的生命周期
fragment的生命周期与持有他的activity的生命周期相关联
表一. Fragment生命周期
Method | Description |
onAttach() | fragemnt实例关联到Activity实例,此时这个activity还未完全初始化完 |
onCreate() | Fragment被创建 |
onCreateView() | fragment实例创建自己的view层级(fragment第一次话他自己的用户界面时调用)the inflated view成为activity view层级的一部分 |
onActivityCreated() | Activity和fragment的view层级创建完的同时他们的实例也已经创建完成。此时,view可以通过findviewById方法被找到 |
onResume() | Fragment可见并且是激活状态 |
onPause() | Fragment可见但非激活状态,例如,另一个activity覆盖在带有fragment的activity上面 |
onStop() | Fragment不可见 |
4. 定义和使用Fragments
4.1. 定义fragments
定义一个新的fragment,你可以继承android.app.Fragment 类或他的一个子类,例如 ListFragment, DialogFragment, PreferenceFragment或者WebViewFragment。下面的代码是一个实现的例子:
1 package com.example.android.rssfeed; 2 3 import android.app.Fragment; 4 import android.os.Bundle; 5 import android.view.LayoutInflater; 6 import android.view.View; 7 import android.view.ViewGroup; 8 import android.widget.TextView; 9 10 public class DetailFragment extends Fragment { 11 12 @Override 13 public View onCreateView(LayoutInflater inflater, ViewGroup container, 14 Bundle savedInstanceState) { 15 View view = inflater.inflate(R.layout.fragment_rssitem_detail, 16 container, false); 17 return view; 18 } 19 20 public void setText(String item) { 21 TextView view = (TextView) getView().findViewById(R.id.detailsText); 22 view.setText(item); 23 } 24 }
4.2. 静态添加fragments
要使用一个新的fragment,可以静态地将他添加到一个XML布局文件中
你可以使用FragmentManager类来检查布局文件是否包含了这个fragment
1 DetailFragment fragment = (DetailFragment) getFragmentManager(). 2 findFragmentById(R.id.detail_frag); 3 if (fragment==null || ! fragment.isInLayout()) { 4 // start new Activity 5 } 6 else { 7 fragment.update(...); 8 }
如果一个fragment在XML布局文件里已经定义了, 他的android:name属性会指向相对应的类。
4.3 Fragment生命周期
fragment有他自己的生命周期。但总是和持有他的activity的生命周期相关联。
fragment的onCreate()方法在activity的onCreate()方法之后,该fragment的onCreateView()方法之前被调用。
当fragment开始创建他的用户界面时,系统就调用onCreateView(),在该方法里你可以通过Inflator类的对象调用inflate()方法来inflate一个布局文件,该布局文件作为inflate方法的一个参数。对于headless fragments没有必要实现该方法。
当持有该fragment的activity被创建后,onActivityCreated()方法在onCreateView()方法之后被调用。在这你可以初始化一个需要Context对象的对象。
Fragment并不是Context的子类,所以你必须通过getActivity()方法来获得父activity
一旦fragment可见,onStart()方法就会被调用
如果一个activity停止了,它的fragment也会停止;如果一个activity被销毁,它的fragment也会被销毁。
4.4 fragments之间的通讯
为了增加fragments的复用,fragment和fragment不应该直接和彼此沟通,fragemnts之间的每次沟通都应该通过持有他们的activity来完成。
为了达到该目的,fragment应该定义一个内部接口,然后要求activity必须实现该接口。该方法可以避免fragment对该activity的有任何了解(不懂怎么翻译,原文:This way you avoid that the fragment has any knowledge about the activity which uses it)。在fragment的onAttach()方法中,可以检查activity是否正确实现了该接口。
例如,假定你有一个fragment,该fragemnt要将一个值传递给他的父activity。可以通过下面的方法来实现。
1 package com.example.android.rssfeed; 2 3 import android.app.Activity; 4 import android.app.Fragment; 5 import android.os.Bundle; 6 import android.view.LayoutInflater; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.Button; 10 11 public class MyListFragment extends Fragment { 12 13 private OnItemSelectedListener listener; 14 15 @Override 16 public View onCreateView(LayoutInflater inflater, ViewGroup container, 17 Bundle savedInstanceState) { 18 View view = inflater.inflate(R.layout.fragment_rsslist_overview, 19 container, false); 20 Button button = (Button) view.findViewById(R.id.button1); 21 button.setOnClickListener(new View.OnClickListener() { 22 @Override 23 public void onClick(View v) { 24 updateDetail(); 25 } 26 }); 27 return view; 28 } 29 30 public interface OnItemSelectedListener { 31 public void onRssItemSelected(String link); 32 } 33 34 @Override 35 public void onAttach(Activity activity) { 36 super.onAttach(activity); 37 if (activity instanceof OnItemSelectedListener) { 38 listener = (OnItemSelectedListener) activity; 39 } else { 40 throw new ClassCastException(activity.toString() 41 + " must implemenet MyListFragment.OnItemSelectedListener"); 42 } 43 } 44 45 @Override 46 public void onDetach() { 47 super.onDetach(); 48 listener = null; 49 } 50 51 // may also be triggered from the Activity 52 public void updateDetail() { 53 // create a string just for testing 54 String newTime = String.valueOf(System.currentTimeMillis()); 55 56 // inform the Activity about the change based 57 // interface defintion 58 listener.onRssItemSelected(newTime); 59 } 60 }
5. 在fragments中存储(持久化)数据
5.1 应用程序重启时保存数据
在fragments中你也需要存储你的应用数据。你可以把数据存储到一个中央地区。例如:
- SQLite database
- File
- 应用对象,在应用需要去处理存储的情况下
5.2配置变化时持久化数据
如果想要在配置变化时保持数据,你可以使用应用对象(application object)。
除此之外,还可以在fragments里调用setRetainState(true)方法,这种方法在配置变化时会保持fragment的实例,但是只有在fragment未加入到回退栈时才起作用。这种方式并不被Google提倡用在有用户界面的fragment上。在这种情况下,数据必须存为成员变量。
如果要被保存的数据由Bundle类支持,则可以使用onSaveInstanceState()方法将数据存放到Bundle,再在onActivityCreated()方法中找回数据。
6. 运行时修改Fragments
FragmentManager类和FragmentTransaction类允许你在activity的布局中添加,删除和替换fragments。
Fragments可以通过transaction来动态修改。要动态将fragments添加到现有的布局中,通常在要添加Fragment的XML布局文件中定义一个container(这个container就相当于在xml文件中的LinearLayout(or other layout)用来装载fragment),你可以使用一个FrameLayout 元素。
1 FragmentTransaction ft = getFragmentManager().beginTransaction(); 2 ft.replace(R.id.your_placehodler, new YourFragment()); 3 ft.commit();
一个新的Fragment将会替换现有的Fragment,这个现有的Fragment就是先前被添加到container里的fragment。
如果你想要把transaction添加到Android的回退栈中,你可以使用addToBackStack()方法。这会使该操作添加到activity的历史栈中,通过返回按钮就可以恢复Fragment的变化。
7. Fragment transition的动画
在fragment的事物期间,可以定义动画,该动画基于Property Animation API来使用,通过调用setCustomAnimations()方法。
也可以通过调用setTransition()方法使用多种Android提供的基础动画。这些通过以FragmentTransaction为首的内容来定义。TRANSIT_FRAGMENT_*.
两种方式都允许你定义一个实体动画和一个现有动画。
8. 将Fragment transition添加到回退栈中
你可以添加一个FragmentTransition到回退栈中以便用户可以使用回退按钮倒退到这个转变。
使用FragmentTransition对象中的addToBackStack来实现。
9. Fragments的后台处理
9.1 无界面的Fragments
使用Fragments,可以不需要用户界面。
实现一个无界面的fragment,只需要在fragment里的onCreateView()方法中返回null即可。
Tip:建议在使用无界面的fragment来处理后台操作时结合setRetainInstance()方法来使用,通过这种方式,在异步操作的时候就无需处理配置变化了(configuration changes)
9.2 无界面fragments处理配置变化
无界面的fragment通常用于封装一些配置变化的状态或者用于一个后台处理任务。因此,你应该设置无界面的fragment处于被保持(retained)的状态。一个被保持的fragment在配置变化期间不会被销毁。
设置fragment为被保持,调用setRetainInsatance()方法即可。
你可以使用FragmentManager类的add()方法来添加这样的fragment到activity中。如果之后你还会查找到该Fragment,你需要给这个fragment添加一个标签tag,这样就可以使用FragmentManager中的findFragmentByTag()方法来查找到它。
Warging:onRetainNonConfigurationInstance()已废弃,should be replaced by retained headless fragments。
10. Fragments 教程
10.1 概览
接下来的教程说明了怎样使用fragment。应用将会根据横屏模式和竖屏模式分别使用带有fragment的不同布局。
在竖屏模式下,RssfeedActivity会显示一个Fragment。在这个fragment中用户可以跳转到另外一个带有fragment的activity。
在横屏模式下,RssfeedActivity会并列的显示两个fragments
1. 创建项目
按照下列内容创建一个新的Android Project。
Table 2. Android project
Property | Value |
---|---|
Application Name | RSS Reader |
Project Name | com.example.android.rssfeed |
Package name | com.example.android.rssfeed |
Template | BlankActivity |
Activity | RssfeedActivity |
Layout | activity_rssfeed |
10.2 创建一个标准的layouts
在res/layout文件夹下创建或者改变以下布局文件。
创建一个新的布局文件:fragment_rssitem_detail.xml.该布局文件被DetailFragment使用。
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <TextView 8 android:id="@+id/detailsText" 9 android:layout_width="wrap_content" 10 android:layout_height="match_parent" 11 android:layout_gravity="center_horizontal|center_vertical" 12 android:layout_marginTop="20dip" 13 android:text="Default Text" 14 android:textAppearance="?android:attr/textAppearanceLarge" 15 android:textSize="30dip" /> 16 17 </LinearLayout>
创建一个新的布局文件:fragment_rsslist_overview.xml,该布局文件用于MyListFragment类。
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <Button 8 android:id="@+id/button1" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:text="Press to update" 12 /> 13 14 </LinearLayout>
修改现有的activity_rssfeed.xml文件。该布局是RssfeedActivity默认的布局文件,她展示两个fragments。
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:orientation="horizontal" > 6 7 <fragment 8 android:id="@+id/listFragment" 9 android:layout_width="0dp" 10 android:layout_weight="1" 11 android:layout_height="match_parent" 12 android:layout_marginTop="?android:attr/actionBarSize" 13 class="com.example.android.rssfeed.MyListFragment" ></fragment> 14 15 <fragment 16 android:id="@+id/detailFragment" 17 android:layout_width="0dp" 18 android:layout_weight="2" 19 android:layout_height="match_parent" 20 class="com.example.android.rssfeed.DetailFragment" > 21 <!-- Preview: layout=@layout/details --> 22 </fragment> 23 24 </LinearLayout>
10.3 创建Fragment类
接下来要创建Fragment类,由DetailFragment类开始
1 package com.example.android.rssfeed; 2 3 import android.app.Fragment; 4 import android.os.Bundle; 5 import android.view.LayoutInflater; 6 import android.view.View; 7 import android.view.ViewGroup; 8 import android.widget.TextView; 9 10 public class DetailFragment extends Fragment { 11 12 @Override 13 public View onCreateView(LayoutInflater inflater, ViewGroup container, 14 Bundle savedInstanceState) { 15 View view = inflater.inflate(R.layout.fragment_rssitem_detail, 16 container, false); 17 return view; 18 } 19 20 public void setText(String item) { 21 TextView view = (TextView) getView().findViewById(R.id.detailsText); 22 view.setText(item); 23 } 24 }
创建MyListFragment类,先不管这个类的名字,他不会像他的命名一样展示一个列表,先用一个Button来代替,点击该按钮会将当前时间传递给details fragemnt。
1 package com.example.android.rssfeed; 2 3 import android.app.Activity; 4 import android.app.Fragment; 5 import android.os.Bundle; 6 import android.view.LayoutInflater; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.Button; 10 11 public class MyListFragment extends Fragment { 12 13 private OnItemSelectedListener listener; 14 15 @Override 16 public View onCreateView(LayoutInflater inflater, ViewGroup container, 17 Bundle savedInstanceState) { 18 View view = inflater.inflate(R.layout.fragment_rsslist_overview, 19 container, false); 20 Button button = (Button) view.findViewById(R.id.button1); 21 button.setOnClickListener(new View.OnClickListener() { 22 @Override 23 public void onClick(View v) { 24 updateDetail(); 25 } 26 }); 27 return view; 28 } 29 30 public interface OnItemSelectedListener { 31 public void onRssItemSelected(String link); 32 } 33 34 @Override 35 public void onAttach(Activity activity) { 36 super.onAttach(activity); 37 if (activity instanceof OnItemSelectedListener) { 38 listener = (OnItemSelectedListener) activity; 39 } else { 40 throw new ClassCastException(activity.toString() 41 + " must implemenet MyListFragment.OnItemSelectedListener"); 42 } 43 } 44 45 46 // May also be triggered from the Activity 47 public void updateDetail() { 48 // create fake data 49 String newTime = String.valueOf(System.currentTimeMillis()); 50 // Send data to Activity 51 listener.onRssItemSelected(newTime); 52 } 53 }
10.4 RssfeedActivity
按照下面的代码来修改RssfeedActivity。
1 package com.example.android.rssfeed; 2 3 import android.os.Bundle; 4 import android.app.Activity; 5 import android.view.Menu; 6 7 public class RssfeedActivity extends Activity implements MyListFragment.OnItemSelectedListener{ 8 9 @Override 10 protected void onCreate(Bundle savedInstanceState) { 11 super.onCreate(savedInstanceState); 12 setContentView(R.layout.activity_rssfeed); 13 } 14 15 // if the wizard generated an onCreateOptionsMenu you can delete 16 // it, not needed for this tutorial 17 18 @Override 19 public void onRssItemSelected(String link) { 20 DetailFragment fragment = (DetailFragment) getFragmentManager() 21 .findFragmentById(R.id.detailFragment); 22 if (fragment != null && fragment.isInLayout()) { 23 fragment.setText(link); 24 } 25 } 26 27 }
10.5 运行
运行该例子。在横屏模式或者竖屏模式都会显示两个fragments。如果点击了ListFragment中的按钮,DetailFragment上的当前时间会被更新。
11. Fragments 教程-竖屏模式的布局
11.1 为竖屏模式创建布局文件
RssfeedActivity在竖屏模式下应该使用特定的布局文件。在竖屏模式Android会检查有没有layout-port文件夹,该文件夹下是否有合适的布局文件。如果Android没有找到符合的布局文件,他就会去使用layout文件夹。
因此我们要创建res/layout-port文件夹。之后在res/layout-port文件夹下创建activity_rssfeed.xml布局文件。
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="horizontal" > 6 7 <fragment 8 android:id="@+id/listFragment" 9 android:layout_width="match_parent" 10 android:layout_height="match_parent" 11 android:layout_marginTop="?android:attr/actionBarSize" 12 class="com.example.android.rssfeed.MyListFragment" /> 13 </LinearLayout>
继续创建activity_detail.xml。该布局文件用户DetailActivity。注意我们本可以在res/layout文件夹下创建该文件,但是它只用于竖屏模式下,因此我们把它放到这个文件夹下(res/layout-port)。
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <fragment 8 android:id="@+id/detailFragment" 9 android:layout_width="match_parent" 10 android:layout_height="match_parent" 11 class="com.example.android.rssfeed.DetailFragment" /> 12 13 </LinearLayout>
11.2 DetailActivity
按照下面的类,重新创建DetailActivity
1 package com.example.android.rssfeed; 2 3 import android.app.Activity; 4 import android.content.res.Configuration; 5 import android.os.Bundle; 6 import android.widget.TextView; 7 8 public class DetailActivity extends Activity { 9 10 public static final String EXTRA_URL = "url"; 11 12 @Override 13 protected void onCreate(Bundle savedInstanceState) { 14 super.onCreate(savedInstanceState); 15 16 // Need to check if Activity has been switched to landscape mode 17 // If yes, finished and go back to the start Activity 18 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 19 finish(); 20 return; 21 } 22 setContentView(R.layout.activity_detail); 23 Bundle extras = getIntent().getExtras(); 24 if (extras != null) { 25 String s = extras.getString(EXTRA_URL); 26 TextView view = (TextView) findViewById(R.id.detailsText); 27 view.setText(s); 28 } 29 } 30 }
11.3 调整RssfeedActivity
调整RssfeedActivity类来显示DetailActivity以防另一个fragment不在该布局内
1 package com.example.android.rssfeed; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.view.Menu; 7 8 public class RssfeedActivity extends Activity implements 9 MyListFragment.OnItemSelectedListener { 10 11 @Override 12 protected void onCreate(Bundle savedInstanceState) { 13 super.onCreate(savedInstanceState); 14 setContentView(R.layout.activity_rssfeed); 15 } 16 17 @Override 18 public void onRssItemSelected(String link) { 19 DetailFragment fragment = (DetailFragment) getFragmentManager() 20 .findFragmentById(R.id.detailFragment); 21 if (fragment != null && fragment.isInLayout()) { 22 fragment.setText(link); 23 } else { 24 Intent intent = new Intent(getApplicationContext(), 25 DetailActivity.class); 26 intent.putExtra(DetailActivity.EXTRA_URL, link); 27 startActivity(intent); 28 29 } 30 } 31 32 }
11.4 运行
运行你的例子。如果在竖屏模式下运行应用,你应该只看到一个Fragment,使用Ctrl+F11快捷键来改变方向。在横向模式下你会看到两个fragments。如果你点击竖屏模式的按钮,新的DetailActivity会被重启,并且显示当前的时间。在横屏模式下两个fragment都会被看到。