六个故事搞懂Fragment 故事1-初识Fragment - NewsHub的模块化革命

故事1:初识Fragment - NewsHub的模块化革命

晨光中的困境

清晨的第一缕阳光透过百叶窗的缝隙洒在张小安的办公桌上,照亮了散乱的草稿纸和喝了半杯的咖啡。作为移动互联网创业公司"启明科技"的Android开发工程师,小安已经连续奋战了三个星期,他负责开发的新闻阅读应用"NewsHub"即将进入内测阶段。

然而,此刻小安的眉头却紧锁得像一团解不开的毛线。

"又是这个问题..."他喃喃自语,手指在触摸板上快速滑动,屏幕上显示着两套几乎完全相同的XML布局文件。

"为什么手机版和平板版的代码重复率这么高?这简直是在浪费生命!"

小安揉了揉疲惫的眼睛,显示器上的代码仿佛在嘲笑他的困境。作为一名有着三年Android开发经验的工程师,他深知这种代码重复带来的维护噩梦:

ini 复制代码
<!-- 手机版 activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
​
    <FrameLayout
        android:id="@+id/news_list_container"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
​
</LinearLayout>
ini 复制代码
<!-- 平板版 activity_main_tablet.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
​
    <FrameLayout
        android:id="@+id/news_list_container"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
​
    <FrameLayout
        android:id="@+id/news_detail_container"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
​
</LinearLayout>

仅仅因为多了一个详情页面的容器,他就不得不维护两套几乎完全相同的布局文件。更糟糕的是,对应的Java代码也需要分别处理:

scss 复制代码
// MainActivity.java
public class MainActivity extends AppCompatActivity {
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
​
        if (isTablet()) {
            setContentView(R.layout.activity_main_tablet);
            // 平板逻辑:同时显示列表和详情
            showNewsList();
            showNewsDetail(null);
        } else {
            setContentView(R.layout.activity_main);
            // 手机逻辑:只显示列表,点击后跳转
            showNewsList();
        }
    }
​
    private boolean isTablet() {
        return getResources().getBoolean(R.bool.isTablet);
    }
​
    private void showNewsList() {
        // 无论是手机还是平板,这部分代码都一样
        NewsListFragment listFragment = new NewsListFragment();
        getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.news_list_container, listFragment)
            .commit();
    }
​
    private void showNewsDetail(NewsItem newsItem) {
        if (isTablet()) {
            // 平板:在右侧容器中显示详情
            NewsDetailFragment detailFragment = new NewsDetailFragment();
            if (newsItem != null) {
                Bundle args = new Bundle();
                args.putParcelable("news_item", newsItem);
                detailFragment.setArguments(args);
            }
            getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.news_detail_container, detailFragment)
                .commit();
        } else {
            // 手机:启动新的Activity显示详情
            Intent intent = new Intent(this, NewsDetailActivity.class);
            if (newsItem != null) {
                intent.putExtra("news_item", newsItem);
            }
            startActivity(intent);
        }
    }
}

"这样下去不行..."小安靠在椅背上,望着窗外的城市天际线,"每次修改一个功能,我都要在多个地方同步更新,太容易出错了。"

他想起上周那次惨痛的经历:因为修复手机版的一个列表显示bug,忘记同步更新平板版的代码,导致测试组发现平板应用崩溃,整个迭代计划被迫推迟。

"一定有更好的方法..."小安打开了技术文档浏览器,开始搜索"Android多屏适配最佳实践"。

意外的导师

就在小安陷入沉思的时候,办公室的门被轻轻推开了。

"小安,还在为NewsHub的适配问题烦恼吗?"

说话的是李明轩,启明科技的资深架构师。李工有着十多年移动开发经验,是公司技术团队的核心人物。他五十岁左右的年纪,头发已经花白,但眼神依旧锐利,仿佛能看透代码背后的本质。

"李工..."小安有些不好意思地笑了笑,"您怎么知道的?"

"你这周提交的代码我看了,"李工走到小安的工位旁,指着屏幕,"手机版和平板版的代码重复率超过70%,这在敏捷开发中是很危险的信号。"

小安点点头,沮丧地说:"我知道这样不好,但是我想不出更好的解决方案。Activity的架构天然就不支持模块化,我只能通过不同的布局文件来适配。"

李工笑了笑,从旁边拉过一张椅子坐下:"小安,你知道为什么Android在3.0版本引入Fragment吗?"

"我记得文档上说是为了更好地支持大屏幕设备..."小安回答。

"没错,但Fragment的意义远不止于此。"李工的眼中闪烁着智慧的光芒,"让我给你讲个故事吧。"

Fragment的诞生故事

"在Fragment出现之前,Android应用的开发模式就像是你现在遇到的问题,"李工开始娓娓道来,"每个屏幕都是一个独立的Activity,就像每个房间都是一栋独立的房子。"

小安想象着那个场景:一个社区里,每栋房子都独立存在,有自己的厨房、卧室、客厅。如果要建一个新的社区,就必须重新建造所有的房子。

"这种模式在手机时代还行,因为屏幕小,一个Activity就能显示所有内容。但平板出现后问题就暴露了。"李工继续说道,"平板屏幕大,可以同时显示多个界面。如果还用Activity,就像在同一个房间里建多栋房子,既浪费空间又难以管理。"

"所以Google设计了Fragment?"小安追问道。

"是的!"李工的语气变得兴奋起来,"Fragment就像积木块。你可以把一个Activity想象成一个玩具底板,Fragment就是各种形状的积木。在手机上,你可以在底板上放一块积木;在平板上,你可以同时放多块积木。"

李工拿出纸笔,画了一个简单的示意图:

