Fragment使用小结

本文详细介绍了在Android开发中如何使用Fragment,包括动态和静态方式的实现步骤,以及Fragment与Activity之间的信息传递机制。通过示例代码演示了如何在Activity中添加和管理Fragment,以及如何在Fragment间传递数据。

△概述

→Fragment是从安卓的3.0开始有的,主要针对大屏幕的设备进行工作(例如平板)。

→Fragment中文翻译“片段,碎片”,"Fragment"确实是作为"Activity"的一个片段存在的,你可以将他理解为一个模块,或者"Activity"里的"Activity",如果将"Activity"当做是房子,"Fragment"就是里面一个房间。

→Fragment有他自己的生命周期,他有自己一个布局,能够接受和处理自己的输入事件,能在"Activity"运行时对"ragment"进行添加、删除等操作。

→Fragment必须嵌入到"Activity"里运行(就像房间必须放到房子里面),他的生命周期受到"Activity"周期影响(房间总受房子影响),比较典型的影响效果如:

(1)当“Activity"被pause了(即回调了"onPause()"方法),他里面的所有Fragment也被pause。

(2)当"Activity"被stop(即回调了"onStop()"方法),他里面的"Fragment"也全都会stop。

(3)当"Activity"被销毁了(即回调了"onDestroy()"方法),他里面的"Fragment"也全都会被销毁掉。

→虽然"Fragment"在一定程度上受到"Activity"影响,但是当"Activity"处于resume状态(即回调了“onResume()”方法),这个时候可以很自由地去对"Fragment"进行添加、移除、更替等等不同操作。

→一个"Activity"可以装填多个的"Fragment",同个"Fragment"的不同实例也能存在不同的"Activity"里面。

→刚才说了fragment有自己的布局文件,虽然如此,不过你也可以建立一个没有布局的fragment,此时,这个fragment只是为了完成某个逻辑功能,不需要和用户交互。

总的来说,"Fragment"就像一个更轻巧的"Activity",在你学习"Fragment"时,很多地方将他和"Activity"进行对比,将有助于更好理解以及使用"Fragment"。

下文,将分别讨论动态、静态方式来使用"Fragment",以及动静态方式下"Fragment"与"Activity"进行通信,先来看一下效果图:


最外层的就是一个"Activity"布局,里面放个"Fragment",另外我可以将背景颜色改了,就是为了能够明显看出"Fragment"到底加在到哪去了,我们将分别以静态和动态的方式完成这个例子。


△静态方式使用Fragment

主要包括如下步骤:

(1)创建一个类并继承系统"Fragment"类。

(2)定义一个布局文件,作为这个"Fragment"布局文件。

(3)在"Fragment"的"onCreateView()"方法里面,绘制并且返回你的布局文件。

(4)在"Activity"布局文件里,使用<fragment>标签添加你自己的"Fragment"。

回想一下"Activity"使用方式,其实前面的那几步都很类似,只是最后那步不同,因为"Fragment"必须加载到"Fragment"里面。下面我们一步一步展开来讲。

→创建一个类并继承系统"Fragment"类:

自定义类的名字是"TestFragment",像这样子:
public class TestFragment extends Fragment{

}
覆写生命周期方法的代码稍后再补上。

→定义一个布局文件,作为"Fragment"布局文件:

该布局文件的名字:fragment_test,像这样子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_light">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TestFragment"
        android:gravity="center"
        android:textSize="24sp"/>
</LinearLayout>
非常简单一个布局文件,只有一个“TextView”,另外我将线性布局背景颜色改了,方便识别。

→在"Fragment"的"onCreateView()"方法里面,绘制并且返回你的布局文件:

