如何在ViewPager和TabLayout中复用Fragment类?多标签崩溃排查
问题根源分析
你遇到的空指针问题,核心原因是Fragment的生命周期时机不对:
当你在ChartBuilder.createTab()里创建GraphFragment并直接调用graphFragment.getGraphLayout()时,这个Fragment的视图还没有被创建出来。
- 单个标签页时,ViewPager会立刻加载这个唯一的Fragment,它的
onCreateView和onViewCreated会马上执行,graphLayout被正常初始化,所以能拿到有效引用; - 多个标签页时,ViewPager默认只会预加载当前页面左右各1个Fragment,后面的Fragment暂时不会触发视图创建流程,此时
graphLayout还是null,调用graphLayout.addView(lineChart)自然就会崩溃。
而且从设计角度来说,外部代码直接操作Fragment内部的View是不合理的,Fragment的视图应该由它自己管理,外部只需要传递必要的数据即可。
解决方案
我们需要把图表创建的逻辑移到GraphFragment内部,让Fragment自己负责初始化视图和创建图表,外部只传递数据。具体步骤如下:
1. 改造GraphFragment,让它能接收数据并创建图表
修改GraphFragment.java,添加存储图表数据的变量、设置数据的方法,然后在视图创建完成后自动初始化图表:
public class GraphFragment extends Fragment { private RelativeLayout graphLayout; // 新增:存储图表所需数据 private JSONObject xValues; private JSONObject yValues; private String label; private Context mContext; public GraphFragment() { } // 新增:设置图表数据的方法 public void setChartData(JSONObject xValues, JSONObject yValues, String label, Context context) { this.xValues = xValues; this.yValues = yValues; this.label = label; this.mContext = context; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_graph, container, false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { graphLayout = view.findViewById(R.id.graphLayout); // 视图创建完成后,自动初始化图表 if (xValues != null && yValues != null && label != null) { initChart(); } } // 新增:图表初始化逻辑 private void initChart() { LineChart lineChart = new LineChart(mContext); try { LineData lineData = new LineData(createDataSet()); lineChart.setData(lineData); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT ); lineChart.setLayoutParams(layoutParams); lineChart.invalidate(); graphLayout.addView(lineChart); } catch (JSONException e) { e.printStackTrace(); } } // 原有的createDataSet方法移到这里 private LineDataSet createDataSet() throws JSONException { List<Entry> entries = new ArrayList<Entry>(); JSONArray names = xValues.names(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); long timeReference = 0; long newXvalue; for(int i=0; i<names.length(); i++){ try{ if(i==0){ Date date = simpleDateFormat.parse(xValues.getString(names.getString(i))); timeReference = date.getTime(); } Date date = simpleDateFormat.parse(xValues.getString(names.getString(i))); Timestamp timestamp = new Timestamp(date.getTime()); newXvalue = timestamp.getTime()- timeReference; entries.add(new Entry(newXvalue, yValues.getLong(names.getString(i)))); }catch (ParseException p){ p.printStackTrace(); } } LineDataSet lineDataSet = new LineDataSet(entries, this.label); lineDataSet.setColor(mContext.getResources().getColor(R.color.colorPrimary)); lineDataSet.setValueTextColor(mContext.getResources().getColor(R.color.colorAccent)); return lineDataSet; } }
2. 修改ChartBuilder,不再直接操作Fragment视图
调整ChartBuilder.java的逻辑,只负责创建Fragment并传递数据,然后添加到ViewPager:
public class ChartBuilder { private JSONObject xValues; private JSONObject yValues; private String label; private MainActivity mActivity; private String tabLabel; public ChartBuilder(JSONObject xValues, JSONObject yValues, String label, MainActivity mActivity, String tabLabel) { this.xValues = xValues; this.yValues = yValues; this.label = label; this.mActivity = mActivity; this.tabLabel = tabLabel; } private void createTab(){ GraphFragment graphFragment = new GraphFragment(); // 传递数据给Fragment,不再直接操作视图 graphFragment.setChartData(xValues, yValues, label, mActivity); mActivity.addGraphFragment(graphFragment, tabLabel); } public void createChart() throws JSONException { createTab(); } }
3. 优化ViewPagerAdapter的更新逻辑
默认的FragmentPagerAdapter调用notifyDataSetChanged时可能不会正确更新Fragment,建议在你的ViewPagerAdapter里重写getItemPosition方法:
public class ViewPagerAdapter extends FragmentPagerAdapter { private final List<Fragment> fragmentList = new ArrayList<>(); private final List<String> titleList = new ArrayList<>(); public ViewPagerAdapter(FragmentManager fm) { super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); } @Override public Fragment getItem(int position) { return fragmentList.get(position); } @Override public int getCount() { return fragmentList.size(); } public void addFragment(Fragment fragment, String title) { fragmentList.add(fragment); titleList.add(title); } @Override public CharSequence getPageTitle(int position) { return titleList.get(position); } // 新增:强制Adapter更新所有Fragment @Override public int getItemPosition(@NonNull Object object) { return POSITION_NONE; } public void removeFragments() { fragmentList.clear(); titleList.clear(); } }
为什么这样能解决问题?
- 现在图表的创建逻辑完全由
GraphFragment自己控制,只有当它的视图创建完成(onViewCreated执行)后,才会去初始化图表并添加到布局里,从根本上避免了空指针; - 遵循了Fragment的设计原则:Fragment负责自己的视图和逻辑,外部只传递数据,代码结构更清晰,也更符合Android的生命周期规范。
内容的提问来源于stack exchange,提问作者David Alejandro
相关产品推荐
相关产品推荐