scss 复制代码
手机布局:
┌─────────────────┐
│  Activity       │
│  ┌─────────────┐│
│  │ Fragment A  ││  (新闻列表)
│  └─────────────┘│
│                 │
│  ┌─────────────┐│
│  │ Fragment B  ││  (详情页面)
│  └─────────────┘│
└─────────────────┘
(一次只显示一个Fragment)
​
平板布局:
┌─────────────────┐
│  Activity       │
│  ┌──────────┐ │
│  │Fragment A│ │  (新闻列表)
│  │          │ │
│  └──────────┘ │
│  ┌──────────┐ │
│  │Fragment B│ │  (详情页面)
│  │          │ │
│  └──────────┘ │
└─────────────────┘
(同时显示两个Fragment)

"我明白了!"小安的眼睛亮了起来,"Fragment就是UI的模块化单元,Activity负责组装和管理这些模块!"

"完全正确!"李工赞许地点头,"这就是Fragment的核心设计理念:UI与逻辑的模块化。每个Fragment都是一个独立的组件,有自己的布局和生命周期,但必须托管在Activity中。"

Fragment的三大优势

李工看着小安恍然大悟的表情,继续深入讲解:"Fragment不仅解决了多屏适配问题,还有三个重要的优势:"

1. 代码复用性

"你看你现在的代码,"李工指着小安的屏幕,"NewsListFragment和NewsDetailFragment的逻辑其实是一样的,不管在手机还是平板上。如果用Fragment,你只需要写一次:"

less 复制代码
// NewsListFragment.java
public class NewsListFragment extends Fragment {
    private RecyclerView recyclerView;
    private NewsAdapter adapter;
    private List<NewsItem> newsList;
​
    // 回调接口,用于通知Activity用户点击了某条新闻
    public interface OnNewsSelectedListener {
        void onNewsSelected(NewsItem newsItem);
    }
​
    private OnNewsSelectedListener listener;
​
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        // 确保宿主Activity实现了回调接口
        if (context instanceof OnNewsSelectedListener) {
            listener = (OnNewsSelectedListener) context;
        } else {
            throw new RuntimeException(context.toString()
                + " must implement OnNewsSelectedListener");
        }
    }
​
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                           @Nullable ViewGroup container,
                           @Nullable Bundle savedInstanceState) {
        // 创建Fragment的视图
        View view = inflater.inflate(R.layout.fragment_news_list,
                                    container, false);
​
        recyclerView = view.findViewById(R.id.recyclerView);
        setupRecyclerView();
​
        return view;
    }
​
    @Override
    public void onViewCreated(@NonNull View view,
                           @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
​
        // 加载新闻数据
        loadNewsData();
    }
​
    private void setupRecyclerView() {
        adapter = new NewsAdapter(newsList, new NewsAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(NewsItem newsItem) {
                // 通知Activity用户选择了某条新闻
                if (listener != null) {
                    listener.onNewsSelected(newsItem);
                }
            }
        });
​
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        recyclerView.setAdapter(adapter);
    }
​
    private void loadNewsData() {
        // 从网络或数据库加载新闻数据
        // 这里使用模拟数据
        newsList = NewsRepository.getInstance().getLatestNews();
        adapter.notifyDataSetChanged();
    }
​
    // ... 其他方法
}

"看到吗?这个Fragment完全不知道自己会被用在手机还是平板上,它只负责显示新闻列表。如何使用这个Fragment,由Activity决定。"

2. 灵活的组合方式

"Activity就像乐高积木的底板,Fragment就是不同形状的积木。"李工拿起小安桌上的乐高积木举例,"你可以根据需要自由组合:"

scala 复制代码
// 手机版MainActivity.java
public class MainActivity extends AppCompatActivity
        implements NewsListFragment.OnNewsSelectedListener {
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_phone);
​
        // 手机版:只显示新闻列表Fragment
        if (savedInstanceState == null) {
            getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.fragment_container, new NewsListFragment())
                .commit();
        }
    }
​
    @Override
    public void onNewsSelected(NewsItem newsItem) {
        // 手机版:启动新的Activity显示详情
        Intent intent = new Intent(this, NewsDetailActivity.class);
        intent.putExtra("news_item", newsItem);
        startActivity(intent);
    }
}
scss 复制代码
// 平板版MainActivity.java
public class MainActivity extends AppCompatActivity
        implements NewsListFragment.OnNewsSelectedListener {
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_tablet);
​
        // 平板版:同时显示列表和详情Fragment
        if (savedInstanceState == null) {
            getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.news_list_container, new NewsListFragment())
                .add(R.id.news_detail_container, new NewsDetailFragment())
                .commit();
        }
    }
​
    @Override
    public void onNewsSelected(NewsItem newsItem) {
        // 平板版:直接更新详情Fragment
        NewsDetailFragment detailFragment =
            (NewsDetailFragment) getSupportFragmentManager()
                .findFragmentById(R.id.news_detail_container);
​
        if (detailFragment != null) {
            detailFragment.showNewsDetail(newsItem);
        }
    }
}

"这样,NewsListFragment和NewsDetailFragment的代码只需要写一次,但在不同的Activity中可以有不同的使用方式。"

3. 更好的生命周期管理

"Fragment有自己的生命周期,但它始终依赖于Activity。"李工继续解释,"这种设计让UI组件的管理更加精细化。"

他再次在纸上画了一个生命周期图:

复制代码
Activity生命周期:
    onCreate → onStart → onResume → onPause → onStop → onDestroy
         │        │        │        │        │        │
         ▼        ▼        ▼        ▼        ▼        ▼
Fragment生命周期:
    onAttach → onCreate → onCreateView → onViewCreated →
    onStart → onResume → onPause → onStop → onDestroyView → onDestroy → onDetach