对比"Activity"我们覆写"onCreate()"方法,而"Fragment"则是使用"onCreateView()"方法返回它的布局,当然"Fragment"也有"onCreate()"方法,这个将在后文论述,先来看看"onCreateView()"方法,像这样子:
public class TestFragment extends Fragment{

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_test,container,false);
    }
}
相比第一步的代码,只是多了一个方法回调,其中"inflater"参数是"onCreateView()"方法里面传进来的,然后调用"inflate()"方法绘制布局文件,这个方法接受几个参数,这里分别解释一下:
R.layout.fragment_test:很显然的,这个就是要绘制的布局文件,只要传入文件名就行了。
container:"Fragment"所在的父容器控件,这个参数同样也是由"onCreateView()"方法里面所传来的。
false:是否需要将被绘制布局文件挂载到父容器里面,简单地说就是是否要将"Fragment"布局文件挂载到container里,在这里必须传入false,因为系统会自动将"Fragment"挂载到父容器里面,如果这里传入true,就会导致重复挂载。

→在"Activity"布局文件里,使用<fragment>标签添加你自己的"Fragment":

现在我们将有一个"MainActivity",其对应的布局文件叫做"activity_main",布局文件代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent" android:id="@+id/ll_activity_main">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="我是MainActivity"
        android:textSize="24sp"
        android:gravity="center_horizontal"/>
    <fragment
        android:name="com.jf.fragmenttest.TestFragment"
        android:id="@+id/first_Fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
里面只有两个控件,"TextView"是方便我们来识别"MainActivity",重点在于<fragment>标签,他里面的两个属性比较重要:
android:name:这个属性用来标识<fragment>标签使用的是哪个"Fragment"类,这里需要使用完整类名,就是需要写出包名。
android:id:添加id属性以后,你才能在"Fragment"所在宿主"Activity"里面使用"fragmentManager.findFragmentById()"方法找到"Fragment"实例。如果不加id属性,当你需要动态操作"Fragment"时,没有办法能够操作。当然你也可以添加"android:tag"属性,这个我们稍后再说。

这里需要补充一点说明的是,"Fragment"是在Android3.0之后才推出的,所以对于以前版本如果需要使用的话,就要添加兼容包,所以其实有两个包里面都有"Fragment":
android.app.Fragment;
android.support.v4.app.Fragment;
第二个是兼容包的,这里需要注意的是,如果使用兼容包的Fragment,那么你的"Activity"不能继承"Activity"类,必须继承"FragmentActivity"类,不然的话你的应用没法运行。


到此,所有步骤都完成了,"MainActivity"里的代码就不贴了,什么逻辑都没有的,只要调用"setContentView()"设置布局文件即可。前面图片所显示的那个例子就已经能正常运行。静态方式使用"Fragment"已经完成。

△动态方式使用"Fragment"

动态方式来使用"Fragment"同样包括几个步骤,和静态方式相比较,在创建"Fragment"方面,步骤基本是一样的,只是不再去使用"<fragment>"标签,而是使用"FragmentManager"对象进行"Fragment"添加以及其他操作。具体步骤如下所示:
(1)创建一个类并继承"Fragment"。

(2)定义一个布局文件,作为这个"Fragment"布局文件。

(3)在"Fragment"的"onCreateView()"方法里面,绘制并且返回你的布局文件。

(4)在"Activity"里,使用"getFragmentManager()"方法获得"Fragment"的管理器,通过管理起来获取事务(FragmentTransaction对象),通过事务对"Fragment"做动态的一些操作。

虽然前面几步代码是一样的,不过我们还是一步一步来吧。

→创建一个类并继承"Fragment":

类名还是"TestFragment",像这样子:
public class TestFragment extends Fragment{

}

→定义一个布局文件,作为这个"Fragment"布局文件:

布局文件的名字是"fragment_test",代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_light">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TestFragment"
        android:gravity="center"
        android:textSize="24sp"/>
</LinearLayout>


→在"Fragment"的"onCreateView()"方法里面,绘制并且返回你的布局文件:

public class TestFragment extends Fragment{

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_test,container,false);
    }
}
inflate()方法里面各个参数作用,已在前文作出解释,这里就不再重复了。

→在"Activity"里,使用"getFragmentManager()"方法获得"Fragment"的管理器,通过管理起来获取事务(FragmentTransaction对象),通过事务对"Fragment"做动态的一些操作。

