A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 张老师 黑马粉丝团   /  2016-12-6 16:06  /  2065 人查看  /  2 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 就业部_安卓组 于 2016-12-6 16:06 编辑

        上节讲了一些关于静态和动态使用 Fragment 的用法,这节我们讲一讲 Fragment 与 Activity 和 Fragment 之间的通信、Fragment 回退栈的一些用法。

Fragment 回退栈

       我们都知道这样一种情形,比如有两个 Activity : Activity 1和 Activity 2,如果我们从 Activity 1 的界面跳到 Activity 2,然后这个时候按 Back (返回)键,会发现回到了 Activity 1界面,而再按一次 Back 键会退出整个应用。

       这是因为每开启一个 Activity 系统都会将它压到任务栈里面去,俗称“压栈”。而之后再按 Back 键的时候会一个个的出栈,那 Fragment 是不是也类似呢?
       假设现在我们有两个 Fragment:Fragment 1 和 Fragment 2,我们现在从 Fragment 1 的界面跳到 Fragment 2 ,然后按 Back 键,发现程序是直接退出了,而不是返回到 Fragment 1 。那如果现在想实现类似 Activity 的那种后退栈的功能:从 Fragment 1 的界面跳到 Fragment 2 ,然后按 Back 键,会返回到 Fragment 1 。这个功能该怎么实现呢?这其实就利用到了返回栈的知识。

       其实很简单,FragmentTransaction中提供了一个addToBackStack()方法,可以将一个事务添加到返回栈中。
       我们先回顾一下之前动态加载 Fragment 的代码,然后在此基础之上,增加一行代码就可以将Fragment添加到返回栈中:
[Java] 纯文本查看 复制代码
        //步骤一:添加一个FragmentTransaction的实例    
        FragmentManager fm = getFragmentManager();

        // 开启事务
        FragmentTransaction transaction = fm.beginTransaction();

        //步骤二:用add()方法加上Fragment的对象
        TestFragment mTestFragment = new TestFragment();
        transaction.add(R.id.fl_content, mTestFragment);
        transaction.addToBackStack(null);

        //步骤三:调用commit()方法使得FragmentTransaction实例的改变生效
        transaction.commit();
       就是这么简单,我们在事务提交之前调用了 FragmentTransaction 的 addToBackStack() 方法,它可以接受一个名字用于描述返回栈的状态,一般传入null即可。

       比如说我们现在有一个需求:要求有三个界面,从第一个界面点击按钮能进入第二个界面,从第二个界面点击按钮能进入第三个界面,然后按 Back 键的时候,依次退出。
       拿到这个需求之后我们分析一下,需求很简单,就是三个界面跳转,用三个 Activity 可以轻松实现,但如果我们不用 Activity ,改成用 Fragment 呢?这该怎么实现呢?

       也许你会想,用三个 Fragment 然后每一个 Fragment 都有一个布局,布局就是一个按钮,按钮可以点击,点击的就替换当前显示的 Fragment,这样就可以实现了。不过这样只能实现一半,也就是说,当我点击按钮的时候的确可以实现界面上的跳转,但是当我按 Back 键的时候就直接退出了。因为这三个 Fragment 都是嵌套在同一个 Activity 上的,当我点击了 Back 键的时候,退出的是整个 Activity ,而一旦 Activity 销毁了,那依附在其之上的 Fragment 也都统统销毁了,那我们该怎么办呢?

       想想前面讲的回退栈,我们首先要知道为什么 Activity 就能一个一个的退出呢?

       这是因为 Activity 有一个任务栈,当创建了一个 Activity 的时候就会有一个 Activity 实例被压栈,而每次点击 Back 键的时候就会有一个 Activity 被弹栈,因此 Activity 由于任务栈的结构能轻松实现上述需求。而我们要模拟的就是 Activity 任务栈,名为 Fragment 回退栈。简而言之就是模拟一个任务栈,每次 new 出来一个新的 Fragment 并要替换的时候将它加入到回退栈中,这样以后点击 Back 键的时候就会出现 Activity 退出的效果。说了这么多文字,上一下代码。

