Fragment实例精讲——底部导航栏+ViewPager滑动切换页面
感谢你提出“Fragment实例精讲——底部导航栏+ViewPager滑动切换页面”。结合你之前对Android四大组件(Activity
、Service
、BroadcastReceiver
、ContentProvider
)、Intent
、复杂数据传递以及Fragment
底部导航栏的实现(方法1:BottomNavigationView
直接切换,方法2:ViewPager2
+BottomNavigationView
,方法3:Jetpack Navigation)的学习,我推测你希望深入探索一种结合底部导航栏和ViewPager滑动切换页面的具体实现。由于你之前已探讨了方法2(ViewPager2
+BottomNavigationView
),本次请求可能是在方法2的基础上要求更详细的实例讲解,或希望进一步优化、扩展功能(例如复杂数据传递、动画效果、状态管理),或者你可能误将方法2称为“ViewPager”而非“ViewPager2”。为了保持一致性,我将基于ViewPager2(AndroidX的现代ViewPager)实现一个精讲实例,延续之前的场景(HomeFragment
、TaskFragment
、FileFragment
),并加入复杂数据传递(Parcelable
对象)、滑动动画和状态管理等高级功能。
如果你指的是旧版ViewPager
(非ViewPager2)或其他实现方式(如结合Navigation组件的滑动效果),或有特定需求(如与ViewModel、Room集成),请进一步说明,我可以调整方案。以下我将以通俗易懂的中文,详细讲解如何使用ViewPager2和BottomNavigationView
实现底部导航栏与滑动切换页面,包含完整代码、步骤解析和优化技巧,并整合Intent
、Service
、BroadcastReceiver
和DocumentsProvider
。
一、ViewPager2 + BottomNavigationView + Fragment 精讲
1. 项目概述
- 目标:实现一个底部导航栏,包含三个导航项:首页(
HomeFragment
)、任务(TaskFragment
)、文件(FileFragment
),支持点击导航项和滑动切换页面。 - 功能:
- HomeFragment:显示欢迎信息,接收通过
Intent
传递的复杂数据(User
对象)。 - TaskFragment:输入任务,添加至列表,启动
LogService
记录,发送广播通知CustomReceiver
,传递Parcelable
任务列表。 - FileFragment:打开文件选择器(结合
DocumentsProvider
),显示选中的文件URI。 - 高级特性:
- 复杂数据传递:通过
Intent
传递User
对象和List<Task>
(参考“Intent之复杂数据传递”)。 - 滑动动画:为ViewPager2添加缩放和渐变动画。
- 状态管理:保存和恢复导航状态及任务列表。
- 组件交互:
- 使用
Intent
启动Service
、发送广播、传递复杂数据。 - 集成
DocumentsProvider
(参考“ContentProvider再探”)选择文件。 - 场景:用户通过点击或滑动切换页面,执行任务管理、文件选择等操作,体验流畅的导航和数据交互。
2. 与之前方法的对比
- 方法2(ViewPager2):已实现滑动切换和底部导航,本次精讲将增强功能(复杂数据、动画、状态管理)。
- 方法1(BottomNavigationView):无滑动效果,适合简单场景。
- 方法3(Jetpack Navigation):声明式导航,适合复杂导航,但滑动需额外配置。
- 本次实现:基于方法2(ViewPager2),优化为更完整的实例,强调复杂数据传递和用户体验。
二、实现步骤
步骤1:添加依赖
在app/build.gradle
中添加AndroidX、ViewPager2和Material Design依赖:
dependencies { implementation ‘androidx.appcompat:appcompat:1.6.1’ implementation ‘com.google.android.material:material:1.9.0’ implementation ‘androidx.fragment:fragment:1.5.7’ implementation ‘androidx.viewpager2:viewpager2:1.0.0’ }
步骤2:定义复杂数据类(Parcelable)
创建User
和Task
类,用于传递复杂数据。
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
@Override
public int describeContents() {
return 0;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
import android.os.Parcel;
import android.os.Parcelable;
public class Task implements Parcelable {
private String title;
private String status;
public Task(String title, String status) {
this.title = title;
this.status = status;
}
protected Task(Parcel in) {
title = in.readString();
status = in.readString();
}
public static final Creator<Task> CREATOR = new Creator<Task>() {
@Override
public Task createFromParcel(Parcel in) {
return new Task(in);
}
@Override
public Task[] newArray(int size) {
return new Task[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(title);
dest.writeString(status);
}
@Override
public int describeContents() {
return 0;
}
public String getTitle() {
return title;
}
public String getStatus() {
return status;
}
}
步骤3:创建Fragment
实现HomeFragment
、TaskFragment
、FileFragment
,支持复杂数据传递和状态管理。
HomeFragment(显示欢迎信息和复杂数据):
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
public class HomeFragment extends Fragment {
private static final String ARG_USER = “user”;
public static HomeFragment newInstance(User user) {
HomeFragment fragment = new HomeFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_USER, user);
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
TextView welcomeText = view.findViewById(R.id.welcome_text);
// 获取复杂数据
User user = getArguments() != null ? getArguments().getParcelable(ARG_USER) : null;
String welcomeMessage = user != null
? "欢迎, " + user.getName() + " (年龄: " + user.getAge() + ")"
: "欢迎使用应用!";
welcomeText.setText(welcomeMessage);
return view;
}
}
TaskFragment(任务管理,传递复杂数据):
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
import java.util.ArrayList;
public class TaskFragment extends Fragment {
private TextView taskListText;
private ArrayList taskList = new ArrayList<>();
private static final String ARG_TASKS = “tasks”;
public static TaskFragment newInstance(ArrayList<Task> tasks) {
TaskFragment fragment = new TaskFragment();
Bundle args = new Bundle();
args.putParcelableArrayList(ARG_TASKS, tasks);
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_task, container, false);
EditText taskInput = view.findViewById(R.id.task_input);
Button addTaskBtn = view.findViewById(R.id.add_task_btn);
taskListText = view.findViewById(R.id.task_list_text);
// 恢复任务列表
if (savedInstanceState != null) {
taskList = savedInstanceState.getParcelableArrayList(ARG_TASKS);
} else if (getArguments() != null) {
taskList = getArguments().getParcelableArrayList(ARG_TASKS);
}
updateTaskListText();
addTaskBtn.setOnClickListener(v -> {
String taskTitle = taskInput.getText().toString();
if (!taskTitle.isEmpty()) {
Task newTask = new Task(taskTitle, "进行中");
taskList.add(newTask);
updateTaskListText();
// 启动Service
Intent serviceIntent = new Intent(getActivity(), LogService.class);
serviceIntent.putExtra("log_message", "添加任务: " + taskTitle);
getActivity().startService(serviceIntent);
// 发送广播
Intent broadcastIntent = new Intent("com.example.CUSTOM_ACTION");
broadcastIntent.putExtra("task", newTask);
getActivity().sendBroadcast(broadcastIntent);
// 传递任务列表到HomeFragment(通过Activity)
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.putExtra("user", new User(taskTitle, 25));
intent.putParcelableArrayListExtra("tasks", taskList);
getActivity().startActivity(intent);
taskInput.setText("");
}
});
return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(ARG_TASKS, taskList);
}
private void updateTaskListText() {
StringBuilder displayText = new StringBuilder("任务列表:\n");
for (Task task : taskList) {
displayText.append("- ").append(task.getTitle()).append(": ").append(task.getStatus()).append("\n");
}
taskListText.setText(displayText.toString());
}
}
FileFragment(文件选择):
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.fragment.app.Fragment; public class FileFragment extends Fragment {
private static final int REQUEST_OPEN_DOCUMENT = 1;
private TextView fileText; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_file, container, false); Button openFileBtn = view.findViewById(R.id.open_file_btn); fileText = view.findViewById(R.id.file_text); // 恢复状态 if (savedInstanceState != null) { fileText.setText(savedInstanceState.getString("file_uri", "未选择文件")); } openFileBtn.setOnClickListener(v -> { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/plain"); startActivityForResult(intent, REQUEST_OPEN_DOCUMENT); }); return view; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_OPEN_DOCUMENT && resultCode == RESULT_OK && data != null) { fileText.setText("选择的文件: " + data.getData()); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("file_uri", fileText.getText().toString()); }
}
步骤4:创建ViewPager2适配器 实现FragmentStateAdapter
,支持复杂数据传递。
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.ArrayList; public class ViewPagerAdapter extends FragmentStateAdapter {
private User user;
private ArrayList tasks; public ViewPagerAdapter(FragmentActivity fragmentActivity, User user, ArrayList<Task> tasks) { super(fragmentActivity); this.user = user; this.tasks = tasks; } @Override public Fragment createFragment(int position) { switch (position) { case 0: return HomeFragment.newInstance(user); case 1: return TaskFragment.newInstance(tasks); case 2: return new FileFragment(); default: return HomeFragment.newInstance(user); } } @Override public int getItemCount() { return 3; } public void updateData(User user, ArrayList<Task> tasks) { this.user = user; this.tasks = tasks; notifyDataSetChanged(); }
}
步骤5:创建MainActivity 整合ViewPager2
和BottomNavigationView
,添加滑动动画和状态管理。
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.ArrayList; public class MainActivity extends AppCompatActivity {
private ViewPagerAdapter adapter;
private ViewPager2 viewPager;
private BottomNavigationView bottomNav;
private User user;
private ArrayList tasks; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); viewPager = findViewById(R.id.view_pager); bottomNav = findViewById(R.id.bottom_navigation); // 初始化数据 user = new User("Guest", 0); tasks = new ArrayList<>(); if (savedInstanceState != null) { user = savedInstanceState.getParcelable("user"); tasks = savedInstanceState.getParcelableArrayList("tasks"); } else { Intent intent = getIntent(); if (intent != null) { user = intent.getParcelableExtra("user"); tasks = intent.getParcelableArrayListExtra("tasks"); if (tasks == null) tasks = new ArrayList<>(); } } // 设置ViewPager2 adapter = new ViewPagerAdapter(this, user, tasks); viewPager.setAdapter(adapter); viewPager.setPageTransformer(new ZoomOutPageTransformer()); // ViewPager2滑动监听 viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override public void onPageSelected(int position) { switch (position) { case 0: bottomNav.setSelectedItemId(R.id.nav_home); break; case 1: bottomNav.setSelectedItemId(R.id.nav_task); break; case 2: bottomNav.setSelectedItemId(R.id.nav_file); break; } } }); // BottomNavigationView点击监听 bottomNav.setOnItemSelectedListener(item -> { int itemId = item.getItemId(); if (itemId == R.id.nav_home) { viewPager.setCurrentItem(0); } else if (itemId == R.id.nav_task) { viewPager.setCurrentItem(1); } else if (itemId == R.id.nav_file) { viewPager.setCurrentItem(2); } return true; }); // 恢复导航状态 if (savedInstanceState != null) { int selectedNav = savedInstanceState.getInt("selected_nav", R.id.nav_home); bottomNav.setSelectedItemId(selectedNav); viewPager.setCurrentItem(getPositionForNavItem(selectedNav)); } } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); user = intent.getParcelableExtra("user"); tasks = intent.getParcelableArrayListExtra("tasks"); if (tasks == null) tasks = new ArrayList<>(); adapter.updateData(user, tasks); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("selected_nav", bottomNav.getSelectedItemId()); outState.putParcelable("user", user); outState.putParcelableArrayList("tasks", tasks); } private int getPositionForNavItem(int itemId) { if (itemId == R.id.nav_home) return 0; if (itemId == R.id.nav_task) return 1; if (itemId == R.id.nav_file) return 2; return 0; }
}
滑动动画:
import android.view.View;
import androidx.viewpager2.widget.ViewPager2; public class ZoomOutPageTransformer implements ViewPager2.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f; @Override public void transformPage(View page, float position) { int pageWidth = page.getWidth(); if (position < -1) { // 页面完全滑出左边 page.setAlpha(0f); } else if (position <= 1) { // 页面在滑动范围内 float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); float vertMargin = pageWidth * (1 - scaleFactor) / 2; float horzMargin = pageWidth * (1 - scaleFactor) / 2; if (position < 0) { page.setTranslationX(horzMargin - vertMargin / 2); } else { page.setTranslationX(-horzMargin + vertMargin / 2); } page.setScaleX(scaleFactor); page.setScaleY(scaleFactor); page.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); } else { // 页面完全滑出右边 page.setAlpha(0f); } }
}
步骤6:复用Service和BroadcastReceiver 复用之前的LogService
和CustomReceiver
。
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException; public class LogService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String message = intent.getStringExtra(“log_message”);
if (message != null) {
try {
File file = new File(getFilesDir(), “app_log.txt”);
FileWriter writer = new FileWriter(file, true);
writer.append(message).append(“\n”);
writer.close();
Log.d(“LogService”, “记录日志: ” + message);
} catch (IOException e) {
Log.e(“LogService”, “日志记录失败”, e);
}
}
return START_STICKY;
} @Override public IBinder onBind(Intent intent) { return null; }
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log; public class CustomReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Task task = intent.getParcelableExtra(“task”);
String message = task != null ? “新任务: ” + task.getTitle() + ” (” + task.getStatus() + “)” : “无效任务”;
Log.d(“CustomReceiver”, “收到广播: ” + message);
}
}
步骤7:复用DocumentsProvider 复用之前的TextDocumentsProvider
(参考“ContentProvider再探”)。 步骤8:注册组件 在AndroidManifest.xml
中声明: 步骤9:运行与测试 部署应用,启动MainActivity
,验证底部导航栏是否显示“首页”“任务”“文件”。 滑动或点击导航项,检查是否平滑切换到HomeFragment
、TaskFragment
、FileFragment
,验证缩放动画效果。 在TaskFragment
输入任务,验证: 任务是否添加到列表。 LogService
是否记录日志(检查app_log.txt
)。 CustomReceiver
是否在Logcat打印广播(包含Task
对象)。 HomeFragment
是否显示新传递的User
对象。 在FileFragment
打开文件选择器,验证是否显示TextDocumentsProvider
的文件并返回URI。 测试屏幕旋转,检查任务列表和导航状态是否正确恢复。 三、实现的关键点 1. ViewPager2配置 适配器:FragmentStateAdapter
支持动态创建Fragment,传递复杂数据(User
、List<Task>
)。 滑动动画:使用ZoomOutPageTransformer
实现缩放和渐变效果。 缓存控制: viewPager.setOffscreenPageLimit(2); // 缓存相邻页面
2. 复杂数据传递 通过Bundle:Fragment使用newInstance
接收Parcelable
数据。 通过Intent:TaskFragment
通过Intent
传递User
和List<Task>
到MainActivity
,更新ViewPagerAdapter
。 注意事项: 确保Parcelable
实现正确,避免ClassCastException
。 检查1MB数据限制,超大数据用URI或数据库。 3. 状态管理 Fragment状态:在onSaveInstanceState
保存任务列表和文件URI。 Activity状态:保存导航位置和复杂数据: outState.putInt("selected_nav", bottomNav.getSelectedItemId()); outState.putParcelable("user", user); outState.putParcelableArrayList("tasks", tasks);
恢复:在onCreate
或onNewIntent
恢复数据,更新适配器。 4. 与四大组件结合 Intent:TaskFragment
启动Service
、发送广播,传递Parcelable
。 Service:异步记录任务日志。 BroadcastReceiver:接收Task
对象,打印日志。 DocumentsProvider:提供文件访问,结合SAF。 5. 性能与优化 性能: 设置setOffscreenPageLimit
控制缓存,平衡内存和性能。 使用notifyDataSetChanged
动态更新数据。 用户体验: 滑动动画提升交互感。 禁用滑动(可选):viewPager.setUserInputEnabled(false);
屏幕旋转:自动恢复导航和数据状态。 6. 与方法1-3对比 方法1:简单,无滑动,适合轻量场景。 方法2(本次):滑动切换,缓存Fragment,适合动态交互,增强复杂数据和动画。 方法3:声明式导航,适合复杂场景,但滑动需额外配置。 选择建议:本次方法适合需要滑动效果和复杂数据传递的中型项目。 7. 常见问题 滑动与点击不同步:确保ViewPager2
和BottomNavigationView
监听逻辑一致。 状态丢失:检查onSaveInstanceState
和恢复逻辑。 性能问题:限制缓存页面数,优化Parcelable
数据大小。 四、学习与实践资源(中文) 官方文档: Android开发者 – ViewPager2 Android开发者 – BottomNavigationView 书籍:《Android编程权威指南》(中文版,Big Nerd Ranch出品) 课程:B站或慕课网,搜索“Android ViewPager2”。 实践: 添加更多动画效果(如淡入淡出)。 结合ViewModel管理任务列表。 五、下一步 为了更精准地讲解,请告诉我: 你是否特指旧版ViewPager
或有其他实现方式? 你希望聚焦的功能(复杂数据、动画、状态管理、ViewModel)? 当前技术水平(熟悉ViewPager2、需要更多优化技巧)? 是否需要扩展功能(如与Room、ViewModel集成)? 是否探索其他方法(如自定义View或Navigation滑动)? 你可以基于示例扩展功能(如添加ViewModel或更多动画),或提出具体问题,我会提供更深入的代码或分析!