宿主"Activity"的名字是"MainActivity",我们先来看看其对应的布局文件里的代码,相比静态添加方式,有点变化,文件名字:activity_main,像这样子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent" android:id="@+id/ll_activity_main">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="我是MainActivity"
        android:textSize="24sp"
        android:gravity="center_horizontal"/>
</LinearLayout>
相比静态方式添加,这里去掉了<fragment>标签,因为我们使用的是动态加载,所以不再去添加<fragment>标签,注意我在线性布局哪里添加id属性,这个将在动态添加"Fragment"时会用到。
接下来是"MainActivity"里的代码,像这样子:
public class MainActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        通过"getFragmentManager()"方法获取"Fragment"管理器        
        FragmentManager fragmentManager = getFragmentManager();
//        通过"Fragment"管理器来获取事务对象
        FragmentTransaction transaction = fragmentManager.beginTransaction();
//        通过事务里的方法对"Fragment"进行动态的操作
        transaction.add(R.id.ll_activity_main,new TestFragment());
//        操作完成之后记得提交,否则的话所有操作无效
        transaction.commit();
    }
}
可以看到,动态添加"Fragment",都是通过"Fragment"事务完成,其实不但动态添加,就算更替或者移除,都是通过事务来完成的,这里说下"fragmentTransaction.add()"方法里的两个参数:
R.id.ll_activity_main:每个"Fragment"实例都必须被放入一个布局容器里面,需要提供这个布局容器的id值,在本例中,提供的是"Activity"布局文件最外层布局的id。
第二个参数就是我们的"Fragment"对象实例,这个不用多说了吧。

同样还是需要补充一点,和前文所论述的类似的,如果你是用兼容包里的"Fragment",那么如下是需要修改的:
(1)继承"FragmentActivity"而不是"Activity"
(2)调用"getSupportFragmentManager()"而不是"getFragmentManager()"
其他那些基本都是一致的啦。

△Fragment与Activity传信息

Activity发消息到Fragment

我们通过一个例子来看"Activity"发消息给"Fragment",先来看图:

说明一下,整个总体是个"Activity",然后上面"EditText"以及"Button"都是直接放置在"Activity"布局文件里的控件,蓝色部分则是被放置在"Activity"里的"Fragment"。
这个小例子的功能就是,在文本输入框里面输入内容,点击按钮,就可以将内容发送到"Fragment"里面,"Fragment"收到内容之后通过他自己的"TextView"显示出来。
不论静态还是动态添加"Fragment",从"Activity"发消息到"Fragment"的方法都是一样的。步骤如下:
(1)先在Fragment里面定义共有方法。
(2)Activity获取Fragment实例。然后直接调用Fragment里的共有方法即可。
唯一不同的地方是,静态和动态加载的Fragment,获取方式是不同的。
现在我们一点一点来看,先来看看Fragment布局文件里的内容,文件名字:fragment_test,像这样子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_light">

    <TextView
        android:id="@+id/tv_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="显示来自Activity内容"
        android:textSize="24sp"/>
</LinearLayout>
内容非常地简单,只有一个"TextView",用来显示从"Activity"那里接收到的内容。
接着看看"Fragment"里的代码,"Fragment"类,类名"TestFragment",这里也是我们所说两个步骤的第一步:在Fragment里面定义共有方法,像这样子:
public class TestFragment extends Fragment{

    private TextView messageTv;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//        绘制对应布局文件
        View rootView = inflater.inflate(R.layout.fragment_test,container,false);
//        获取布局文件里的"TextView"
        messageTv = (TextView)rootView.findViewById(R.id.tv_message);
        return rootView;//返回所绘制的布局文件对象
    }

    /*
    定义一个公有方法,
    用来接收来自"Activity"的数据
     */
    public void sendToFragment(String message){
        messageTv.setText(message);
    }
}
注意下面那个方法,"sendToFragment()"这个方法被"Fragment"所在"Activity"所调用,接收来自"Activity"里的信息,收到之后就会调用textview.setText()方法显示所收到的数据。
现在我们来看一下"Activity"布局文件里的代码,文件名字:activity_main。像这样子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent" android:id="@+id/ll_activity_main">
<!--EditText用来输入要发送的内容-->
    <EditText
        android:id="@+id/et_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="发给Fragment内容"/>
    <!-- 发送按钮-->
    <Button
        android:id="@+id/btn_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送"/>
    <!-- 静态加载Fragment-->
    <fragment
        android:id="@+id/test_fragment"
        android:name="com.jf.fragmenttest.TestFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
