Android - Fragment

Fragment

  1. Fragment,碎片,是 Android 3.0 开始引入的,其目的在于同时兼容手机和平板的开发,能让程序更合理地利用大屏幕的空间;
  2. Fragment 是可以嵌入到 Activity 中的 UI 片段,也必须嵌入到 Activity 中,它可以包含布局,也有自己的生命周期(其生命周期受到其宿主 Activity 的影响),是一个轻量型的 Activity;
  3. 一个 Activity 中可以包含多个 Fragment,一个 Fragment 也可以在多个 Activity 中复用;
  4. 下图取自官方文档,很好地解释了 Fragment 的应用:


    Android - Fragment_第1张图片

Fragment 的使用

Fragment 有两种使用方式,一种是是静态添加到 Activity 中,另一种是动态添加 Activity 中。

静态添加

  1. 首先在一个 Activity 中添加两个 Fragment,这两个 Fragment 是水平分布,首先是左侧的 Fragment 布局文件,fragment_left.xml:



    

我们将左侧的 Fragment 背景颜色设为蓝色,在里面放了一个 Button,并让它水平居中。

  1. 然后是右侧的 Fragment 的布局文件,fragment_right.xml:



    


我们将右侧的 Fragment 背景颜色设为红色,在里面放了一个 TextView,并让它水平居中。

  1. 左侧的 XML 布局文件对应的 Java 代码,LeftFragment.java
public class LeftFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_left, container, false);
        return view;
    }

}
  1. 右侧的 XML 布局文件对应的 Java 代码,RightFragment.java
public class RightFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_right, container, false);
        return view;
    }
    
}

上面分别新建了两个类,并让它们继承自 Fragment 类,代表左右侧 Fragment,并将各自对应的布局传进去。

  1. 接下来是 activity_main.xml:



    

    


在 Activity 的布局中,我们定义了两个 fragment 控件,并让它们水平排列,其中右侧的 Fragment 充满了剩下的空间。
注意: fragment 控件中有个属性 android:name,其值为 包名.类名,包名不能省略。

  1. MainActivity 的代码,默认即可,MainActivity.java:
public class MainActivity extends AppCompatActivity {

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

}
  1. 效果演示:
Android - Fragment_第2张图片

总结:静态添加 Fragment 的步骤

  1. 新建 Fragment 的布局;
  2. 新建一个继承自 Fragment 类 的类,并将对应的布局传进去;
  3. 在 Activity 布局中定义 fragment 控件,将 fragment 控件的 *android:name * 属性的值设为 包名.类名

动态添加

上面的程序中我们定义了一个按钮,但没有给它添加点击事件,现在我们把给它增加一个功能,使按钮被按下时,把右侧的 Fragment 更换成另一个 Fragment。

  1. 新建 fragment_right_2.xml:



    


上面只是在原来 fragment_right.xml 上修改了背景颜色和 TextView 的颜色。

  1. SecondRightFragment.java:
public class SecondRightFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_right_2, container, false);
        return view;
    }

}
  1. activity_main.xml:



    

    

        

    
    

activity_main.xml 中我们添加了在 LinearLayout 中嵌套了一个 FrameLayout 布局,并让它与左侧的 fragment 控件水平排列,后面我们将在代码中用另一个 Fragment 来取代 FrameLayout 里的内容,FrameLayout 在这里起占位作用,FrameLayout 也可以换成其他布局。

  1. MainActivity.java:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        Button btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(this);

    }

    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn:
                replaceFragment();
                break;
            default:
                break;
        }
    }

    private void replaceFragment() {

        // 1. 创建需要添加的 Fragment 的对象
        SecondRightFragment fragment = new SecondRightFragment();

        // 2. 获取 FragmentManager,在  Activity 中直接调用 getFragmentManager() 方法获取
        FragmentManager fm = getFragmentManager();

        // 3. 开启一个事务,通过调用 beginTransaction() 方法开启
        FragmentTransaction transaction = fm.beginTransaction();

        // 4. 向布局中添加 Fragment,调用 replace() 方法实现,需要传入布局的 id 和待添加的 Fragment 的对象
        transaction.replace(R.id.layout_right, fragment);

        // 5. 提交事务,调用 commit() 方法来完成
        transaction.commit();

    }
    
}
  1. 效果演示:
Android - Fragment_第3张图片

点击按钮之后,变成下面的界面:

Android - Fragment_第4张图片

此时点击返回键,会发现直接退出程序。如果想要点击返回键不退出程序,而是返回上一个 Fragment 时,可以在调用 FragmentTransaction 的 commit() 方法之前调用 addToBackStack() 方法,并给它传入 null,之后按下返回键将不会直接退出程序,而是返回上一个 Fragment。