"Fragment的生命周期比Activity更细致,"李工解释道,"比如onCreateViewonDestroyView专门管理View的创建和销毁,而onAttachonDetach管理Fragment与Activity的绑定关系。这种细粒度的控制在复杂应用中非常有用。"

Fragment的三种创建方式

"理论讲得差不多了,让我们看看实际怎么创建Fragment。"李工切换到IDE,开始演示。

方式一:静态加载(XML中声明)

"最简单的方式是在XML布局文件中直接声明Fragment:"李工创建了一个新的布局文件。

ini 复制代码
<!-- activity_main_static.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
​
    <!-- 静态加载Fragment -->
    <fragment
        android:id="@+id/news_list_fragment"
        android:name="com.example.newshub.NewsListFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
​
    <fragment
        android:id="@+id/news_detail_fragment"
        android:name="com.example.newshub.NewsDetailFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
​
</LinearLayout>

"静态加载的好处是简单直观,"李工解释,"Fragment会随着Activity的创建而自动创建。但缺点是不够灵活,一旦在XML中声明就不能在运行时更改。"

对应的Activity代码非常简单:

scala 复制代码
public class MainActivity extends AppCompatActivity {
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_static);
​
        // 可以通过findViewById获取Fragment实例
        NewsListFragment listFragment = (NewsListFragment)
            getSupportFragmentManager().findFragmentById(R.id.news_list_fragment);
        NewsDetailFragment detailFragment = (NewsDetailFragment)
            getSupportFragmentManager().findFragmentById(R.id.news_detail_fragment);
    }
}

方式二:动态加载(代码中添加)

"更常用的方式是动态加载,"李工继续演示,"这样可以在运行时根据需要添加、替换、移除Fragment。"

xml 复制代码
<!-- activity_main_dynamic.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
scala 复制代码
public class MainActivity extends AppCompatActivity {
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_dynamic);
​
        // 动态添加Fragment
        if (savedInstanceState == null) {
            NewsListFragment listFragment = new NewsListFragment();
​
            getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.fragment_container, listFragment)
                .commit();
        }
    }
}

"动态加载的关键是FragmentTransaction,"李工强调,"所有的Fragment操作都需要通过事务来执行:"

scss 复制代码
// Fragment的基本操作
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
​
// 添加Fragment
transaction.add(R.id.fragment_container, newFragment);
​
// 替换Fragment
transaction.replace(R.id.fragment_container, newFragment);
​
// 移除Fragment
transaction.remove(oldFragment);
​
// 隐藏Fragment(View被销毁但实例保留)
transaction.hide(fragment);
​
// 显示Fragment
transaction.show(fragment);
​
// 添加到回退栈(用户可以按返回键撤销这个操作)
transaction.addToBackStack(null);
​
// 提交事务
transaction.commit();

方式三:工厂模式创建

"在实际项目中,我推荐使用工厂模式创建Fragment,"李工分享了一个最佳实践,"这样可以确保Fragment总是以正确的方式被创建。"

java 复制代码
public class NewsDetailFragment extends Fragment {
​
    private static final String ARG_NEWS_ITEM = "news_item";
    private static final String ARG_SHOW_COMMENT = "show_comment";
​
    private NewsItem newsItem;
    private boolean showComment;
​
    // 私有构造函数,强制使用工厂方法
    private NewsDetailFragment() {
        // Required empty public constructor
    }
​
    /**
     * 工厂方法:创建新闻详情Fragment
     * @param newsItem 要显示的新闻项
     * @param showComment 是否显示评论区
     * @return NewsDetailFragment实例
     */
    public static NewsDetailFragment newInstance(NewsItem newsItem, boolean showComment) {
        NewsDetailFragment fragment = new NewsDetailFragment();
​
        // 创建参数Bundle
        Bundle args = new Bundle();
        args.putParcelable(ARG_NEWS_ITEM, newsItem);
        args.putBoolean(ARG_SHOW_COMMENT, showComment);
​
        // 设置参数
        fragment.setArguments(args);
​
        return fragment;
    }
​
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
​
        // 读取参数
        Bundle args = getArguments();
        if (args != null) {
            newsItem = args.getParcelable(ARG_NEWS_ITEM);
            showComment = args.getBoolean(ARG_SHOW_COMMENT, false);
        }
    }
​
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                           @Nullable ViewGroup container,
                           @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_news_detail, container, false);
​
        // 使用参数初始化UI
        initializeUI(view);
​
        return view;
    }
​
    private void initializeUI(View view) {
        TextView titleView = view.findViewById(R.id.news_title);
        TextView contentView = view.findViewById(R.id.news_content);
        View commentSection = view.findViewById(R.id.comment_section);
​
        if (newsItem != null) {
            titleView.setText(newsItem.getTitle());
            contentView.setText(newsItem.getContent());
            commentSection.setVisibility(showComment ? View.VISIBLE : View.GONE);
        }
    }
}

"工厂模式的优点是:"

  1. 参数安全:通过Bundle传递参数,确保参数在Fragment重建时不会丢失
  2. 创建统一:所有Fragment都通过工厂方法创建,避免忘记设置必要参数
  3. 代码清晰:调用者一看就知道需要哪些参数

使用工厂模式创建Fragment:

scss 复制代码
// 创建并显示新闻详情Fragment
NewsItem selectedNews = getSelectedNewsItem();
NewsDetailFragment detailFragment = NewsDetailFragment.newInstance(selectedNews, true);
​
getSupportFragmentManager()
    .beginTransaction()
    .replace(R.id.fragment_container, detailFragment)
    .addToBackStack(null)  // 添加到回退栈,用户可以返回
    .commit();

实战重构:NewsHub的模块化改造

"理论讲完了,现在让我们实际重构你的NewsHub应用。"李工拍了拍小安的肩膀,"你准备好开始这场模块化革命了吗?"

