Android TabLayout 片段仅在切换到特定片段后更新

Android TabLayout Fragment Only Updates After Switching to Specific Fragment

背景

我遇到了 Android 项目中片段更新的问题。该应用程序为 D&D 玩家掷骰子。与 Java Math.random 方法相比,该项目使用 Random.Org 中的 API 来提供更大的随机数生成熵。

该应用程序显示带有视图寻呼机的选项卡布局,它在三个选项卡之间切换 - 一个用于掷单个骰子,一个用于掷一组骰子,另一个用于显示掷骰历史和日志信息。 Roll Die在位置0,Roll Set在位置1,History在位置2。View Pager使用了FragmentPagerAdapter。

History 包含两个 TextView,我用它们来显示滚动结果和记录数据,以确保与 API 的连接顺利进行。数据通过两个接口从 RollDie 和 RollSet 片段传递到 Main Activity。然后接口方法将数据发送到 FragmentPagerAdapter,后者又通过 bundle 将两个 String ArrayLists 传递到 History 片段。

问题

我的问题是“历史记录”选项卡似乎仅在加载单个骰子选项卡后才会更新。当我掷一个骰子然后检查历史页面时,相关的 TextViews 会按应有的方式显示。但是,当我掷一组时,历史页面不会更新,直到我 return 到单个骰子滚动选项卡。

非常感谢对此问题的任何帮助。相关代码post编在下面。

来自 API 的代码可以在 Github 上找到。我还遗漏了我写的两个 类,称为 Die 和 DieSet - 它们工作得很好,我认为这已经足够长了。我很乐意 post 那些 类 如果有人认为它会有所帮助。

与 API 键相关的代码以及一些 IDE 生成的注释已被省略。

主要Activity

public class MainActivity extends AppCompatActivity implements DieRollFragment.RollFragmentListener, DieSetFragment.SetFragmentListener{

    private static String[] key;
    TabLayout tabLayout;
    ViewPager viewPager;
    PagerAdapter pagerAdapter;

    protected void onStart() {
        super.onStart();
        userKeyDialog();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tabLayout = findViewById(R.id.navTabs);
        viewPager = findViewById(R.id.viewPager);
        pagerAdapter = new PagerAdapter(getSupportFragmentManager(), BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, tabLayout.getTabCount());
        viewPager.setAdapter(pagerAdapter);
        tabLayout.setupWithViewPager(viewPager);
    }

    private void userKeyDialog(){
        //OMITTED FOR PRIVACY REASONS
    }

    public static String getKey() {
        //OMITTED FOR PRIVACY REASONS
    }


    //DieRollFragment Interface Methods
    @Override
    public void rollResultsUpdate(String input) {
        pagerAdapter.setResults(input);
    }

    @Override
    public void rollLogsUpdate(String input) {
        pagerAdapter.setLogs(input);
    }
    
    //DieSetFragment Interface Methods
    @Override
    public void setResultsUpdate(String input) {
        pagerAdapter.setResults(input);
    }

    @Override
    public void setLogsUpdate(String input) {
        pagerAdapter.setLogs(input);
    }
}

FragmentPagerAdapter

public class PagerAdapter extends FragmentPagerAdapter {

    int numOfTabs;
    ArrayList<String> results;
    ArrayList<String> logs;

    public PagerAdapter(FragmentManager fm, int behavior, int numOfTabs) {
        super(fm, behavior);
        this.numOfTabs = numOfTabs;
        results = new ArrayList<>();
        logs = new ArrayList<>();
    }


    @NonNull
    @Override
    public Fragment getItem(int position) {
        switch(position){
            case(1):
                return DieSetFragment.newInstance(getKey());
            case(2):
                return RollHistoryFragment.newInstance(results, logs);
            case(0):
            default:
                return DieRollFragment.newInstance(getKey());
        }
    }

    @Override
    public CharSequence getPageTitle(int position){
        String title;
        switch(position){
            case(1):
                title = "Roll Set";
                return title;
            case(2):
                title = "Results";
                return title;
            case(0):
            default:
                title = "Roll Die";
                return title;
        }

    @Override
    public int getCount() {
        return 3;
    }

    public void setResults(String input) {
        results.add(input);
    }

    public void setLogs(String input) {
        logs.add(input);
    }
}

RollHistoryFragment

public class RollHistoryFragment extends Fragment{