总结:动态添加 Fragment 的步骤

  1. 创建需要添加的 Fragment 的对象;
  2. 获取 FragmentManager,在 Activity 中直接调用 getFragmentManager() 方法获取;
  3. 开启一个事务,通过调用 FragmentManager 对象的 beginTransaction() 方法开启;
  4. 向布局中添加 Fragment,调用 FragmentTransaction 的 replace() 方法实现,需要传入布局的 id 和待添加的 Fragment 的对象;
  5. 提交事务,调用 FragmentTransaction 的 commit() 方法来完成。

Fragment 的生命周期

  1. 先通过一张图片来了解 Fragment 的生命周期,以下图片取自《第一行代码》:


    Android - Fragment_第5张图片
  • onAttach(): 当 Fragment 和 Activity 第一次建立联系时调用,之后不再调用;
  • onCreate(): onAttach() 方法之后立刻调用;
  • onCreateView(): 加载布局时调用;
  • onActivityCreated(): 与 Fragment 关联的 Activity 创建之后调用;
  • onStart(): Fragment 可见时调用;
  • onResume(): onStart() 执行之后调用;
  • onPause(): 当另一个 Activity 在前台并获得焦点,但该 Activity 并没有占满整个屏幕,而 Fragment 的 宿主 Activity (即Fragment 所在的 Activity) 仍然可见时调用;
  • onStop(): Fragment 不可见时调用:当 Fragment 的宿主 Activity 处于 stopped 状态时调用;当 Fragment 从宿主 Activity 中删除 (调用 remove() 方法) 但没有被添加到返回栈 (没有调用 addToBackStack() 方法) 之中时调用。(处于 stopped 状态的 Fragment 依然存活,它的所有状态和信息会被系统保留起来,只是对于用户而言是不可见的,当 宿主 Activity 被杀死时,Fragment 相应地也会被杀死);
  • onDestroyView(): 当与 Fragment 关联的 View 被被移除时调用;
  • onDestroy(): 当 Fragment 不再被使用时调用,如按返回键时;
  • onDeatach(): 当 Fragment 与 Activity 解除关联时调用。
    详细内容请看这篇文章 Fragment 整个生命周期演示
  1. 接下来修改上面的 RightFragment.java 代码,重写图片中的方法,重写的规则是在每个方法里面增加一条 Log.d 语句,这样我们就能在 logcat 里面看到每个方法在什么时候被调用:
public class RightFragment extends Fragment {

    private static final String TAG = "Lifecycle";

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d(TAG,"onAttach");
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"onCreate");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(TAG,"onCreateView");
        View view = inflater.inflate(R.layout.fragment_right, container, false);
        return view;
    }

    @Override
    public void onActivityCreated(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,"onDestroy");
    }

}

3. 好了,接下来运行程序,并打开 logcat 窗口,看看发生了什么:

Android - Fragment_第6张图片

可以看到,在程序启动时调用了 onCreate() 方法,接着在加载布局的时候调用了 onCreateView() 方法,紧接着又分别调用了 onActivityCeated() 、onStart() 、onResume() 这三个方法。哎?等等,好像有点不对,怎么一开始不是调用 onAttach() 方法?回到我们上面的代码,我们在重写 onAttach() 方法时,传入的参数为类型为 Context,所以在一开始是不会调用的,将传入的参数类型修改为 Activity 就可以了。另外,在 SDK API 23 的版本中,当我们传入的参数类型为 Activity 时会提示该方法已过时。

  1. 将 onAttach() 方法传入的参数类型改为 Activity,再次运行程序,此时执行的方法如下:
Android - Fragment_第7张图片

当我们点击左侧 Fragment 中的按钮时,将会动态替换掉 RightFragment,此时执行的方法如下:

  1. 让我们重新运行程序,此时执行的方法如下:
Android - Fragment_第8张图片

此时按下主页键,执行的方法如下:

再按下多任务键,回到程序,此时执行的方法如下:

  1. 再重新运行程序,然后按下多任务键,此时执行的方法如下:

再回到程序,此时执行的方法如下:

  1. 再重新运行程序,然后按下返回键,此时执行的方法如下:

通过上面的一系列操作,我们对 Fragment 的生命周期已经有了一些了解了。

Fragment 与 Activity 之间的通信

  1. 在 Activity 里调用 Fragment 里的方法:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

在 Activity 中调用 FragmentManager 对象的 findFragmentById() 方法,就能获得与 Activity 关联的 Fragment 的实例,进而通过该实例调用 Fragment 里的方法。

  1. 在 Fragment 里调用 Activity 里的方法:
MainActivity activity = (MainActivity) getActivity();

在 Fragment 中通过 getActivity() 方法,就能获得与 Fragment 关联的 Activity 的实例,进而通过该实例调用 Activity 里的方法。

  1. 下面来看一个实例:
Android - Fragment_第9张图片