小安用力点头,眼中充满了兴奋:"准备好了!李工,我们从哪里开始?"

"首先,我们需要识别应用中的UI模块。"李工在白板上画了一个思维导图:

复制代码
NewsHub应用模块分析
├── 新闻列表模块
│   ├── 新闻条目显示
│   ├── 下拉刷新
│   └── 加载更多
├── 新闻详情模块
│   ├── 新闻内容显示
│   ├── 图片浏览
│   └── 分享功能
├── 用户中心模块
│   ├── 用户信息
│   ├── 设置选项
│   └── 历史记录
└── 搜索模块
    ├── 搜索框
    ├── 搜索结果
    └── 搜索历史

"根据这个分析,你的NewsHub至少需要4个核心Fragment:NewsListFragment、NewsDetailFragment、UserFragment和SearchFragment。"李工继续说道,"让我们先从最简单的开始:创建NewsListFragment。"

第一步:创建Fragment基类

"在实际项目中,我建议先创建一个BaseFragment,"李工开始编写代码,"这样可以避免重复编写通用代码。"

less 复制代码
// BaseFragment.java
public abstract class BaseFragment extends Fragment {
​
    protected View rootView;
    protected Context context;
    protected FragmentActivity activity;
​
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        this.context = context;
        if (context instanceof FragmentActivity) {
            this.activity = (FragmentActivity) context;
        }
    }
​
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                           @Nullable ViewGroup container,
                           @Nullable Bundle savedInstanceState) {
        // 如果rootView已经创建,直接返回(避免重复创建)
        if (rootView != null) {
            return rootView;
        }
​
        // 获取布局ID
        int layoutId = getLayoutId();
        rootView = inflater.inflate(layoutId, container, false);
​
        // 初始化View
        initView();
​
        return rootView;
    }
​
    @Override
    public void onViewCreated(@NonNull View view,
                           @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
​
        // 设置数据和事件监听
        initData();
        initListener();
    }
​
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        // 清理资源
        rootView = null;
    }
​
    /**
     * 获取Fragment的布局ID
     */
    protected abstract int getLayoutId();
​
    /**
     * 初始化View
     */
    protected abstract void initView();
​
    /**
     * 初始化数据
     */
    protected abstract void initData();
​
    /**
     * 初始化事件监听
     */
    protected void initListener() {
        // 子类可以重写此方法来设置监听器
    }
​
    /**
     * 显示Toast提示
     */
    protected void showToast(String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }
​
    /**
     * 显示进度条
     */
    protected void showProgressDialog(String message) {
        // 实现进度条显示逻辑
        // 这里可以封装一个通用的进度条工具类
    }
​
    /**
     * 隐藏进度条
     */
    protected void hideProgressDialog() {
        // 实现进度条隐藏逻辑
    }
}

"BaseFragment提供了Fragment的通用功能,"李工解释道,"这样每个具体的Fragment只需要关注自己的业务逻辑。"

第二步:创建NewsListFragment

"现在让我们创建新闻列表Fragment:"

scss 复制代码
// NewsListFragment.java
public class NewsListFragment extends BaseFragment
        implements SwipeRefreshLayout.OnRefreshListener {
​
    private static final String TAG = "NewsListFragment";
​
    // UI组件
    private RecyclerView recyclerView;
    private SwipeRefreshLayout swipeRefreshLayout;
    private ProgressBar progressBar;
    private TextView emptyView;
​
    // 数据和适配器
    private NewsAdapter newsAdapter;
    private List<NewsItem> newsList = new ArrayList<>();
​
    // 分页相关
    private int currentPage = 1;
    private boolean isLoading = false;
    private boolean hasMoreData = true;
​
    // 回调接口
    public interface OnNewsInteractionListener {
        void onNewsSelected(NewsItem newsItem);
        void onNewsFavorite(NewsItem newsItem);
        void onNewsShare(NewsItem newsItem);
    }
​
    private OnNewsInteractionListener listener;
​
    public static NewsListFragment newInstance() {
        return new NewsListFragment();
    }
​
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof OnNewsInteractionListener) {
            listener = (OnNewsInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                + " must implement OnNewsInteractionListener");
        }
    }
​
    @Override
    protected int getLayoutId() {
        return R.layout.fragment_news_list;
    }
​
    @Override
    protected void initView() {
        recyclerView = rootView.findViewById(R.id.recyclerView);
        swipeRefreshLayout = rootView.findViewById(R.id.swipeRefreshLayout);
        progressBar = rootView.findViewById(R.id.progressBar);
        emptyView = rootView.findViewById(R.id.emptyView);
​
        // 设置下拉刷新
        swipeRefreshLayout.setOnRefreshListener(this);
​
        // 设置RecyclerView
        setupRecyclerView();
​
        // 初始状态显示加载进度
        showLoading(true);
    }
​
    @Override
    protected void initData() {
        // 加载第一页数据
        loadNewsData(1, true);
    }
​
    @Override
    protected void initListener() {
        // RecyclerView的滚动监听,用于实现加载更多
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView,
                                 int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
​
                if (!isLoading && hasMoreData) {
                    LinearLayoutManager layoutManager =
                        (LinearLayoutManager) recyclerView.getLayoutManager();
​
                    if (layoutManager != null) {
                        int visibleItemCount = layoutManager.getChildCount();
                        int totalItemCount = layoutManager.getItemCount();
                        int pastVisibleItems = layoutManager.findFirstVisibleItemPosition();
​
                        // 当滚动到最后几个item时,加载更多数据
                        if ((visibleItemCount + pastVisibleItems) >= totalItemCount
                            && totalItemCount > 0) {
                            loadNewsData(currentPage + 1, false);
                        }
                    }
                }
            }
        });
    }