首先是 MainActivity 的 xml 布局:
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.mesmerize.fragmentstacktest.MainActivity">

    <FrameLayout
        android:id="@+id/fl_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></FrameLayout>
</RelativeLayout>

这三个 Fragment 统一显示在 FrameLayout 中。
MainActivity.java 的代码如下:
[Java] 纯文本查看 复制代码
package com.mesmerize.fragmentstacktest;

import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentManager fragmentManager = getFragmentManager();

        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.fl_content, new FragmentTestFirst());

        fragmentTransaction.commit();
    }
}
上述代码很简单,就是将第一个 Fragment(FragmentTestFirst)添加到布局中用于展示。
接下来看看 FragmentTestFirst 的代码。首先是其 xml 布局文件。
fragment_first.xml 如下:
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#f0f000"
    android:layout_height="match_parent">

    <TextView
        android:layout_marginTop="100dp"
        android:layout_centerHorizontal="true"
        android:text="我是第一个 Fragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:layout_centerInParent="true"
        android:id="@+id/btn_first"
        android:text="点击跳转第二个 Fragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>
显示效果如下:


FragmentTestFirst.java
[Java] 纯文本查看 复制代码
package com.mesmerize.fragmentstacktest;

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

/**
 * Created by mesmerize on 2016/12/6.
 */

public class FragmentTestFirst extends Fragment implements View.OnClickListener {

    private Button btn_first;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_first, null);

        btn_first = (Button) view.findViewById(R.id.btn_first);
        btn_first.setOnClickListener(this);

        return view;
    }


    @Override
    public void onClick(View v) {

        FragmentTestSecond fragmentTestSecond = new FragmentTestSecond();

        FragmentManager fragmentManager = getFragmentManager();

        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.fl_content, fragmentTestSecond);
        fragmentTransaction.addToBackStack(null);

        fragmentTransaction.commit();
    }
}
       这段代码,首先在 onCreateView 里面填充 layout 布局 fragment_first ,然后用其填充的 view 查找 Button 这个按钮并且给其设置点击事件。在点击事件里面做了如下处理:
        1.创建下一个要显示的 Fragment 实例,即 FragmentTestSecond
        2.开启事务,replace并且将其加入到回退栈里面,addToBackStack;
        3.提交
       这是第一个 Fragment 所要处理的事情,我们按照此逻辑快速的写出 FragmentTestSecond 以及 FragmentTestThird。
FragmentTestSecond.java
[Java] 纯文本查看 复制代码
package com.mesmerize.fragmentstacktest;

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

/**
 * Created by mesmerize on 2016/12/6.
 */

public class FragmentTestSecond extends Fragment implements View.OnClickListener {

    private Button btn_second;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_second, null);

        btn_second = (Button) view.findViewById(R.id.btn_second);
        btn_second.setOnClickListener(this);

        return view;
    }


    @Override
    public void onClick(View v) {

        FragmentTestThird fragmentTestSecond = new FragmentTestThird();

        FragmentManager fragmentManager = getFragmentManager();

        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.fl_content, fragmentTestSecond);
        fragmentTransaction.addToBackStack(null);

        fragmentTransaction.commit();
    }
}

fragment_second.xml
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#f246f7"
    android:layout_height="match_parent">

    <TextView
        android:layout_marginTop="100dp"
        android:layout_centerHorizontal="true"
        android:text="我是第二个 Fragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:layout_centerInParent="true"
        android:id="@+id/btn_second"
        android:text="点击跳转第三个 Fragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>


FragmentTestThird.java
[Java] 纯文本查看 复制代码
package com.mesmerize.fragmentstacktest;

import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

/**
 * Created by mesmerize on 2016/12/6.
 */

