更新 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];
}
}
}
我正在开发一个预算应用程序,在这种情况下,我有一个统计信息 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];
}
}
}