第十九个项目——使用Fragment组织UI页面

实验目的

  • 掌握Fragment组件的使用方法;

  • 掌握FragmentActivity组件的交互方式;

实验要求

  • 使用Fragment实现新闻列表加载

实验内容

Fragment中文名为片段碎片,它可视为Activity UI页面中的部分或轻量级的Activity,其具备与Activity相似的生命周期。通过Fragment可将原有的ActivityUI页面进行划分,提升UI页面的可重用性以及适配性。本实验通过Fragment第七个项目中新闻列表页面进行重构,介绍Fragment的基本使用方法。

使用Fragment时,需要从自定义Fragment子类,并至少需要重写下列三个方法:

  • onCreate(),与Activity中的onCreate()方法相似,该方法在创建Fragment实例时被回调,

    通常进行Fragment中组件的初始化;

  • onCreateView(),该方法在当需要Fragment绘制UI时被调用,方法需要返回Fragment的UI布局根视图;

  • onPause(),该方法在用户离开FragmentUI页面时被调用,通常进行数据持久化或组件销毁;

除了上述三个方法必须进行重写外,Fragment还包含了与生命周期相关的其他几个回调函数,详见官方开发文档。

步骤一,打开第七个项目

打开第七个项目,在该项目的基础上完成本次实验。

步骤二,新建NewsListFragment

Project窗口中选择app/java文件夹,右击选择【New】->【Fragment】->【Fragment(Blank)】,在弹出的 New Android Component对话框中Fragment Name文本框中输入NewsListFragment, 勾选Create layout XML?、取消勾选Include fragment factory methods?Include interface callbacks复选框,点击【Finish】完成新建Fragment的创建, 具体如图1. 新建NewsListFragment所示。

完成新建操作后,在项目中将生成NewsListFragment.java以及fragment_news_list.xml两个文件,分别对应 Fragment的源文件以及布局文件。

使用Fragment重构第七个项目,主要是将原在MainActivity中各回调函数的业务代码相应的迁移到NewsListFragment相应的回调函数中,而原有activity_main.xml布局元素(除根视图元素外)也可一并迁移至fragment_news_list.xml文件中。

/**
 * A simple {@link Fragment} subclass.
 */
public class NewsListFragment extends Fragment {

    public NewsListFragment() {
            // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                            ViewGroup container,
                            Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_news_list,
                            container, false);
    }
}

步骤三,更改fragment_news_list.xml布局文件

第七个项目activity_main.xml布局文件中的ListView标签剪切并复制到fragment_news_list.xml文件中,具体代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lv_news_list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:scrollbars="none"
        android:layout_margin="8dp"
        android:divider="@android:color/transparent"
        android:dividerHeight="8dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>

步骤四,修改NewsListFragment类定义

NewsListFragment中实现新闻列表加载功能,主要思路是将原MainActivity中的业务代码逐一剪切复制到NewsListFragment当中。

1. 将titles、newsAdapter等对象声明复制到NewsListFragment中

titlesnewsListnewsAdapter等对象申明从MainActivity剪切复制到NewsListFragment中。

2. 将initData()方法复制到NewsListFragment中

initData()方法定义从MainActivity剪切复制到NewsListFragment中。

3. 重写NewsListFragment的onCreate()方法

NewsListFragment类中重写onCreate()方法,在其中调用initData()方法进行新闻列表数据初始化。

4. 重写NewsListFragment的onCreateView()方法

onCreateView()方法在Fragment绘制UI时被回调,该方法返回值为完成布局渲染的根视图元素。 为方便加载布局,该方法包含了三个参数:

  • LayoutInflater inflater,用于加载布局的LayoutInflater对象;

  • ViewGroup containerFragment加载的布局应嵌入进的ViewGroup对象;

  • Bundle savedInstanceState,初始化所需的Bundle对象;

NewsListFragmentonCreateView()方法中,首先,通过getActivity()获取到与绑定当前FragmentActivity对象,并将其作为Context上下文对象进行保存,便于后续使用;其次,通过LayoutInflater对象加载了fragment_news_list布局;最后,通过加载好的布局绑定ListView控件对象,并设置该对象所需的NewsAdapter适配器。

public class NewsListFragment extends Fragment {

    private Context context = null;

    private String[] titles = null;
    private String[] authors = null;
    private String[] contents = null;
    private TypedArray images;

    private List<News> newsList = new ArrayList<>();