public class FragmentTestThird extends Fragment implements View.OnClickListener {


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_third, null);

        TextView tv_third = (TextView) view.findViewById(R.id.tv_third);

        tv_third.setOnClickListener(this);

        return view;
    }


    @Override
    public void onClick(View v) {

        Toast.makeText(getActivity(),"我是第三个 Fragment",Toast.LENGTH_SHORT).show();
    }

}
fragment_third.xml
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#f23231"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_third"
        android:layout_marginTop="100dp"
        android:layout_centerHorizontal="true"
        android:text="我是第三个 Fragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>


       在第三个 Fragment 中,点击文字会弹出一段话,话的内容是“我是第三个 Fragment”。
       接下来写完这些代码之后,赶紧运行一下测试一下效果,看看是否完成了需求。
       首先,运行之后:



        然后点击按钮:



        然后继续点击按钮:




        这个时候点击文字:




       目前来看是没有什么问题的,我们点一下后退键,发现点一下之后回退到 FragmentTestSecond 的界面,然后再次点击回到 FragmentTestFirst 的界面,再次后退才会退出整个应用。
        而如果我们不加入到回退栈的话,点击一次后退键就直接退出了整个应用。
       这就是 Fragment 的回退栈,看到这里,赶紧去试一下吧!


Fragment 与 Activity 通信

       尽管我们的 Fragment 都是要依附于 Activity 显示,但其实他们的关系并不是那么的亲密。我们发现他们都有各自独立的类,如果我们想再 Activity 中调用 Fragment 里面的方法,或者 Fragment 里面调用 Activity 的 方法,该如何做呢?
       其实,为了方便它们之间的通信, FragmentManager 已经提供了相应的方法,专门用来获取 Fragment 的实例,代码如下:
[Java] 纯文本查看 复制代码
TestFragment fragment = (TestFragment) getFragmentManager().findFragmentById(R.id.test_fragment);


        我们可以通过 FragmentManager 的 findFragmentById() 的这个方法,轻松在 Activity 中得到相应的 Fragment 的实例,对象都拿到了,这样不就可以调用 Fragment 的方法了吗?
        是不是很简单?
        既然 Activity 调用 Fragment 的方法我们已经知道了,那么 Fragment 又该怎么来调用 Activity 中的方法呢?其实查看上面的代码,我们在 Fragment 回退栈那一小节中的第三个例子里,用到了一个 API 叫 getActivity()。其实每个 Fragment 都会依附于一个 Activity 之上,所以每个 Fragment 都可以通过调用 getActivity() 的方法来得到当前所依附的 Activity 的实例,举例:
[Java] 纯文本查看 复制代码
MainActivity mainActivity = (MainActivity) getActivity();

       同理,我们拿到了 Activity 的实例,拿想调用里面的方法就变得简单了。在这里我要提一点,如果说在 Fragment 中需要 Context,我们可以通过 getActivity(),但是如果这个 Context 需要在 Activity 被销毁之后依然存在,那么我们就不能使用 getActivity,这个时候我们应该使用 getActivity().getApplicationContext()。
      
       也许你会有疑问,那 Fragment 和 Fragment 可以通信吗?
        Fragment 和 Activity 可以通过 getActivity() 以及 findFragmentById() 或者 findFragmentByTag() 进行任何操作,但是 Fragment 里面操作另外的 Fragment 可以实现吗?对于这个问题,答案其实是可以进行通信的,我们可以在 Fragment 中拿到另一个 Fragment 所依附的 Activity ,然后通过此 Activity 去获取另一个 Fragment 的实例,这样就可以实现 Fragment 与 Fragment 之间的通信了。虽然可以实现,但是没有特殊需要是不提倡的。因为 Activity 的角色就是 Fragment 之间的一个总线,一个协调管理的角色,应该是它来决定 Fragment 应该做什么不应该做什么。虽然 Fragment 不能响应 Intent 打开,但是 Activity 可以,Activity 可以接收 Intent,然后根据参数判断显示哪个 Fragment。





       Android 人事+技术总贴
       Android 基础篇总贴
       Android 进阶篇总贴
       Android 讨论区

       以上言论,如有错误或者表达不准确或者有纰漏的地方还请指正,同时欢迎大家在评论区留言给予一定的建议,我会根据大家的意见进行相应的改正,谢谢各位!





2 个回复

倒序浏览
966666066666
回复 使用道具 举报

6 翻了
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马