    TextView results, logs;
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";
    ArrayList<String> inputResults, inputLogs;
    Button clearLogs;

    public RollHistoryFragment() {
        // Required empty public constructor
    }

    public static RollHistoryFragment newInstance(ArrayList<String> inputResults, ArrayList<String> inputLogs) {
        RollHistoryFragment fragment = new RollHistoryFragment();
        Bundle args = new Bundle();
        args.putStringArrayList(ARG_PARAM1, inputResults);
        args.putStringArrayList(ARG_PARAM2, inputLogs);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            inputResults = getArguments().getStringArrayList(ARG_PARAM1);
            inputLogs = getArguments().getStringArrayList(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_roll_history, container, false);

        results = view.findViewById(textViewResults);
        results.setMovementMethod(new ScrollingMovementMethod());
        logs = view.findViewById(textViewLogs);
        logs.setMovementMethod(new ScrollingMovementMethod());

        clearLogs = view.findViewById(buttonClear);
        clearLogs.setOnClickListener(view1 -> {
            results.setText("");
            logs.setText("");
        });

        updateResults(inputResults);
        updateLogs(inputLogs);
        return view;
    }

    public void updateResults(ArrayList<String> input){
        for (String s : input) results.append(s+"\n");
    }

    public void updateLogs (ArrayList<String> input){
        for (String s : input) logs.append(s+"\n");
    }
}

DieRollFragment

public class DieRollFragment extends Fragment {

    private static final String ARG_PARAM1 = "param1";
    private String key;
    private RollFragmentListener listener;

    public DieRollFragment() {
        // Required empty public constructor
    }

    public static DieRollFragment newInstance(String key){
        DieRollFragment fragment = new DieRollFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, key);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            key = getArguments().getString(ARG_PARAM1);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_die_roll, container, false);
        ImageButton rollD4 = view.findViewById(R.id.rollD4);
        ImageButton rollD6 = view.findViewById(R.id.rollD6);
        ImageButton rollD8 = view.findViewById(R.id.rollD8);
        ImageButton rollD10 = view.findViewById(R.id.rollD10);
        ImageButton rollD12 = view.findViewById(R.id.rollD12);
        ImageButton rollD20 = view.findViewById(R.id.rollD20);
        ImageButton rollD100 = view.findViewById(R.id.rollD100);

        rollD4.setOnClickListener(view1 -> {
            Die d4 = new Die(4, key);
            int result = d4.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d4.getLog());
        });

        rollD6.setOnClickListener(view12 -> {
            Die d6 = new Die(6, key);
            int result = d6.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d6.getLog());
        });

        rollD8.setOnClickListener(view13 -> {
            Die d8 = new Die(8, key);
            int result = d8.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d8.getLog());
        });

        rollD10.setOnClickListener(view14 -> {
            Die d10 = new Die(10, key);
            int result = d10.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d10.getLog());
        });

        rollD12.setOnClickListener(view15 -> {
            Die d12 = new Die(12, key);
            int result = d12.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d12.getLog());
        });

        rollD20.setOnClickListener(view16 -> {
            Die d20 = new Die(20, key);
            int result = d20.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d20.getLog());
        });

        rollD100.setOnClickListener(view17 -> {
            Die d100 = new Die(100, key);
            int result = d100.makeRoll();
            showAlert(result);
            listener.rollResultsUpdate("Roll = " + result);
            listener.rollLogsUpdate(d100.getLog());
        });
        return view;
    }

    public interface RollFragmentListener{
        void rollResultsUpdate(String input);
        void rollLogsUpdate(String input);
    }

    private void showAlert(int result){
        new AlertDialog.Builder(Objects.requireNonNull(getContext()))
                .setTitle("You rolled a: ")
                .setMessage(Integer.toString(result))
                .show();
    }

    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if(context instanceof RollFragmentListener){
            listener = (RollFragmentListener) getContext();
        } else throw new RuntimeException(context.toString() + " must implement RollFragmentListener");
    }
    @Override
    public void onDetach() {
        super.onDetach();
        listener = null;
    }
}

