欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第一行代码之碎片(fragment)

程序员文章站 2022-03-04 19:43:22
...

4.2 碎片的使用方式

4.2.1 碎片的简单用法

新建一个左侧碎片布局 fragment_left.xml,代码如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="按钮"/>

</LinearLayout>

然后新建右侧碎片布局fragment_right.xml,代码如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#00ff00">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="这是右边的fragment"/>

</LinearLayout>

将布局的背景色设置成绿色,并放置了一个 TextView 用于显示一段文本

 

建议使用 support-v4 库中的 Fragment,因为它可以让碎片在所有 Android 系统版本中保持功能一致性

LeftFragment 的代码如下所示:

public class LeftFragment extends Fragment {

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

这里仅仅是重写了 Fragment 的 onCreateView()方法

然后在这个方法中通过 LayoutInflater 的 inflate()方法将刚才定义的 fragment_left 布局动态加载进来

接着我们用同样的方法再新建一个 RightFragment:

public class RightFragment extends Fragment {

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

接下来修改 activity_fragment.xml 中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <fragment
        android:id="@+id/fragment_left"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <fragment
        android:id="@+id/fragment_right"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>
    
</LinearLayout>

可以看到,我们使用了<fragment>标签在布局中添加碎片

通过 android:name 属性来显式指明要添加的碎片类名,注意一 定要将类的包名也加上

这样最简单的碎片示例就已经写好了,运行一下程序,(平板上)效果如图:

第一行代码之碎片(fragment)

4.2.2 动态添加碎片

碎片真正的强大之处在于,它可以在程序运行时动态地添加到活动当中

根据具体情况来动态地添加碎片,你就 可以将程序界面定制得更加多样化

在上一节代码的基础上继续完善,新建 fragment_another_right.xml,代码如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#ffff00">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="这是另外一个右边的fragment"/>

</LinearLayout>

然后新建 AnotherRightFragment 作为另一个右侧碎片,代码如下所示:

public class AnotherRightFragment extends Fragment {

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

接下来看一下如何将它动态地添加到活动当中。修改 activity_fragment.xml,代码如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <fragment
        android:id="@+id/fragment_left"
        android:name="com.wonderful.myfirstcode.inquiry_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"/>
    
    <!--
    <fragment
        android:id="@+id/fragment_right"
        android:name="com.wonderful.myfirstcode.inquiry_fragment.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>
         -->

</LinearLayout>

可以看到,现在将右侧碎片放在了一个 FrameLayout 中

下面在代码中向 FrameLayout 里添加内容,从而实现动态添加碎片的功能

修改 Activity 中的代码,如下所示:

public class FragmentActivity extends AppCompatActivity implements View.OnClickListener {

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

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
        replaceFragment(new RightFragment());
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button:
                replaceFragment(new AnotherRightFragment());
                break;

            default:
                break;
        }
    }

    private void replaceFragment(Fragment fragment){
        // 获取FragmentManager
        FragmentManager fragmentManager = getSupportFragmentManager();
        // 开启事务
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 添加或替换碎片
        transaction.replace(R.id.right_layout,fragment);
        // 提交事务
        transaction.commit();
    }
}

上述代码,给左侧碎片中的按钮注册了一个点击事件

调用 replaceFragment() 方法动态添加碎片

结合代码可看出,动态添加碎片主要分为 5 步

 

1. 创建待添加的碎片实例

2. 获取 FragmentManager,在活动中可以直接调用 getSupportFragmentManager()方法得到

3. 开启一个事务,通过调用 beginTransaction()方法开启

4. 向容器内添加或替换碎片,使用 replace() 方法实现,需要传入容器的 id 和待添加的碎片实例。

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

重新运行程序,效果如图:

第一行代码之碎片(fragment)

4.2.3 在碎片中模拟返回栈

点击按钮添加了一个碎片之后,按下 Back 键程序就会直接退出

如果这里我们想模仿类似于返回栈的效果,按下 Back 键可以回到上一个碎片,该如何实现呢?

FragmentTransaction 中提供了一个 addToBackStack() 方法

可以用于将一个事务添加到返回栈中,修改 Activity 中的代码,如下所示:

public class FragmentActivity extends AppCompatActivity implements View.OnClickListener {

    . . .

    private void replaceFragment(Fragment fragment){
        // 获取FragmentManager
        FragmentManager fragmentManager = getSupportFragmentManager();
        // 开启事务
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 添加或替换碎片
        transaction.replace(R.id.right_layout,fragment);
        // 用于描述返回栈的状态
        transaction.addToBackStack(null);
        // 提交事务
        transaction.commit();
    }
}

在事务提交之前调用了 FragmentTransaction 的 addToBackStack() 方法

它可以接收一个名字用于描述返回栈的状态,一般传入 null 即可

4.2.4 碎片和活动之间进行通信

为了方便碎片和活动之间进行通信,FragmentManager 提供了一个类似于 findViewById() 的方法

专门用于从布局文件中获取碎片的实例。在活动中调用碎片里的方法:

RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);