这里一共三个控件,一个就是文本输入框了,还有一个就是发送按钮,另外一个就是我们的主角啦——静态添加的"Fragment"。
现在看看宿主"Activity"里的代码,重点就看怎么发送消息,这里也是我们所说两个步骤的第二步,获取"Fragment"实例,调用共有方法发送消息,"Activity"名字:MainActivity,像这样子:
public class MainActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        使用"findFragmentById()"获取静态加载的Fragment实例
        final TestFragment testFragment = (TestFragment)getFragmentManager().findFragmentById(R.id.test_fragment);
//        获取EditText
        final EditText contentEt = (EditText)findViewById(R.id.et_content);
//        获取按钮对象并设置监听器
        findViewById(R.id.btn_send).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                获取要发送的内容
                String content = contentEt.getText().toString();
//                如果内容不为空则发送
                if(!content.equals("")){
                    testFragment.sendToFragment(content);//这里就是调用共有方法发送消息
                }
            }
        });
    }
}
我们看到,一共做了几件事情:获取各个控件对象,包括"EditText"对象,"Button"对象,当然还有要通过"fragmentManager.findFragmentById()"方法获取"Fragment"实例对象。然后再为按钮设置监听事件,当按下时,判断一下文本框里有没数据,如果用户有输入,直接调用"TestFragment"所提供的公有方法,就能将信息发过去。

这样就已经完成了"Activity"→"Fragment"发消息的一个过程,本质上就是"Activity"调用"Fragment"里的公有方法发送消息,然后再"Fragment"方法里面自己决定怎么处理。
对于动态加载的"Fragment",由于"Fragment"对象是直接new出来,所以不用"findFrgmentById()",只要在new那时候留住对象的引用即可,随时随地可以发送消息。

Fragment发消息到Avtivity

Fragment发消息给Activity(Fragment调用Activity里的方法),使用的是回调机制,不论静态还是动态加载Fragment,通信方式都一样的,他的具体步骤如下:
1,在Fragment里定义一个接口(interface),接口里面定义方法,这些方法将会直接决定"Fragment"具体可以发送什么给"Activity"。
2,Activity实现这个接口,并且实现接口里的方法。
3,Fragment在onAttach(),方法里面,获取宿主"Activity",然后调用接口里的方法即可。
同样我们也做个小例子,之前我们将文本输入框放在"Activity"里面,现在将他放置在"Fragment"里面。来看效果。

现在先来说明一下,整个界面就是一个"MainActivity",白色部分"TextView"用来显示从"Fragment"接受的内容,蓝色部分是加载在"Activity"里的"Fragment"。我们在"Fragment"的"EditText"里面输入要发送的内容,点击按钮,内容就会发送到"Activity"里面,并由"TextView"显示出来。
我们先来看看每个步骤所对应的代码片段,然后再看完整代码。
(1)在"Fragment"里面定义一个接口,接口里面定义一个方法,我们来定义一个名字叫"TestFragmentListener"的接口,像这样子:
/**
 * 定义一个回调接口,接口里面定义一个方法,
 * 该接口由宿主"Activity"实现,并且实现这个方法
 */