DieSetFragment

    public DieSetFragment() {
        // Required empty public constructor
    }

    public static DieSetFragment newInstance(String key) {
        DieSetFragment fragment = new DieSetFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, key);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
        }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_die_set, container, false);
        assert container != null;
        editNumD4 = view.findViewById(R.id.editNumD4);
        editNumD6 = view.findViewById(R.id.editNumD6);
        editNumD8 = view.findViewById(R.id.editNumD8);
        editNumD10 = view.findViewById(R.id.editNumD10);
        editNumD12 = view.findViewById(R.id.editNumD12);
        editNumD20 = view.findViewById(R.id.editNumD20);
        diceSetList = new ArrayList<>();

        ImageButton buttonRollSet = view.findViewById(R.id.buttonRollSet);
        buttonRollSet.setOnClickListener(view1 -> rollSet(mParam1));

        return view;
    }

    public interface SetFragmentListener{
        void setResultsUpdate(String input);
        void setLogsUpdate(String input);
    }

    public void rollSet(String key){
        int numD4 = Integer.parseInt(editNumD4.getText().toString());
        int numD6 = Integer.parseInt(editNumD6.getText().toString());
        int numD8 = Integer.parseInt(editNumD8.getText().toString());
        int numD10 = Integer.parseInt(editNumD10.getText().toString());
        int numD12 = Integer.parseInt(editNumD12.getText().toString());
        int numD20 = Integer.parseInt(editNumD20.getText().toString());

        for(int i = 0; i<numD4; i++){
            Die d4 = new Die(4,key);
            diceSetList.add(d4);
        }
        for(int i = 0; i<numD6; i++){
            Die d6 = new Die(6,key);
            diceSetList.add(d6);
        }
        for(int i = 0; i<numD8; i++){
            Die d8 = new Die(8,key);
            diceSetList.add(d8);
        }
        for(int i = 0; i<numD10; i++){
            Die d10 = new Die(10,key);
            diceSetList.add(d10);
        }
        for(int i = 0; i<numD12; i++){
            Die d12 = new Die(12,key);
            diceSetList.add(d12);
        }
        for(int i = 0; i<numD20; i++){
            Die d20 = new Die(20,key);
            diceSetList.add(d20);
        }

        DieSet dieSet = new DieSet(diceSetList,key);
        int result = dieSet.rollTotal();
        listener.setLogsUpdate(dieSet.getLOG());
        listener.setResultsUpdate("Set Roll = " + result);
        showAlert(result);
        diceSetList.clear();
    }

    public void showAlert(int result){
        new AlertDialog.Builder(Objects.requireNonNull(getContext()))
                .setTitle("You rolled a: ")
                .setMessage(Integer.toString(result))
                .show();
    }

    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if(context instanceof SetFragmentListener){
            listener = (SetFragmentListener) getContext();
        } else throw new RuntimeException(context.toString() + "must implement SetFragmentListener");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        listener = null;
    }
}

经过一番努力,我找到了一个简单的解决方案。

我从 History 片段的 onCreateView 中删除了对更新方法的调用,并添加了 onResume。

然后我在 onResume 中放置一个 if 语句,检查 ArrayLists inputResults 和 inputLogs 是否不为空,然后调用 updateResults 和 updateLogs。然后我清除 inputResults 和 inputLogs。

修改以下代码:

 public void onResume() {
        super.onResume();

        if (inputResults != null && inputLogs != null){
            updateResults(inputResults);
            updateLogs(inputLogs);
        }

        inputResults.clear();
        inputLogs.clear();
    }

现在一切正常!我认为这个问题的回答令我满意,但我不是 Android 王牌所以如果有人对我如何改进这个东西有建议我愿意接受建议!

其他一些最后说明:我从已弃用的 ViewPager 切换到 ViewPager2。因此,页面适配器现在扩展了 FragmentStateAdapter。这在功能上没有太大变化,但使 MainActivity 代码看起来更整洁。