在碎片中调用活动里的方法:通过调用 getActivity()方法来得到和当前碎片相关联的活动实例

代码如下所示:

MainActivity activity = (MainActivity) getActivity();

4.3 碎片的生命周期

和活动 Acitvity 相似,Fragment 类中也提供了一系列的回调方法,以覆盖碎片生命周期的每个环节。其中,活动中有的回调方法,碎片中几乎都有,不过碎片还提供了一些附加的回调方法,重点来看下这几个回调:

  • onAttach() 当碎片和活动建立关联的时候调用。
  • onCreateView() 为碎片创建视图(加载布局)时调用。
  • onActivityCreated() 确保与碎片相关联的活动一定已经创建完毕的时候调用。
  • onDestroyView() 当与碎片关联的视图被移除的时候调用。
  • onDetach() 当碎片和活动解除关联的时候调用。

另外,在碎片中你也可以通过onSaveInstanceState()方法来保存数据

因为进入停止状态的碎片有可能在系统内存不足的时候被回收

保存下来的数据在 onCreate()、onCreateView() 和 onActivityCreated()这三个方法中

你都可以重新得到,它们都含有一个 Bundle 类型的 savedInstanceState 参数

4.5 碎片的最佳实践——一个简易版的新闻应用

添加后面需要用到的 RecyclerView 依赖库

接下来,准备好一个新闻的实体类,新建类 News,代码如下所示:

/**
 * 新闻实体类
 * Created by KXwon on 2016/12/12.
 */

public class News {

    private String title;   // 新闻标题

    private String content; // 新闻内容

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

接着新建一个 news_content_frag.xml 布局,作为新闻内容的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/visibility_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:visibility="invisible">

        <TextView
            android:id="@+id/news_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="20sp"
            android:padding="10dp"/>
        
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#000"/>

        <TextView
            android:id="@+id/news_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:textSize="18sp"
            android:padding="15dp"/>
        
    </LinearLayout>

    <View
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:background="#000"/>

</RelativeLayout>

新闻内容的布局主要分为两个部分

头部显示新闻标题,正文显示新闻内容

中间使用一条细线分隔开

 

然后再新建一个 NewsContentFragment 类,如下:

/**
 * 新闻内容fragment
 * Created by KXwon on 2016/12/12.
 */

public class NewsContentFragment extends Fragment {

    private View view;

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

    /**
     * 将新闻标题和新闻内容显示在界面上
     * @param newsTitle   标题
     * @param newsContent 内容
     */
    public void refresh(String newsTitle, String newsContent) {
        View visibilityLayout = view.findViewById(R.id.visibility_layout);
        visibilityLayout.setVisibility(View.VISIBLE);
        TextView newsTitleText = (TextView) view.findViewById (R.id.news_title);
        TextView newsContentText = (TextView) view.findViewById(R.id.news_content);
        newsTitleText.setText(newsTitle); // 刷新新闻的标题
        newsContentText.setText(newsContent); // 刷新新闻的内容
    }
}

这样就把新闻内容的碎片和布局创建好了

但它们都是在双页模式下使用的,若要在单页模式中使用

还需创建一个活动 NewsContentActivity,其布局 news_content.xml 中的代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/news_content_fragment"
        android:name="com.wonderful.myfirstcode.chapter4.simple_news.NewsContentFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</LinearLayout>

这里直接在布局中引入了 NewsContentFragment

相当于把 news_content_frag 布局的内容自动加了进来
然后编写 NewsContentActivity 的代码,如下:

public class NewsContentActivity extends AppCompatActivity {

    /**
     * 构建Intent,传递所需数据
     * @param context
     * @param newsTitle
     * @param newsContent
     */
    public static void actionStart(Context context, String newsTitle, String newsContent) {
        Intent intent = new Intent(context, NewsContentActivity.class);
        intent.putExtra("news_title", newsTitle);
        intent.putExtra("news_content", newsContent);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_content);
        // 获取传入的新闻标题、新闻内容
        String newsTitle = getIntent().getStringExtra("news_title");
        String newsContent = getIntent().getStringExtra("news_content");
        // 获取 NewsContentFragment 实例
        NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager()
                .findFragmentById(R.id.news_content_fragment);
        // 刷新 NewsContentFragment 界面
        newsContentFragment.refresh(newsTitle, newsContent); 
    }
}

