更新 MPAndroidChart 中的 XAxis 值

Update XAxis values in MPAndroidChart

我正在开发一个预算应用程序,在这种情况下,我有一个统计信息 class,其中收集每组费用的值并使用 MPAndroidChart 填充在条形图中。这个想法是,当我们 select 微调器中的不同月份时,图表中显示的值会相应更新。当当前月份的支出类别数量与新月份的类别数量相同时,此功能正常工作 selected。另一方面,如果当前月份的类别数与 selected 月份的类别数不同,我将收到 ArrayIndexOutOfBoundsException。

每次在微调器中 select 编辑不同的月份时,我已经在创建这个 class 的新对象,所以我的问题是,如何将 myXAxisValueFormatter 中的 mValues 数组更新为避免出现此异常?

Statistics.java

package com.robin.xbudget;

import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;

import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter;
import com.github.mikephil.charting.utils.ColorTemplate;

import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Statistics extends Fragment {
    private final String TAG = this.getClass().getSimpleName();

    StatisticsListener callback;

    private TextView mNumberIncomes;
    private TextView mNumberExpenses;
    private TextView mNumberDaysPassed;
    private TextView mNumberDaysRemaining;
    private LocalDate mLocalDateFirst;
    private LocalDate mLocalDateLast;
    private Spinner monthSpinner;
    private BarChart mBarChart;
    private BarDataSet mBarDataSet;
    private BarData mData;
    ArrayList<BarEntry> barEntries;


    @RequiresApi(api = Build.VERSION_CODES.O)
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.activity_statistics, container, false);

        mNumberIncomes = (TextView) view.findViewById(R.id.number_incomes);
        mNumberExpenses = (TextView) view.findViewById(R.id.number_expenses);
        mNumberDaysPassed = (TextView) view.findViewById(R.id.days_passed_number);
        mNumberDaysRemaining = (TextView) view.findViewById(R.id.days_remaining_number);
        mLocalDateFirst = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
        mLocalDateLast = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());

        mNumberIncomes.setText(String.valueOf(callback.getTotalIncomes()));
        mNumberExpenses.setText(String.valueOf(callback.getTotalExpenses()));

        mNumberDaysPassed.setText(String.valueOf(Period.between(mLocalDateFirst, LocalDate.now()).getDays()));
        mNumberDaysRemaining.setText(String.valueOf(Period.between(LocalDate.now(), mLocalDateLast).getDays()));

        monthSpinner = (Spinner) view.findViewById(R.id.spinner_month_stats);
        monthSpinner.setAdapter(callback.getArrayAdapter());

        monthSpinner.setSelection(callback.getSpinnerPosition());

        monthSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                callback.setPeriodSelected((String) monthSpinner.getAdapter().getItem(position));

                dataFiller();
                mBarChart.invalidate();

                Log.d(TAG, "onItemSelected called");
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });

        mBarChart = (BarChart) view.findViewById(R.id.bar_chart);
        dataFiller();

        return view;
    }

    public void dataFiller(){

        mBarChart.setDrawBarShadow(false);
        mBarChart.setDrawValueAboveBar(true);
        mBarChart.setMaxVisibleValueCount(50);
        mBarChart.setPinchZoom(false);
        mBarChart.setDrawGridBackground(false);

        barEntries = new ArrayList<>();

        int x = 0;

            for (String s : callback.getExpensesDataGroup()) {
            float value = 0;
            for (Transaction t : callback.getExpensesDataChild().get(s)) {
                value += t.getQuantity();
            }
            barEntries.add(new BarEntry((float) ++x, value));
        }

        //This is the graphic
        mBarDataSet = new BarDataSet(barEntries, "Expenses");
        mBarDataSet.setColors(ColorTemplate.COLORFUL_COLORS);
        mBarDataSet.notifyDataSetChanged();
        mData = new BarData(mBarDataSet);
        mData.setBarWidth(0.9f);

        mBarChart.setData(mData);

        String[] expenses = new String[callback.getExpensesDataGroup().size() + 1];
        expenses[0] = "Dummy";
        for (int i = 1; i <= callback.getExpensesDataGroup().size(); i++) {
            expenses[i] = callback.getExpensesDataGroup().get(i - 1);
        }

        Log.d(TAG, "Prior to mValues before: " + expenses.length);

        mBarChart.notifyDataSetChanged();
        XAxis xAxis = mBarChart.getXAxis();
        xAxis.setValueFormatter(new myXAxisValueFormatter(expenses));
        xAxis.setPosition(XAxis.XAxisPosition.TOP);
        xAxis.setGranularity(1f);
        //xAxis.setCenterAxisLabels(true);
        //xAxis.setAxisMinimum(1);

    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try{
            callback = (StatisticsListener)context;
        }catch(ClassCastException cce){
            throw new ClassCastException("Class must implement StatisticListener");
        }
    }


    class myXAxisValueFormatter extends IndexAxisValueFormatter {

        private String[] mValues;
        public myXAxisValueFormatter(String[]values) {
            this.mValues = values;
        }

        @Override
        public String getFormattedValue(float value) {
            return mValues[(int)value];
        }
    }

    interface StatisticsListener{

        double getTotalIncomes();
        double getTotalExpenses();

        Map<String, List<Transaction>> getExpensesDataChild();
        List<String> getExpensesDataGroup();
        ArrayAdapter getArrayAdapter();

        void setPeriodSelected(String periodSelected);
        int getSpinnerPosition();
    }
}