public interface TestFragmentListener{
    public void editTextContent(String message);
}
注意这段代码是在"TestFragment"类里面的,这段代码就表示了"Fragment" 及其对应的宿主"Activity"通信接口。
(2)Activity实现这个接口,并且实现接口里的方法。像这样子:
public class MainActivity extends Activity implements TestFragment.TestFragmentListener{
//<span style="white-space:pre">	</span>实现接口里的方法
    @Override
    public void editTextContent(String message) {
        textView.setText(message);//调用textView.setText()显示接收到的信息
    }
}
可以看到,这和普通Java实现接口以及重写方法,基本没有什么区别。
(3)Fragment在onAttach(),方法里面,获取宿主"Activity",然后调用接口里的方法即可。
public class TestFragment extends Fragment{

//<span style="white-space:pre">	</span>定义一个成员变量,用来指向宿主"Activity"
    private TestFragmentListener testFragmentListener;
//<span style="white-space:pre">	</span>在onAttach()方法里面获取并且转型"Activity"
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        testFragmentListener = (TestFragmentListener)activity;
    }
}
这里需要注意的是记得进行类型转变,好了关键的代码看完了,现在看看完整代码。
首先,"Fragment"类对应的布局文件,文件名字:fragment_test,像这样子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_light">
<!--这是用户输入文本用的-->
    <EditText
        android:id="@+id/et_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入文本发送到Activity里"
        android:textSize="24sp"
        android:textColorHint="@android:color/white"/>
    <!-- 发送按钮-->
    <Button
        android:id="@+id/btn_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点我发送"/>
</LinearLayout>

可以看到,就只放了两个控件,"EditText"用来接受用户输入,"Button"用来发送消息到"Activity"。

然后,"TestFragment"类的代码,像这样子:

public class TestFragment extends Fragment{

    private EditText contentEt;//这是用户文本输入控件
    private Button sendBtn;//这个指向发送按钮
    private TestFragmentListener testFragmentListener;//这个是自定义接口

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//        绘制对应布局文件
        View rootView = inflater.inflate(R.layout.fragment_test,container,false);
//        找到相对应的控件
        contentEt = (EditText)rootView.findViewById(R.id.et_content);
        sendBtn = (Button)rootView.findViewById(R.id.btn_send);
//        设置按钮的监听器
        sendBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                获取"EditText"里的内容
                String content = contentEt.getText().toString();
//                 如果内容不为空则发送消息
                if (!content.equals("")) {
//                    调用接口里的方法发送消息
                    testFragmentListener.editTextContent(content);
                }
            }
        });
        return rootView;
    }

    /**
     * 定义一个回调接口,接口里面定义一个方法,
     * 该接口由宿主"Activity"实现,并且实现这个方法
     */
    public interface TestFragmentListener{
        public void editTextContent(String message);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        testFragmentListener = (TestFragmentListener)activity;
    }

}
这里主要逻辑如下:定义一个回调接口,接口里面定义通信方法,在onAttach()方法里面获取宿主"Activity",在点击了按钮之后调用接口里的方法,发送消息。

然后到了"Activity"布局文件,文件名字:activity_main,像这样子:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent" android:id="@+id/ll_activity_main">
    <!--用来显示Fragment发的内容-->
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="接收来自Fragment里的内容"
        android:textSize="24sp"
        android:gravity="center_horizontal"/>

    <!-- 静态加载Fragment-->
    <fragment
        android:id="@+id/test_fragment"
        android:name="com.jf.fragmenttest.TestFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
这里只有两个控件,TextView用来显示Fragment发的内容,然后就是静态添加的"Fragment"。

接下来是宿主"Activity"里的内容,类名叫做:MainActivity,像这样子:

public class MainActivity extends Activity implements TestFragment.TestFragmentListener{

    private TextView textView;//显示消息用的"TextView"
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView)findViewById(R.id.tv_content);
    }
    
//    实现Fragment所定义的接口里的方法
    @Override
    public void editTextContent(String message) {
//        将"Fragment"发过来的消息显示在"TextView"里面
        textView.setText(message);
    }

    
}
这里就是实现接口以及实现接口里的方法,然后收到消息之后,就显示在自己的"TextView"里面。

到此,从"Fragment"发消息到"Activity"也介绍完。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值