在 onCreate() 方法中通过 Intent 获取传入的新闻标题和内容

然后调用 FragmentManager 的 findFragmentById() 方法得到 NewsContentFragment 的实例

接着调用它的 refresh() 方法,并将新闻的标题和内容传入,显示数据

(关于 actionStart() 方法可以阅读前面的探究活动2.5.2相关笔记。)

接下来还需再创建显示新闻列表的布局 news_title_frag.xml,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/news_title_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

新建 news_item.xml 作为 上述 RecyclerView 子项的布局:

<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/news_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:singleLine="true"
    android:ellipsize="end"
    android:textSize="18sp"
    android:padding="10dp"/>

子项的布局就只有一个 TextView

新闻列表和子项布局都创建好了,接下来就需要一个用于展示新闻列表的地方

这里新建 NewsTitleFragment 作为展示新闻列表的碎片:

/**
 * 新闻列表fragment
 * Created by KXwon on 2016/12/12.
 */

public class NewsTitleFragment extends Fragment{

    private boolean isTowPane;

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

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout)!= null){
            // 可以找到 news_content_layout 布局时,为双页模式
            isTowPane = true;
        }else {
            // 找不到 news_content_layout 布局时,为单页模式
            isTowPane = false;
        }
    }
}

为实现上述 onActivityCreated() 方法中判断当前时双页还是单页模式

接下来在 NewsTitleFragemt 中新建一个内部类 NewsAdapter 来作为 RecyclerView 的适配器

如下:

public class NewsTitleFragment extends Fragment{

    private boolean isTowPane;

    . . .
    
    class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {

        private List<News> mNewsList;

        class ViewHolder extends RecyclerView.ViewHolder {

            TextView newsTitleText;

            public ViewHolder(View view) {
                super(view);
                newsTitleText = (TextView) view.findViewById(R.id.news_title);
            }
        }

        public NewsAdapter(List<News> newsList) {
            mNewsList = newsList;
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item, parent, false);
            final ViewHolder holder = new ViewHolder(view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    News news = mNewsList.get(holder.getAdapterPosition());
                    if (isTwoPane) {
                        // 若是双页模式,则刷新 NewsContentFragment 中的内容
                        NewsContentFragment newsContentFragment = (NewsContentFragment)
                                getFragmentManager().findFragmentById(R.id.news_content_fragment);
                        newsContentFragment.refresh(news.getTitle(), news.getContent());
                    } else {
                        // 若是单页模式,则直接启动 NewsContentActivity
                        NewsContentActivity.actionStart(getActivity(), news.getTitle(), news.getContent());
                    }
                }
            });
            return holder;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            News news = mNewsList.get(position);
            holder.newsTitleText.setText(news.getTitle());
        }

        @Override
        public int getItemCount() {
            return mNewsList.size();
        }

    }

需要注意的是,这里把适配器写成内部类是为了直接访问 NewsTitleFragment 的变量

 比如:isTowPane

现在还剩最后一步收尾工作,就是向 RecyclerView 中填充数据了

修改 NewsTitleFragment 中的代码,如下所示:

public class NewsTitleFragment extends Fragment{

    . . .

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.news_title_frag, container, false);
        
        RecyclerView newsTitleRecyclerView = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        newsTitleRecyclerView.setLayoutManager(layoutManager);
        NewsAdapter adapter = new NewsAdapter(getNews());
        newsTitleRecyclerView.setAdapter(adapter);
        
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout) != null) {
            // 可以找到news_content_layout布局时,为双页模式
            isTwoPane = true;
        } else {
            // 找不到news_content_layout布局时,为单页模式
            isTwoPane = false;
        }
    }

    /**
     * 初始化50条模拟新闻数据
     * @return
     */
    private List<News> getNews() {
        List<News> newsList = new ArrayList<>();
        for (int i = 1; i <= 50; i++) {
            News news = new News();
            news.setTitle("This is news title " + i);
            news.setContent(getRandomLengthContent("新闻内容吼吼吼" + i + "!"));
            newsList.add(news);
        }
        return newsList;
    }

    /**
     * 随机生成不同长度的新闻内容
     * @param content
     * @return
     */
    private String getRandomLengthContent(String content) {
        Random random = new Random();
        int length = random.nextInt(20) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(content);
        }
        return builder.toString();
    }

    . . . 
}

到这里,所有的代码编写工作就完成了,运行程序,效果如下:

第一行代码之碎片(fragment)

点击一条新闻,会启动一个新的活动来显示新闻内容:

第一行代码之碎片(fragment)

相关标签: Android