如何在父类实现通用视图与代码,在子Fragment中复用?
如何通过基类Fragment复用通用视图逻辑?
我有3个Fragment,它们包含4个通用组件:1个TextView、2个Button、1个ProgressBar。不想在每个Fragment中重复调用findViewById、setText或setOnClickListener等相同逻辑,希望将这些代码统一放在基类Fragment中,让3个Fragment继承该基类,但不清楚具体实现方式。比如是否需要将这些通用视图放在XML中,在基类Fragment的onCreateView方法中加载,并在onViewCreated中编写逻辑?
现有代码
单个Fragment的布局XML
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/borde" tools:context=".PartidosFragment"> <!-- Common views --> <ProgressBar android:id="@+id/progress_bar" style="?android:attr/progressBarStyle" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="84dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/anterior_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/anterior_test" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/actual_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/actual_test" android:textSize="24sp" app:layout_constraintBottom_toBottomOf="@+id/anterior_button" app:layout_constraintEnd_toStartOf="@+id/siguiente_button" app:layout_constraintStart_toEndOf="@+id/anterior_button" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/siguiente_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sigueinte_test" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <!-- End of common views --> <!-- More views unique to each Fragment --> </androidx.constraintlayout.widget.ConstraintLayout>
通用Java逻辑代码
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); this.progressBar = view.findViewById(R.id.progress_bar); this.actTv = view.findViewById(R.id.actual_tv); this.antBt = view.findViewById(R.id.anterior_button); this.sigBt = view.findViewById(R.id.siguiente_button); this.antBt.setOnClickListener(this); this.sigBt.setOnClickListener(this); this.actTv.setText(Temporada.getTemporadaFormateada(String .valueOf(this.temporada))); // 如果是最早赛季,隐藏上一赛季按钮 if (this.temporada != MyApplication.oldestYear) { this.antBt.setText(String.format("< %s", Temporada .getTemporadaFormateada(String.valueOf(this.temporada - 1)))); this.antBt.setVisibility(View.VISIBLE); } else { this.antBt.setVisibility(View.INVISIBLE); } // 如果是最新赛季,隐藏下一赛季按钮 if (this.temporada != MyApplication.newestYear) { this.sigBt.setText(String.format("%s >", Temporada .getTemporadaFormateada(String.valueOf(this.temporada + 1)))); this.sigBt.setVisibility(View.VISIBLE); } else { this.sigBt.setVisibility(View.INVISIBLE); } }
解决方案
1. 提取通用布局(消除XML重复)
把通用视图单独抽成一个布局文件layout/common_season_controls.xml:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <ProgressBar android:id="@+id/progress_bar" style="?android:attr/progressBarStyle" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="84dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/anterior_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/anterior_test" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/actual_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/actual_test" android:textSize="24sp" app:layout_constraintBottom_toBottomOf="@+id/anterior_button" app:layout_constraintEnd_toStartOf="@+id/siguiente_button" app:layout_constraintStart_toEndOf="@+id/anterior_button" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/siguiente_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sigueinte_test" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
然后在每个Fragment的布局中用<include>引入这个通用布局,替换原来的通用视图部分:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/borde" tools:context=".PartidosFragment"> <!-- 引入通用布局 --> <include layout="@layout/common_season_controls" /> <!-- More views unique to each Fragment --> </androidx.constraintlayout.widget.ConstraintLayout>
2. 创建基类Fragment
创建BaseSeasonFragment,把通用逻辑全部封装在这里,同时给子类留出扩展点:
import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; public abstract class BaseSeasonFragment extends Fragment implements View.OnClickListener { protected ProgressBar progressBar; protected TextView actTv; protected Button antBt; protected Button sigBt; // 抽象方法:让子类提供当前赛季值 protected abstract int getCurrentSeason(); // 抽象方法:让子类处理上一赛季点击事件 protected abstract void onPreviousSeasonClicked(); // 抽象方法:让子类处理下一赛季点击事件 protected abstract void onNextSeasonClicked(); @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 子类需重写此方法加载自身布局 return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 初始化通用视图 initCommonViews(view); // 设置通用逻辑 setupCommonLogic(); } private void initCommonViews(View view) { progressBar = view.findViewById(R.id.progress_bar); actTv = view.findViewById(R.id.actual_tv); antBt = view.findViewById(R.id.anterior_button); sigBt = view.findViewById(R.id.siguiente_button); antBt.setOnClickListener(this); sigBt.setOnClickListener(this); } private void setupCommonLogic() { int currentSeason = getCurrentSeason(); // 更新当前赛季文本 actTv.setText(Temporada.getTemporadaFormateada(String.valueOf(currentSeason))); // 处理上一赛季按钮状态 if (currentSeason != MyApplication.oldestYear) { antBt.setText(String.format("< %s", Temporada.getTemporadaFormateada(String.valueOf(currentSeason - 1)))); antBt.setVisibility(View.VISIBLE); } else { antBt.setVisibility(View.INVISIBLE); } // 处理下一赛季按钮状态 if (currentSeason != MyApplication.newestYear) { sigBt.setText(String.format("%s >", Temporada.getTemporadaFormateada(String.valueOf(currentSeason + 1)))); sigBt.setVisibility(View.VISIBLE); } else { sigBt.setVisibility(View.INVISIBLE); } } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.anterior_button) { onPreviousSeasonClicked(); // 点击后刷新按钮状态 setupCommonLogic(); } else if (id == R.id.siguiente_button) { onNextSeasonClicked(); // 点击后刷新按钮状态 setupCommonLogic(); } } // 公共方法:子类切换赛季后可调用此方法刷新控件状态 protected void refreshSeasonControls() { setupCommonLogic(); } }
3. 子类Fragment继承基类
以PartidosFragment为例,继承BaseSeasonFragment并实现抽象方法:
import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; public class PartidosFragment extends BaseSeasonFragment { private int temporada; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 加载自身布局(已包含通用布局) return inflater.inflate(R.layout.fragment_partidos, container, false); } @Override protected int getCurrentSeason() { return temporada; } @Override protected void onPreviousSeasonClicked() { // 子类自定义上一赛季逻辑 temporada--; loadSeasonData(temporada); } @Override protected void onNextSeasonClicked() { // 子类自定义下一赛季逻辑 temporada++; loadSeasonData(temporada); } // 子类独有的数据加载方法 private void loadSeasonData(int season) { // 实现自身的数据加载逻辑 } }
内容的提问来源于stack exchange,提问作者xerez
相关产品推荐
相关产品推荐