    private NewsAdapter newsAdapter = null;
    private ListView lvNewsList;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        initData();
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                    ViewGroup container,
                    Bundle savedInstanceState) {
        context = getActivity();
        View rootView = inflater.inflate(R.layout.fragment_news_list,
                                 container, false);    

        lvNewsList = rootView.findViewById(R.id.lv_news_list);
        newsAdapter = new NewsAdapter(context,
                                R.layout.list_item,
                                newsList);
        lvNewsList.setAdapter(newsAdapter);

        return rootView;
    }

    private void initData() {
        int length;

        titles = getResources().getStringArray(R.array.titles);
        authors = getResources().getStringArray(R.array.authors);
        images = getResources().obtainTypedArray(R.array.images);

        if (titles.length > authors.length) {
            length = authors.length;
        } else {
            length = titles.length;
        }

        for (int i = 0; i < length; i++) {
            News news = new News();
            news.setTitle(titles[i]);
            news.setAuthor(authors[i]);
            news.setImageId(images.getResourceId(i, 0));

            newsList.add(news);
        }
    }
}

完成将MainActivity的代码复制到NewsListFragment后,MainActivity类定义代码如下所示。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }    
}

步骤五,加载Fragment

Activity中加载Fragment有2种方式:

  • 静态加载,在Activity对应的布局中使用标签进行静态加载;

  • 动态加载,在Activity中使用FragmentManagerFragmentTransaction动态对Fragment进行数据加载及绑定;

本实验中使用静态加载的方式加载Fragment,打开activity_main.xml布局文件,由于在步骤三 中已经将原ListView标签从activity_main.xml中剪切复制到fragment_news_list.xml中,因此直接在 activity_main.xml根视图元素中加入标签,并通过android:name属性指定 所需加载的Fragment类名(包含包名),最终的布局文件代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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=".MainActivity">

    <fragment
        android:name="com.glriverside.xgqin.listviewdemo.NewsListFragment"
        android:id="@+id/news_list"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />
</android.support.constraint.ConstraintLayout>

步骤六,编译并运行项目

编译并运行项目,其执行效果与第七个项目相同。

附:Fragment动态加载

动态加载Fragment需要使用FragmentManagerFragmentTransaction两个组件。动态加载Fragment需要指定对应的ViewGroup视图容器,并通过FragmentTransaction完成事务操作。

1. 首先调整activity_main.xml布局文件

activity_main.xml布局文件中,需要定义ViewGroup视图容器作为动态加载Fragment的根视图容器, 在本实验中,使用ConstraintLayout作为根视图容器,并指定其id属性为fragment_container, 具体代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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=".MainActivity">

    <android.support.constraint.ConstraintLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/fragment_container"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        >
    </android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>

2. 修改MainActivity的onCreate()方法

Fragment 静态加载方式中,通过在布局文件中指定,当Activity被实例化时, 指定的Fragment将被实例化,且该Fragment对象对应的onCreate()onCreateView()方法会被回调执行。

动态加载Fragment则需要通过FragmentTransaction碎片事务组件完成。 首先,需要获取FragmentManager对象; 其次,调用FragmentManager对象的beginTransaction()方法获得FragmentTransaction对象;在此基础上 调用FragmentTransaction对象的相应方法添加、替换Fragment至指定的ViewGroup视图容器;最后, 调用FragmentTransaction对象的commit()方法提交碎片事务,执行Fragment加载操作。

在该过程中,以下几个方法尤为重要:

  • getSupportFragmentManager(),获取FragmentManager对象;

  • FragmentManager.beginTransaction(),开始碎片事务,获得FragmentTransaction对象;

  • FragmentTransaction.add()FragmentTransaction.replace()

    添加或替换Fragment至指定ViewGroup视图容器;

  • FragmentTransaction.addToBackStack(),添加Fragment至返回栈;

  • FragmentTransaction.commit(),提交碎片事务;

如下代码所示,首先获取了FragmentManager对象,并开始FragmentTransaction碎片事务,再将实例化的NewsListFragment对象通过FragmentTransaction.add()方法添加到R.id.fragment_container视图容器中,最后通过FragmentTransaction.commit()提交碎片事务,完成Fragment动态加载操作。

public class MainActivity extends AppCompatActivity {
    FragmentManager fragmentManager;

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

        fragmentManager = getSupportFragmentManager();
        FragmentTransaction ft = fragmentManager.beginTransaction();

        NewsListFragment newsListFragment = new NewsListFragment();

        ft.add(R.id.fragment_container, newsListFragment);
        ft.commit();
    }    
}

实验小结

通过本次实验,你应该掌握了如下知识内容:

  • 使用Fragment组织UI界面及逻辑;

  • 使用FragmentManagerFragmentTransaction进行Fragment动态加载;

Last updated

Was this helpful?