​
    private void setupRecyclerView() {
        newsAdapter = new NewsAdapter(newsList, new NewsAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(NewsItem newsItem) {
                if (listener != null) {
                    listener.onNewsSelected(newsItem);
                }
            }
​
            @Override
            public void onFavoriteClick(NewsItem newsItem) {
                if (listener != null) {
                    listener.onNewsFavorite(newsItem);
                }
                // 更新本地状态
                newsItem.setFavorite(!newsItem.isFavorite());
                newsAdapter.notifyDataSetChanged();
            }
​
            @Override
            public void onShareClick(NewsItem newsItem) {
                if (listener != null) {
                    listener.onNewsShare(newsItem);
                }
            }
        });
​
        recyclerView.setLayoutManager(new LinearLayoutManager(context));
        recyclerView.setAdapter(newsAdapter);
​
        // 添加分割线
        recyclerView.addItemDecoration(new DividerItemDecoration(context,
            DividerItemDecoration.VERTICAL));
    }
​
    private void loadNewsData(int page, boolean isRefresh) {
        if (isLoading) return;
​
        isLoading = true;
​
        if (isRefresh) {
            currentPage = 1;
            hasMoreData = true;
            if (!swipeRefreshLayout.isRefreshing()) {
                showLoading(true);
            }
        } else {
            showLoadMore(true);
        }
​
        // 模拟网络请求延迟
        new Handler().postDelayed(() -> {
            List<NewsItem> newData = NewsRepository.getInstance()
                .getNewsList(page, 20);
​
            if (getActivity() == null) return; // Fragment已经detached
​
            if (isRefresh) {
                newsList.clear();
                newsList.addAll(newData);
            } else {
                newsList.addAll(newData);
            }
​
            // 检查是否还有更多数据
            hasMoreData = newData.size() >= 20;
            currentPage = page;
​
            // 更新UI
            newsAdapter.notifyDataSetChanged();
​
            // 隐藏加载状态
            showLoading(false);
            showLoadMore(false);
            swipeRefreshLayout.setRefreshing(false);
​
            // 显示空状态
            if (newsList.isEmpty()) {
                showEmptyView(true);
            } else {
                showEmptyView(false);
            }
​
            isLoading = false;
​
        }, 1500); // 模拟1.5秒的网络延迟
    }
​
    @Override
    public void onRefresh() {
        loadNewsData(1, true);
    }
​
    private void showLoading(boolean show) {
        progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
        if (show) {
            recyclerView.setVisibility(View.GONE);
            emptyView.setVisibility(View.GONE);
        }
    }
​
    private void showLoadMore(boolean show) {
        if (newsAdapter != null) {
            newsAdapter.setLoading(show);
        }
    }
​
    private void showEmptyView(boolean show) {
        emptyView.setVisibility(show ? View.VISIBLE : View.GONE);
        recyclerView.setVisibility(show ? View.GONE : View.VISIBLE);
    }
​
    /**
     * 刷新数据
     */
    public void refresh() {
        if (swipeRefreshLayout != null && !swipeRefreshLayout.isRefreshing()) {
            swipeRefreshLayout.setRefreshing(true);
        }
        onRefresh();
    }
​
    /**
     * 获取当前新闻列表
     */
    public List<NewsItem> getNewsList() {
        return new ArrayList<>(newsList);
    }
}

"NewsListFragment实现了完整的新闻列表功能,"李工解释道,"包括下拉刷新、加载更多、空状态显示等。"

对应的布局文件:

ini 复制代码
<!-- fragment_news_list.xml -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/swipeRefreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
​
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
​
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"
            android:paddingTop="8dp"
            android:paddingBottom="8dp" />
​
        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:visibility="gone" />
​
        <LinearLayout
            android:id="@+id/emptyView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:orientation="vertical"
            android:visibility="gone">
​
            <ImageView
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_gravity="center_horizontal"
                android:src="@drawable/ic_empty_news"
                android:tint="@color/textSecondary" />
​
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="暂无新闻"
                android:textColor="@color/textSecondary"
                android:textSize="16sp" />
​
            <Button
                android:id="@+id/btnRetry"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="重新加载" />
​
        </LinearLayout>
​
    </FrameLayout>
​
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

第三步:创建NewsDetailFragment

"接下来是新闻详情Fragment:"

scss 复制代码
// NewsDetailFragment.java
public class NewsDetailFragment extends BaseFragment {
​
    private static final String ARG_NEWS_ITEM = "news_item";
    private static final String TAG = "NewsDetailFragment";
​
    // UI组件
    private TextView titleView;
    private TextView contentView;
    private TextView authorView;
    private TextView timeView;
    private ImageView coverImageView;
    private WebView webView;
    private FloatingActionButton fabFavorite;
    private FloatingActionButton fabShare;
    private ProgressBar progressBar;
​
    // 数据
    private NewsItem newsItem;
    private boolean isLoading = true;
​
    // 回调接口
    public interface OnDetailInteractionListener {
        void onNewsShare(NewsItem newsItem);
        void onNewsFavorite(NewsItem newsItem, boolean isFavorite);
        void onAuthorClick(String authorId);
    }
​
    private OnDetailInteractionListener listener;
​
    public static NewsDetailFragment newInstance(NewsItem newsItem) {
        NewsDetailFragment fragment = new NewsDetailFragment();
        Bundle args = new Bundle();
        args.putParcelable(ARG_NEWS_ITEM, newsItem);
        fragment.setArguments(args);
        return fragment;
    }
​
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof OnDetailInteractionListener) {
            listener = (OnDetailInteractionListener) context;
        }
    }
​
    @Override
    protected int getLayoutId() {
        return R.layout.fragment_news_detail;
    }
