我想获得一个类似于Loop-Habit Tracker应用程序中的图表的图表:水平滚动时,月份(和年份)位于图表的左侧,而每月的第一个替换为月份名称。
MPAndroidChart或任何其他库是否有可能?
答案 0 :(得分:1)
有点麻烦,但是我可以通过将TextView
放在粘性标签位置并让AxisValueFormatter设置文本值来使其工作。您无法保证月份中的第一天实际上将是轴标签,因此我将其设置为将给定月份中显示的第一天替换为月份名称。这利用了以下事实:轴值格式化程序始终从最低值开始向上移动。
以下是展示此方法的完整示例(实施并尝试滚动)
活动布局xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:id="@+id/chart_layout"
tools:context=".MainActivity">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/test_chart"
android:layout_width="0dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:layout_height="0dp"
android:layout_margin="32dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/sticky_label"
android:background="#ffffff"
android:textColor="#000000"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>
活动onCreate
方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ConstraintLayout layout = findViewById(R.id.chart_layout);
final LineChart chart = findViewById(R.id.test_chart);
final TextView sticky = findViewById(R.id.sticky_label);
List<Entry> data = new ArrayList<>();
for(int i = 0; i < 400; ++i) {
data.add(new Entry(2f*i, (float)Math.sin(0.1f*i)));
}
LineDataSet lds = new LineDataSet(data,"data");
lds.setCircleColor(Color.BLACK);
lds.setCircleRadius(6f);
lds.setCircleHoleRadius(3f);
lds.setCircleColorHole(Color.WHITE);
lds.setColor(Color.BLACK);
lds.setLineWidth(4f);
LineData ld = new LineData(lds);
ld.setDrawValues(false);
chart.setData(ld);
final float textSize = 20f;
final XAxis xa = chart.getXAxis();
xa.setGranularity(1f);
xa.setGranularityEnabled(true);
xa.setValueFormatter(new StickyDateAxisValueFormatter(chart, sticky));
xa.setPosition(XAxis.XAxisPosition.BOTTOM);
xa.setTextSize(textSize);
xa.setDrawGridLines(true);
chart.setPinchZoom(false);
chart.zoom(28f,1f,0f,0f);
sticky.setTextSize(textSize);
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener (new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// use removeGlobalOnLayoutListener prior to API 16
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
float xo = xa.getXOffset();
float yo = xa.getYOffset();
final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
float rho = displayMetrics.density;
// WARNING: The 2.2 here was calibrated to the screen I was using. Make sure it works
// on all resolutions and densities. There may be a pixel/dp inconsistency in here somewhere.
// The 10f is from the extra bottom offset (set below).
float ty = chart.getY() + chart.getMeasuredHeight() - rho*yo - 2.2f*textSize - 10f;
float tx = chart.getX() + rho*xo;
sticky.setTranslationY(ty);
sticky.setTranslationX(tx);
}
});
sticky.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.TOP);
chart.getAxisLeft().setTextSize(textSize);
chart.setExtraBottomOffset(10f);
chart.getAxisRight().setEnabled(false);
Description desc = new Description();
desc.setText("");
chart.setDescription(desc);
chart.getLegend().setEnabled(false);
chart.getAxisLeft().setDrawGridLines(true);
}
轴值格式化程序:
public class StickyDateAxisValueFormatter implements IAxisValueFormatter {
private Calendar c;
private LineChart chart;
private TextView sticky;
private float lastFormattedValue = 1e9f;
private int lastMonth = 0;
private int lastYear = 0;
private int stickyMonth = -1;
private int stickyYear = -1;
private SimpleDateFormat monthFormatter = new SimpleDateFormat("MMM", Locale.getDefault());
StickyDateAxisValueFormatter(LineChart chart, TextView sticky) {
c = new GregorianCalendar();
this.chart = chart;
this.sticky = sticky;
}
@Override
public String getFormattedValue(float value, AxisBase axis) {
// Sometimes this gets called on values much lower than the visible range
// Catch that here to prevent messing up the sticky text logic
if( value < chart.getLowestVisibleX() ) {
return "";
}
// NOTE: I assume for this example that all data is plotted in days
// since Jan 1, 2018. Update for your scheme accordingly.
int days = (int)value;
boolean isFirstValue = value < lastFormattedValue;
if( isFirstValue ) {
// starting over formatting sequence
lastMonth = 50;
lastYear = 5000;
c.set(2018,0,1);
c.add(Calendar.DATE, (int)chart.getLowestVisibleX());
stickyMonth = c.get(Calendar.MONTH);
stickyYear = c.get(Calendar.YEAR);
String stickyText = monthFormatter.format(c.getTime()) + "\n" + stickyYear;
sticky.setText(stickyText);
}
c.set(2018,0,1);
c.add(Calendar.DATE, days);
Date d = c.getTime();
int dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
int month = c.get(Calendar.MONTH);
int year = c.get(Calendar.YEAR);
String monthStr = monthFormatter.format(d);
if( (month > stickyMonth || year > stickyYear) && isFirstValue ) {
stickyMonth = month;
stickyYear = year;
String stickyText = monthStr + "\n" + year;
sticky.setText(stickyText);
}
String ret;
if( (month > lastMonth || year > lastYear) && !isFirstValue ) {
ret = monthStr;
}
else {
ret = Integer.toString(dayOfMonth);
}
lastMonth = month;
lastYear = year;
lastFormattedValue = value;
return ret;
}
}
答案 1 :(得分:0)
您可以使用IAxisValueFormatter
,当您覆盖getFormattedValue
时,可以检查该值是否是本月的第一位,然后返回月份名称或日期。