Logcat输出

2020-09-09 08:26:11.208 30608-30608/com.robin.xbudget D/Statistics: Prior to mValues before: 3
2020-09-09 08:26:11.373 30608-30608/com.robin.xbudget D/Statistics: Prior to mValues before: 3
2020-09-09 08:26:11.377 30608-30608/com.robin.xbudget D/Statistics: onItemSelected called
2020-09-09 08:26:13.002 30608-30608/com.robin.xbudget D/Statistics: Prior to mValues before: 2
2020-09-09 08:26:13.013 30608-30608/com.robin.xbudget E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.robin.xbudget, PID: 30608
    java.lang.ArrayIndexOutOfBoundsException: length=2; index=2
        at com.robin.xbudget.Statistics$myXAxisValueFormatter.getFormattedValue(Statistics.java:175)
        at com.github.mikephil.charting.formatter.ValueFormatter.getAxisLabel(ValueFormatter.java:62)
        at com.github.mikephil.charting.components.AxisBase.getFormattedLabel(AxisBase.java:488)
        at com.github.mikephil.charting.components.AxisBase.getLongestLabel(AxisBase.java:474)
        at com.github.mikephil.charting.renderer.XAxisRenderer.computeSize(XAxisRenderer.java:78)
        at com.github.mikephil.charting.renderer.XAxisRenderer.computeAxisValues(XAxisRenderer.java:73)
        at com.github.mikephil.charting.renderer.XAxisRenderer.computeAxis(XAxisRenderer.java:66)
        at com.github.mikephil.charting.charts.BarLineChartBase.notifyDataSetChanged(BarLineChartBase.java:346)
        at com.robin.xbudget.Statistics.onItemSelected(Statistics.java:86)
        at android.widget.AdapterView.fireOnSelected(AdapterView.java:1366)
        at android.widget.AdapterView.dispatchOnItemSelected(AdapterView.java:1355)
        at android.widget.AdapterView.access0(AdapterView.java:59)
        at android.widget.AdapterView$SelectionNotifier.run(AdapterView.java:1314)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7050)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)

您需要检查 getFormattedValue 中的值,因为它可以是 < 0 或 >= mValues.length

class myXAxisValueFormatter extends IndexAxisValueFormatter {

    private String[] mValues;
    public myXAxisValueFormatter(String[]values) {
        this.mValues = values;
    }

    @Override
    public String getFormattedValue(float value) {
        int intValue = (int)value;
        if (intValue < 0 || intValue >= mValues.length){
            return "";
        } else {
            return mValues[intValue];
        }
    }
}