​
    @Override
    protected void initView() {
        titleView = rootView.findViewById(R.id.news_title);
        contentView = rootView.findViewById(R.id.news_content);
        authorView = rootView.findViewById(R.id.news_author);
        timeView = rootView.findViewById(R.id.news_time);
        coverImageView = rootView.findViewById(R.id.news_cover);
        webView = rootView.findViewById(R.id.news_webview);
        fabFavorite = rootView.findViewById(R.id.fab_favorite);
        fabShare = rootView.findViewById(R.id.fab_share);
        progressBar = rootView.findViewById(R.id.progressBar);
​
        // 设置WebView
        setupWebView();
​
        // 初始状态
        showLoading(true);
    }
​
    @Override
    protected void initData() {
        Bundle args = getArguments();
        if (args != null) {
            newsItem = args.getParcelable(ARG_NEWS_ITEM);
            if (newsItem != null) {
                loadNewsDetail();
            }
        }
    }
​
    @Override
    protected void initListener() {
        // 收藏按钮点击
        fabFavorite.setOnClickListener(v -> {
            if (newsItem != null && listener != null) {
                boolean newFavoriteState = !newsItem.isFavorite();
                newsItem.setFavorite(newFavoriteState);
                updateFavoriteIcon(newFavoriteState);
                listener.onNewsFavorite(newsItem, newFavoriteState);
            }
        });
​
        // 分享按钮点击
        fabShare.setOnClickListener(v -> {
            if (newsItem != null && listener != null) {
                listener.onNewsShare(newsItem);
            }
        });
​
        // 作者点击
        authorView.setOnClickListener(v -> {
            if (newsItem != null && listener != null) {
                listener.onAuthorClick(newsItem.getAuthorId());
            }
        });
    }
​
    private void setupWebView() {
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setDomStorageEnabled(true);
        webSettings.setAppCacheEnabled(true);
        webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
​
        // 启用缩放
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false);
​
        // 启用响应式布局
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);
​
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                isLoading = true;
                updateLoadingState();
            }
​
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                isLoading = false;
                updateLoadingState();
            }
​
            @Override
            public void onReceivedError(WebView view, int errorCode,
                                       String description, String failingUrl) {
                super.onReceivedError(view, errorCode, description, failingUrl);
                showError("加载失败:" + description);
            }
        });
    }
​
    private void loadNewsDetail() {
        if (newsItem == null) return;
​
        // 设置基本信息
        titleView.setText(newsItem.getTitle());
        authorView.setText("作者:" + newsItem.getAuthor());
        timeView.setText(formatTime(newsItem.getPublishTime()));
​
        // 加载封面图片
        if (!TextUtils.isEmpty(newsItem.getCoverImageUrl())) {
            Picasso.get()
                .load(newsItem.getCoverImageUrl())
                .placeholder(R.drawable.placeholder_news)
                .error(R.drawable.error_news)
                .into(coverImageView);
        } else {
            coverImageView.setVisibility(View.GONE);
        }
​
        // 更新收藏按钮状态
        updateFavoriteIcon(newsItem.isFavorite());
​
        // 加载详细内容
        loadDetailedContent();
    }
​
    private void loadDetailedContent() {
        // 模拟加载详细内容
        new Handler().postDelayed(() -> {
            if (getActivity() == null) return;
​
            // 如果有HTML内容,使用WebView显示
            if (!TextUtils.isEmpty(newsItem.getHtmlContent())) {
                loadHtmlContent(newsItem.getHtmlContent());
            } else {
                // 否则显示纯文本内容
                loadTextContent(newsItem.getContent());
            }
​
            isLoading = false;
            updateLoadingState();
​
        }, 1000);
    }
​
    private void loadHtmlContent(String htmlContent) {
        // 构建完整的HTML页面
        String html = buildHtmlPage(htmlContent);
        webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
​
        webView.setVisibility(View.VISIBLE);
        contentView.setVisibility(View.GONE);
    }
​
    private void loadTextContent(String textContent) {
        contentView.setText(textContent);
        contentView.setVisibility(View.VISIBLE);
        webView.setVisibility(View.GONE);
    }
​
    private String buildHtmlPage(String content) {
        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html>");
        html.append("<html>");
        html.append("<head>");
        html.append("<meta charset="UTF-8">");
        html.append("<meta name="viewport" content="width=device-width, initial-scale=1.0">");
        html.append("<style>");
        html.append("body { font-family: Arial, sans-serif; font-size: 16px; line-height: 1.6; margin: 16px; }");
        html.append("img { max-width: 100%; height: auto; }");
        html.append("p { margin: 16px 0; }");
        html.append("h1, h2, h3 { color: #333; }");
        html.append("</style>");
        html.append("</head>");
        html.append("<body>");
        html.append(content);
        html.append("</body>");
        html.append("</html>");
        return html.toString();
    }
​
    private void updateLoadingState() {
        if (isLoading) {
            showLoading(true);
        } else {
            showLoading(false);
        }
    }
​
    private void showLoading(boolean show) {
        progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
    }
​
    private void showError(String error) {
        if (getContext() != null) {
            Toast.makeText(getContext(), error, Toast.LENGTH_SHORT).show();
        }
    }
​
    private void updateFavoriteIcon(boolean isFavorite) {
        fabFavorite.setImageResource(isFavorite ?
            R.drawable.ic_favorite_filled : R.drawable.ic_favorite_border);
    }
​
    private String formatTime(long timestamp) {
        Date date = new Date(timestamp);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
        return format.format(date);
    }
​
    /**
     * 更新新闻内容
     */
    public void updateNews(NewsItem newsItem) {
        this.newsItem = newsItem;
        if (isAdded()) {
            loadNewsDetail();
        }
    }
​
    /**
     * 分享新闻
     */
    public void shareNews() {
        if (newsItem != null && listener != null) {
            listener.onNewsShare(newsItem);
        }
    }
}

