Fragment实例精讲——底部导航栏+ViewPager滑动切换页面

感谢你提出“Fragment实例精讲——底部导航栏+ViewPager滑动切换页面”。结合你之前对Android四大组件(ActivityServiceBroadcastReceiverContentProvider)、Intent、复杂数据传递以及Fragment底部导航栏的实现(方法1:BottomNavigationView直接切换,方法2:ViewPager2+BottomNavigationView,方法3:Jetpack Navigation)的学习,我推测你希望深入探索一种结合底部导航栏ViewPager滑动切换页面的具体实现。由于你之前已探讨了方法2(ViewPager2+BottomNavigationView),本次请求可能是在方法2的基础上要求更详细的实例讲解,或希望进一步优化、扩展功能(例如复杂数据传递、动画效果、状态管理),或者你可能误将方法2称为“ViewPager”而非“ViewPager2”。为了保持一致性,我将基于ViewPager2(AndroidX的现代ViewPager)实现一个精讲实例,延续之前的场景(HomeFragmentTaskFragmentFileFragment),并加入复杂数据传递Parcelable对象)、滑动动画状态管理等高级功能。

如果你指的是旧版ViewPager(非ViewPager2)或其他实现方式(如结合Navigation组件的滑动效果),或有特定需求(如与ViewModel、Room集成),请进一步说明,我可以调整方案。以下我将以通俗易懂的中文,详细讲解如何使用ViewPager2BottomNavigationView实现底部导航栏与滑动切换页面,包含完整代码、步骤解析和优化技巧,并整合IntentServiceBroadcastReceiverDocumentsProvider


一、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)

创建UserTask类,用于传递复杂数据。


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

实现HomeFragmentTaskFragmentFileFragment,支持复杂数据传递和状态管理。

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 整合ViewPager2BottomNavigationView,添加滑动动画和状态管理。
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 复用之前的LogServiceCustomReceiver
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,验证底部导航栏是否显示“首页”“任务”“文件”。 滑动或点击导航项,检查是否平滑切换到HomeFragmentTaskFragmentFileFragment,验证缩放动画效果。 在TaskFragment输入任务,验证: 任务是否添加到列表。 LogService是否记录日志(检查app_log.txt)。 CustomReceiver是否在Logcat打印广播(包含Task对象)。 HomeFragment是否显示新传递的User对象。 在FileFragment打开文件选择器,验证是否显示TextDocumentsProvider的文件并返回URI。 测试屏幕旋转,检查任务列表和导航状态是否正确恢复。 三、实现的关键点 1. ViewPager2配置 适配器FragmentStateAdapter支持动态创建Fragment,传递复杂数据(UserList<Task>)。 滑动动画:使用ZoomOutPageTransformer实现缩放和渐变效果。 缓存控制viewPager.setOffscreenPageLimit(2); // 缓存相邻页面 2. 复杂数据传递 通过Bundle:Fragment使用newInstance接收Parcelable数据。 通过IntentTaskFragment通过Intent传递UserList<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); 恢复:在onCreateonNewIntent恢复数据,更新适配器。 4. 与四大组件结合 IntentTaskFragment启动Service、发送广播,传递ParcelableService:异步记录任务日志。 BroadcastReceiver:接收Task对象,打印日志。 DocumentsProvider:提供文件访问,结合SAF。 5. 性能与优化 性能: 设置setOffscreenPageLimit控制缓存,平衡内存和性能。 使用notifyDataSetChanged动态更新数据。 用户体验: 滑动动画提升交互感。 禁用滑动(可选):viewPager.setUserInputEnabled(false); 屏幕旋转:自动恢复导航和数据状态。 6. 与方法1-3对比 方法1:简单,无滑动,适合轻量场景。 方法2(本次):滑动切换,缓存Fragment,适合动态交互,增强复杂数据和动画。 方法3:声明式导航,适合复杂场景,但滑动需额外配置。 选择建议:本次方法适合需要滑动效果和复杂数据传递的中型项目。 7. 常见问题 滑动与点击不同步:确保ViewPager2BottomNavigationView监听逻辑一致。 状态丢失:检查onSaveInstanceState和恢复逻辑。 性能问题:限制缓存页面数,优化Parcelable数据大小。 四、学习与实践资源(中文) 官方文档Android开发者 – ViewPager2 Android开发者 – BottomNavigationView 书籍:《Android编程权威指南》(中文版,Big Nerd Ranch出品) 课程:B站或慕课网,搜索“Android ViewPager2”。 实践: 添加更多动画效果(如淡入淡出)。 结合ViewModel管理任务列表。 五、下一步 为了更精准地讲解,请告诉我: 你是否特指旧版ViewPager或有其他实现方式? 你希望聚焦的功能(复杂数据、动画、状态管理、ViewModel)? 当前技术水平(熟悉ViewPager2、需要更多优化技巧)? 是否需要扩展功能(如与Room、ViewModel集成)? 是否探索其他方法(如自定义View或Navigation滑动)? 你可以基于示例扩展功能(如添加ViewModel或更多动画),或提出具体问题,我会提供更深入的代码或分析!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注