左边是一个 Activity 里的 ListView,右边是一个 Fragment,当点击左边 ListView 的某一个子项时,在右边显示该子项的名字及大图。

  1. 首先是 Activity 的布局,activity_main.xml:


    

    
    


  1. 接下来是右边 Fragment 的布局,fragment_large.xml:



    

    


  1. 样式文件,控制 ListView 的外观,list_view_item.xml:



    

    


  1. 定义一个接口,接口里面定义了一个 send() 方法,此方法用于从 Activity 向 Fragment 发送数据,即当 Activity 里的 ListView 的某一子项被点击时,将该子项的名字与图片 id 传给右边的 Fragment,右边的 Fragment 接收到数据之后,将数据在自己的布局中显示出来。
    FragmentCallbackListener.java:
public interface FragmentCallbackListener {
    void send(String name, int imageId);
}
  1. MainActivity.java:
public class MainActivity extends AppCompatActivity {

    // 定义一个 int 型的数组,用于存放图片资源的 id
    private int[] imageId = {R.drawable.aries, R.drawable.taurus, R.drawable.gemini, R.drawable.cancer,
            R.drawable.leo, R.drawable.virgo, R.drawable.libra, R.drawable.scorpio, R.drawable.sagittarius,
            R.drawable.capricorn, R.drawable.aquarius, R.drawable.pisces, R.drawable.fuck_off};

    // 定义一个 String 类型的数组,用于存放名称
    private String[] titles = {"白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座",
            "天秤座", "天蝎座", "射手座", "摩羯座", "水瓶座", "双鱼座", "Surprise"};

    // 定义一个 FragmentCallbackListener 接口的引用
    private FragmentCallbackListener listener;

    public FragmentCallbackListener getListener() {
        return listener;
    }

    public void setListener(FragmentCallbackListener listener) {
        this.listener = listener;
    }

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

        List> listItems = new ArrayList<>();
        for (int i = 0; i < titles.length; i++) {
            Map listItem = new HashMap<>();
            listItem.put("imageId", imageId[i]);
            listItem.put("titles", titles[i]);
            listItems.add(listItem);
        }

        // 1. 实例化布局中的控件
        ListView listView = (ListView) findViewById(R.id.list_view);

        // 2. 创建一个 Adapter 对象,并进行设置
        SimpleAdapter adapter = new SimpleAdapter(this, listItems, R.layout.list_view_item,
                new String[]{"imageId", "titles"}, new int[]{R.id.iv_small, R.id.tv_small});

        // 3. 将 Adapter 设置到 AdapterView
        listView.setAdapter(adapter);

        // 4. 设置 AdapterView 的监听事件
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView adapterView, View view, int i, long l) {
                // 调用 FragmentCallbackListener 接口的 send() 方法,
                // 把用户当前点击的子项的名称和图片 id 传给实现 FragmentCallbackListener 接口的地方
                listener.send(titles[i], imageId[i]);
            }
        });

        replaceFragment();
    }

    // 此方法用于动态添加 Fragment
    private void replaceFragment() {

        // 1. 创建一个待添加的 Fragment 的对象
        LargeViewFragment fragment = new LargeViewFragment();

        // 2. 获取 FragmentManager,通过 getFragmentManager() 方法获取
        FragmentManager fm = getFragmentManager();

        // 3. 开启一个事务,通过 FragmentManager 对象的 beginTransaction() 方法开启
        FragmentTransaction transaction = fm.beginTransaction();

        // 4. 向布局中添加 Fragment
        transaction.replace(R.id.frame_layout, fragment);

        // 5. 提交事务
        transaction.commit();
    }

}
  1. LargeViewFragment.java:
public class LargeViewFragment extends Fragment {

    private TextView tv_large;
    private ImageView iv_large;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        
        // 加载 Fragment 的布局
        View view = inflater.inflate(R.layout.fragment_large, container, false);
        
        // 实例化布局中的控件
        tv_large = (TextView) view.findViewById(R.id.tv_large);
        iv_large = (ImageView) view.findViewById(R.id.iv_large);

        // 获取宿主 Activity 的实例
        MainActivity activity = (MainActivity) getActivity();

        // 调用 Activity 中的 setListener 方法,并实现 FragmentCallbackListener 接口
        activity.setListener(new FragmentCallbackListener() {
            @Override
            public void send(String name, int imageId) {
                // 更新 UI
                tv_large.setText(name);
                iv_large.setImageResource(imageId);
            }
        });
        return view;
    }
    
}

注意: 上面的代码中使用了接口回调,在 MainActivity 中并不实现该接口,而是在 Fragment 中实现该接口;也就是说,发送数据的一方不实现接口,接收数据的一方负责实现接口。

  1. 演示效果:
Android - Fragment_第10张图片
Android - Fragment_第11张图片
Android - Fragment_第12张图片

Fragment 与 Fragment 之间的通信

在一个 Fragment 中可以得到与它关联的 Activity,然后再通过这个 Activity 去获取另外一个 Fragment 的实例,这样也就实现了不同碎片之间的通信功能。——《第一行代码》- 郭霖


参考资料:

  • 《第一行代码》- 郭霖
  • Fragment 整个生命周期演示 - SnailDream
  • Fragment生命周期(在不同的方法分别能做什么操作)- 东的专栏

你可能感兴趣的:(Android - Fragment)