Activity的重构

"现在Fragment都准备好了,让我们重构Activity:"李工开始创建新的Activity结构。

单Activity架构

"现代Android开发推荐使用单Activity + 多Fragment的架构,"李工解释道,"这样可以避免Activity之间跳转的开销,同时提供更流畅的用户体验。"

scss 复制代码
// MainActivity.java
public class MainActivity extends AppCompatActivity implements
        NewsListFragment.OnNewsInteractionListener,
        NewsDetailFragment.OnDetailInteractionListener {
​
    private static final String TAG = "MainActivity";
​
    // Fragment容器ID
    private static final int FRAGMENT_CONTAINER_ID = R.id.fragment_container;
​
    // 当前显示的Fragment
    private Fragment currentFragment;
​
    // 是否是平板布局
    private boolean isTwoPane;
​
    // Fragment引用
    private NewsListFragment newsListFragment;
    private NewsDetailFragment newsDetailFragment;
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
​
        // 根据设备类型选择不同的布局
        if (isTablet()) {
            setContentView(R.layout.activity_main_tablet);
            isTwoPane = true;
        } else {
            setContentView(R.layout.activity_main_phone);
            isTwoPane = false;
        }
​
        initFragments();
​
        if (savedInstanceState == null) {
            showNewsList();
        }
    }
​
    private boolean isTablet() {
        return getResources().getBoolean(R.bool.isTablet);
    }
​
    private void initFragments() {
        FragmentManager fm = getSupportFragmentManager();
​
        newsListFragment = (NewsListFragment) fm.findFragmentByTag("news_list");
        newsDetailFragment = (NewsDetailFragment) fm.findFragmentByTag("news_detail");
​
        if (newsListFragment == null) {
            newsListFragment = NewsListFragment.newInstance();
        }
​
        if (newsDetailFragment == null && isTwoPane) {
            newsDetailFragment = NewsDetailFragment.newInstance(null);
        }
    }
​
    private void showNewsList() {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
​
        if (isTwoPane) {
            // 平板:显示列表和详情
            if (newsDetailFragment == null) {
                newsDetailFragment = NewsDetailFragment.newInstance(null);
            }
​
            ft.replace(R.id.news_list_container, newsListFragment, "news_list");
            ft.replace(R.id.news_detail_container, newsDetailFragment, "news_detail");
        } else {
            // 手机:只显示列表
            ft.replace(FRAGMENT_CONTAINER_ID, newsListFragment, "news_list");
        }
​
        ft.commit();
        currentFragment = newsListFragment;
    }
​
    @Override
    public void onNewsSelected(NewsItem newsItem) {
        if (isTwoPane) {
            // 平板:更新详情Fragment
            if (newsDetailFragment != null) {
                newsDetailFragment.updateNews(newsItem);
            }
        } else {
            // 手机:替换为详情Fragment
            newsDetailFragment = NewsDetailFragment.newInstance(newsItem);
​
            getSupportFragmentManager()
                .beginTransaction()
                .replace(FRAGMENT_CONTAINER_ID, newsDetailFragment, "news_detail")
                .addToBackStack("news_detail")
                .commit();
​
            currentFragment = newsDetailFragment;
        }
    }
​
    @Override
    public void onNewsFavorite(NewsItem newsItem) {
        // 处理收藏逻辑
        NewsRepository.getInstance().updateFavoriteStatus(newsItem);
        Toast.makeText(this, "已" + (newsItem.isFavorite() ? "收藏" : "取消收藏"),
                      Toast.LENGTH_SHORT).show();
    }
​
    @Override
    public void onNewsShare(NewsItem newsItem) {
        // 处理分享逻辑
        shareNews(newsItem);
    }
​
    @Override
    public void onAuthorClick(String authorId) {
        // 跳转到作者页面
        AuthorFragment authorFragment = AuthorFragment.newInstance(authorId);
​
        getSupportFragmentManager()
            .beginTransaction()
            .replace(FRAGMENT_CONTAINER_ID, authorFragment, "author")
            .addToBackStack("author")
            .commit();
​
        currentFragment = authorFragment;
    }
​
    @Override
    public void onNewsFavorite(NewsItem newsItem, boolean isFavorite) {
        // 处理详情页面的收藏操作
        NewsRepository.getInstance().updateFavoriteStatus(newsItem);
        Toast.makeText(this, "已" + (isFavorite ? "收藏" : "取消收藏"),
                      Toast.LENGTH_SHORT).show();
    }
​
    private void shareNews(NewsItem newsItem) {
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("text/plain");
        shareIntent.putExtra(Intent.EXTRA_SUBJECT, newsItem.getTitle());
        shareIntent.putExtra(Intent.EXTRA_TEXT, newsItem.getTitle() + "\n" + newsItem.getShareUrl());
        startActivity(Intent.createChooser(shareIntent, "分享新闻"));
    }
​
    @Override
    public void onBackPressed() {
        FragmentManager fm = getSupportFragmentManager();
​
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
        } else {
            super.onBackPressed();
        }
    }
}

对应的布局文件:

xml 复制代码
<!-- activity_main_phone.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
xml 复制代码
<!-- activity_main_tablet.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:baselineAligned="false">

    <!-- 左侧:新闻列表 -->
    <FrameLayout
        android:id="@+id/news_list_container"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <!-- 右侧:新闻详情 -->
    <FrameLayout
        android:id="@+id/news_detail_container"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</LinearLayout>

重构完成:模块化的胜利

经过一下午的重构,NewsHub应用的架构焕然一新。小安看着重构后的代码,脸上露出了满意的笑容。

"李工,这简直太神奇了!"小安兴奋地说道,"代码重复率从70%降到了几乎为0,而且逻辑更清晰了!"

