android - MPAndroidChart:x 轴上的日期 - 粘性月份和年份

标签 android mpandroidchart

我想实现一个类似于 Loop - Habit Tracker 应用程序中的图表:水平滚动时,月份(和年份)会粘在图表的左侧,并且每个月的第一天会被替换为月份名称。

这可以通过 MPAndroidChart 或任何其他库实现吗?

enter image description here

最佳答案

这有点棘手,但我可以通过将 TextView 放在粘性标签位置并让 AxisValueFormatter 设置文本值来使其工作。您无法保证该月的第一天实际上是轴标签,因此我将其设置为给定月份中显示的第一天被月份名称替换。这利用了轴值格式化程序始终从最低值开始并向上移动的事实。

这是一个完整的示例,展示了这种方法(实现它并尝试滚动)

Example view

Activity 布局 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>

Activity 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;
    }
}

关于android - MPAndroidChart:x 轴上的日期 - 粘性月份和年份,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51970238/

相关文章:

android - 如何使 android View 在整个应用程序中可见?

android - MPandroidChart 布局高度

android - MPAndroid Line Chart 带曲线的单个数据点

android - MPAndroid 图表未显示 xAxis 的任何标签,缺少什么?

java - setDescriptionTextSize(float size) 无法解析方法 MPAndroidChart

android - 从主屏幕开始 Activity

java - RecyclerView getChildCount() 和 getItemCount() 返回相同的值

java - Android Studio- Java RecyclerView显示的 View (所有者)多于 session 室数据库中的行数

android - 使用 Robolectric 2 测试可序列化的包裹

android - 在 MPAndroidChart 中单击任何条时如何停止突出显示?