当今是移动设备发展非常迅速的时代,不仅手机已经成为了生活必需品,就连平板电脑也变得越来越普及。平板电脑和手机最大的区别就在于屏幕的大小,一般手机屏幕的犬小会在3英寸到 6英寸之间,而一般平板电脑屏幕的大小会在 7 英寸到 10英寸之间。屏幕犬小差距过大有可能会让同样的界面在视觉效果上有较大的差异,比如一些界面在手机上看起来非常美观,但在平板电脑上看起来就可能会有控件被过分拉长、 元素之间空隙过大等情况。作为Android开发人员,能够同时兼顾手机和平板的开发是必须做到的事情。
Android 自 3.0版本开始引人了Fragment的概念, 它可以让界面在平板上更好地展示,下面我们就来一起学习一下。
1、什么是Fragment
碎片 (Fragment) 是一种可以嵌人在活动当中的 UI 片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用得非常广泛。虽然碎片对我们来说应该是个全新的概念, 但相信学习起来应该亳不费力,因为它和Activity实在是太像了,同祥都能包含布局,同样都有自己的生命周期。你甚至可以将碎片理解成一个迷你型的Activity,虽然这个迷你型的Activity有可能和普通的活动是一样大的。
那么究竟如何使用Fragment才能充分的利用平板的空间呢?官网给出了一个很好的例子,一个新闻客户端:
在平板电脑尺寸的设备上运行时,该应用可以在 Activity A 中嵌入两个Fragment。 不过,在手机尺寸的屏幕上,没有足以储存两个Fragment的空间,因此Activity A 只包括用于显示文章列表的Fragment,当用户选择文章时,它会启动Activity B,其中包括用于阅读文章的第二个Fragment。 因此,应用可通过重复使用不同组合的Fragment来同时支持平板电脑和手机。
2、Fragment的使用方式
先写一个最简单的Fragment事例练手,在一个Activity中添加两个Fragment,并让这两个Fragment平分Activity空间。
新建左侧Fragment的布局fragment_left.xml:
<LinearLayout 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"
android:background="#ff00ff"
android:layout_gravity="center_horizontal"
tools:context="com.johnhao.listviewdemo.Fragment.LeftFragment">
<Button
android:id="@+id/btn_left_frag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Button"
android:textColor="#ff00ff"/>
</LinearLayout>
布局很简单,放置一个Button并让它水平居中。然后创建右侧Fragment的布局文件fragment_right.xml:
<LinearLayout 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"
android:background="#00ff00"
tools:context="com.johnhao.listviewdemo.Fragment.RightFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/hello_blank_fragment" />
</LinearLayout>
接着创建一个LeftFragment类,并让它继承Fragment。这里我们建议使用support-v4库中的android.support.v4.app.Fragment。因为这可以使Fragment在所有的Android系统版本中保持功能一致性。另外,我们不需要在build.gradle中添加support-v4库的依赖,因为build.gradle文件中已经添加了appcpmat-v7库的依赖,而这个库会将support-v4库一起引入进来。
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_left, container, false);
}
}
这里仅仅是重写了Fragment的onCreateView()方法,然后方法中通过LayoutInflater的inflate()方法将刚才定义的fragment_left布局动态加载进来。紧接着,我们用同样的方法创建一个RightFragment:
public class RightFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_right, container, false);
}
}
接下来,修改Activity的布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.johnhao.listviewdemo.Activity.FragmentSimActivity">
<fragment
android:id="@+id/left_fragment"
android:name="com.johnhao.listviewdemo.Fragment.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<fragment
android:id="@+id/right_fragment"
android:name="com.johnhao.listviewdemo.Fragment.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
可以看到,我们使用<fragment>标签在布局中添加Fragment,其中指定的属性大多都学习过了。只不过这里还需要童工android:name属性来显式指明要添加的Fragment,这里一定要将类的包名也加上。
重新运行一下程序:
2、动态添加Fragment
刚才的例子过于简单,不能体现出Fragment的强大之处,现在我们学习如何在程序运行时动态的添加Fragment。
我们新建一个Fragment的布局文件,fragment_another.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="#ffff00">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="This is an another Fragment"
android:textSize="30sp"
android:textColor="#000000"/>
</LinearLayout>
这里我们设置了不同颜色的背景、文字和文案,用来和之前的Fragment做视觉上的区分。然后我们新建一个AnotherFragment类:
public class AnotherFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_another, container, false);
}
}
代码内容基本和上面的一致,接下来就是如何将它动态的添加到Activity中。修改布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="com.johnhao.listviewdemo.Activity.FragmentAddActivity">
<fragment
android:id="@+id/left_fragment"
android:name="com.johnhao.listviewdemo.Fragment.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/right_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
可以看到和之前不同的是,我们把右侧Fragment替换成了FrameLayout布局。这种布局默认会把所有控件放在布局的左上角。由于这里仅需要在布局中加入一个Fragment,不需要任何定位,因此非常适合用FrameLayout。
接下来修改Activity代码,向FrameLayout中动态的添加Fragment:
public class FragmentAddActivity extends BaseActivity {
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_add);
btn = findViewById(R.id.btn_left_frag);
replaceFragment(new RightFragment());
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
replaceFragment(new AnotherFragment());
}
});
}
private void replaceFragment(Fragment fragment){
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.right_layout, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
}
首先我们给左侧布局中Button注册一个监听器,然后调用了replaceFragment()方法动态添加了RightFragment。当点击左侧Fragment中的Button时候,又会调用replaceFragment()方法将右侧Fragment替换成AnotherFragment。从代码中可以看出,动态添加Fragment主要分为5步:
1)创建待添加的Fragment实例
2)获取FragmentManager,在support-v4中是通过调用getSupportFragmentManager()方法获得。
3)开启一个事务,通过调用beginTransaction()方法开启
4)向容器内添加或者替换Fragment,一般通过调用replace()方法实现,需要传入容器的id和待添加的Fragment
5)提交事务,调用commit()方法来完成。
除此之外,我们还额外添加了一行代码addToBackStack,这个方法是FragmentTransaction中提供的用于将一个事务添加到返回栈中的方法。如果没有这行,当我们点击back的时候,直接就会退出Activity。而调用addToBackStack()方法后,按下back键之后,Activity并不会退出,而是退回到了RightFragment。然后按一下back键,RightFragment也会消失。最后再按一下back键,Activity才会退出。
重新运行下程序:
3、Fragment和Activity之间进行通信
虽然Fragment是嵌入在Activity中显示的,可实际上他们的关系并没有那么紧密。Fragment和Activity都是各自存在于一个独立的类中,为了方便Fragment和Activity进行通信,FragmentManager提供一个类似于findViewById()的方法,专门用于从布局文件中获取Fragment的实例:
RightFragment rightFragment = (RightFragment)getSupportFragmentManager().findFragmentById(R.id.right_fragment);
调用FragmentManager的findFragmentById()方法,可以在Activity中得到相应Fragment的实例,然后就能轻松的调用Fragment里的方法了。
相反的,在Fragment中可以通过调用getActivity()方法来得到和当前Fragment相关联的Activity的实例:
FragmentAddActivity activity = (FragmentAddActivity) getActivity();
有了活动实例,在Fragment中调用Activity里的方法也就变得容易了。当然Fragment和Activity之间的通信还有其他几种方式实现,这些我们日后再慢慢学习,这里只做个抛砖引玉。
4、Fragment的生命周期
管理Fragment生命周期与管理 Activity 生命周期很相似。和 Activity 一样,Fragment也以三种状态存在:
□ 运行状态:
当一个Fragment是可见的,并且他所关联的Activity正处于运行状态是,该Fragment也处于运行状态。
□ 暂停状态:
当一个Activity进入暂停状态是,与它关联的Fragment就会进入到暂停状态。
□ 停止状态:
当一个Activity进入停止状态是,与它关联的Fragment就会进入停止状态,或者通过调用FragmentTransaction的remove()、replace()方法将Fragment从Activity中移除,但如果在提交事物之前调用addToBackStack()方法,这时的Fragment也会进入到停止状态。
图片为Activity生命周期对Fragment生命周期的影响。那么接下来就了解下Fragment的生命周期:
还是从代码入手,通过打印Log的方式,更加直观的体验Fragment的生命周期。我们在动态添加Fragment的练习中修改,修改RightFragment类:
public class RightFragment extends Fragment{
private static final String TAG = "RightFragment";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.d(TAG, "onCreateView: ");
return inflater.inflate(R.layout.fragment_right, container, false);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(TAG, "onAttach: ");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate: ");
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, "onActivityCreated: ");
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart: ");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView: ");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
@Override
public void onDetach() {
super.onDetach();
Log.d(TAG, "onDetach: ");
}
}
我们在Fragment的每一个回调方法里都添加了LOG,重新运行程序,观察logcat的日志:
可以看到,当RightFragment第一次被加载到屏幕上时,会依次执行onAttach()、onCreate()、onCreateView()、onActivityCreated()、onStart()、onResume()方法。
然后点击左侧LeftFragment中的按钮,加载AnotherFragment,由于AnotherFragment替换掉了RightFragment,此时RightFragment进入了暂停状态,因此onPause()、onStop()、onDestroyView()方法得到了执行。当然,如果在提交事物之前没有调用addTBackStack()方法的话,RightFragment就会进入到destroy状态。
由于代码中我们调用了addTBackStack()方法,点击back键从AnotherFragment回到RightFragment。由于RightFragment重新回到了运行状态,此时onAttach()、onCreate()方法不会被执行,因为我们借助了addTBackStack()方法使得RightFragment没有被销毁。
再次按下back键,我们会看到RightFragment进入了销毁阶段,依次执行了onPause()、onStop()、onDestroyView()、onDestroy()、onDetach()方法。
结合前面的图,是不是对Fragment的生命周期理解的更深刻了呢。
另外值得一提的是,在Fragment中也可以通过onSavedInstanceState()方法来保存数据,因为进入stop状态的Fragment可能会在内部不足时被系统回收。保存下来的数据在onCreate()、onActivityCreated()、onCreateView()方法中可以重新得到,它们都包含有Bundle类型的savedInstanceState参数。
关注获取更多
网友评论