李工笑着点点头:"这就是Fragment的力量。让我们总结一下这次重构的成果:"

重构前后对比

重构前的问题:

  1. 代码重复率高(70%以上)
  2. 手机/平板逻辑混合在一个Activity中
  3. 难以维护和扩展
  4. 违反了单一职责原则

重构后的优势:

  1. 代码复用性大幅提升
ini 复制代码
// NewsListFragment在手机和平板上完全复用
NewsListFragment listFragment = NewsListFragment.newInstance();
  1. 职责分离更清晰
arduino 复制代码
// Activity:负责Fragment的组装和管理
// NewsListFragment:负责新闻列表的显示和交互
// NewsDetailFragment:负责新闻详情的显示和交互
  1. 扩展性更强
scss 复制代码
// 轻松添加新的Fragment
UserFragment userFragment = UserFragment.newInstance();
getSupportFragmentManager()
    .beginTransaction()
    .replace(R.id.fragment_container, userFragment)
    .addToBackStack("user")
    .commit();
  1. 测试更容易
java 复制代码
// 每个Fragment可以独立测试
@Test
public void testNewsListFragment() {
    NewsListFragment fragment = NewsListFragment.newInstance();
    // 测试Fragment的逻辑
}

代码质量指标对比

指标 重构前 重构后 改善幅度
代码重复率 70% 5% ↓93%
类的平均行数 450行 200行 ↓56%
圈复杂度 18 8 ↓56%
单元测试覆盖率 35% 85% ↑143%

Fragment设计原则总结

李工在白板上写下了Fragment的核心设计原则:

"记住这五个Fragment设计原则,你的代码会一直保持高质量:"

  1. 单一职责原则

    • 每个Fragment只负责一个特定的UI功能
    • NewsListFragment只管列表,NewsDetailFragment只管详情
  2. 依赖倒置原则

    • Fragment通过接口与Activity通信
    • 不直接依赖具体的Activity实现
  3. 开闭原则

    • 对扩展开放,对修改关闭
    • 通过添加新Fragment来扩展功能
  4. 组合优于继承

    • Fragment作为UI组件的组合单元
    • Activity通过组合Fragment来构建复杂界面
  5. 生命周期意识

    • 正确处理Fragment的生命周期
    • 避免在Fragment销毁后执行回调

展望未来

"小安,你觉得这次重构最大的收获是什么?"李工问道。

小安思考了一会儿,认真地回答:"我觉得最大的收获是理解了模块化思维。以前我总是想着把所有功能都写在一个Activity里,现在学会了把复杂的功能拆分成独立的、可复用的模块。"

"非常好!"李工赞许地点头,"这正是Fragment的核心思想。而且这只是一个开始,随着你学习的深入,你会发现还有更多精彩的内容:"

  1. Fragment通信的高级技巧

    • ViewModel + LiveData
    • Navigation Component
    • Shared ViewModel
  2. Fragment的性能优化

    • 懒加载机制
    • View缓存策略
    • 内存管理
  3. Fragment的进阶用法

    • 嵌套Fragment
    • ViewPager + Fragment
    • DialogFragment
  4. Fragment的最佳实践

    • 架构模式集成
    • 测试策略
    • 调试技巧

"从下节课开始,我们将深入探索Fragment的生命周期。"李工看了一下表,"你会发现,理解生命周期是掌握Fragment的关键。"

小安兴奋地看着屏幕上重构后的代码,仿佛看到了一个全新的世界。这次与Fragment的初识,不仅解决了他眼前的技术难题,更重要的是打开了一扇通往Android高级开发的大门。

夕阳西下,办公室的灯光陆续熄灭,但小安的编程热情却前所未有地高涨。他迫不及待地想要开始下一段学习旅程,探索Fragment这个强大组件的更多奥秘。

故事完


知识要点总结

Fragment的核心概念

  1. Fragment是什么:UI模块化组件,可以嵌入Activity中
  2. 为什么需要Fragment:解决多屏适配、代码复用、模块化开发等问题
  3. Fragment与Activity的关系:容器与模块的关系,Fragment依赖Activity

Fragment的创建方式

  1. 静态加载:在XML中直接声明,简单但不灵活
  2. 动态加载:在代码中通过FragmentTransaction管理,灵活性高
  3. 工厂模式:推荐的创建方式,确保参数安全和创建统一

Fragment的最佳实践

  1. BaseFragment设计:提取通用功能,减少代码重复
  2. 接口回调:Fragment与Activity的安全通信方式
  3. 生命周期管理:正确处理各个生命周期阶段
  4. 单Activity架构:现代Android开发的推荐模式

实战技能

  1. 重构现有代码:将Activity-Based架构重构为Fragment-Based
  2. 多屏适配:一套代码适配手机和平板
  3. 模块化设计:将复杂功能拆分为独立模块
  4. 代码质量提升:降低重复率,提高可维护性

本章通过NewsHub应用的实际案例,完整展示了Fragment从理论到实践的整个过程,为后续章节的深入学习奠定了坚实的基础。

相关推荐
成都大菠萝1 小时前
Android Auto开发(0)-引言
android
q***33372 小时前
SpringMVC新版本踩坑[已解决]
android·前端·后端
F***74172 小时前
数据库课设---酒店管理系统(MySQL、VBNet)
android·数据库·mysql
踢球的打工仔2 小时前
PHP面向对象(5)
android·java·php
zhaoyufei13314 小时前
Android13删除Taskbar
android
6***B4815 小时前
存储过程(SQL)
android·数据库·sql
学困昇17 小时前
C++中的异常
android·java·c++
Jerry17 小时前
问题记录 - Android IdleHandler 没有执行
android
没有了遇见17 小时前
Android ButterKnife Android 35情况下 适配 Gradle 8.+
android