1. 程式人生 > >為MPAndroidChart之RadarChart新增lable新增點選事件

為MPAndroidChart之RadarChart新增lable新增點選事件

關於RadarChart

先貼上MPAndroidChart 的GitHub上地址:https://github.com/PhilJay/MPAndroidChart 

RadarChart的使用這裡不做介紹,如有需要請自行查閱!

直奔主題,檢視RadarChart的監聽方法,僅僅提供了OnChartValueSelectedListener方法,改方法僅能作用於圖形上的點選事件,並不能實現lable點選事件(及RadarChart的x軸點選事件),所以需要自己實現其方法;

分析

想要實現lable點選事件,我們可以實現RadarChart的setOnTouchListener()方法,從觸控的位置判斷點選區域是否在RadarChart的文字區域,檢視RadarChat原始碼,發現RadarChat的lable繪製主要依靠XAxisRendererRadarChart類實現,

其中drawLable方法則是呼叫MPAndroidChart中的Util方法

參照其原始碼的實現方式,可以計算每個lable的繪製區域Rect及起始繪製點,

 

/**
 * 計算位置
 * @param compositeRadar
 * @return
 */
public static List<RadarPointBean> computePosition(RadarChart compositeRadar) {
    List<RadarPointBean> pointBeans = new ArrayList<>();
    XAxis xAxis = compositeRadar.getXAxis();
    final float labelRotationAngleDegrees = xAxis.getLabelRotationAngle();
    final MPPointF drawLabelAnchor = MPPointF.getInstance(0.5f, 0.25f);
    float sliceangle = compositeRadar.getSliceAngle();
    float factor = compositeRadar.getFactor();
    MPPointF center = compositeRadar.getCenterOffsets();
    MPPointF pOut = MPPointF.getInstance(0, 0);

    for (int i = 0; i < compositeRadar.getData().getMaxEntryCountSet().getEntryCount(); i++) {
        String label = xAxis.getValueFormatter().getFormattedValue(i, xAxis);

        float angle = (sliceangle * i + compositeRadar.getRotationAngle()) % 360f;

        Utils.getPosition(center, compositeRadar.getYRange() * factor
                + xAxis.mLabelRotatedWidth / 2f, angle, pOut);

        pointBeans.add(computeStartPoint(label, pOut.x, pOut.y - xAxis.mLabelRotatedHeight / 2.f,
               mAxisLabelPaint, drawLabelAnchor, labelRotationAngleDegrees));
    }
    return pointBeans;
}

computeStartPoint方法:

 

 /**
     * 計算文字繪製起點
     * @param text
     * @param x
     * @param y
     * @param paint
     * @param anchor
     * @param angleDegrees
     * @return
     */
    private static RadarPointBean computeStartPoint(String text, float x, float y,
                                                    Paint paint,
                                                    MPPointF anchor, float angleDegrees) {
        mDrawTextRectBuffer = new Rect();
        mFontMetricsBuffer = new Paint.FontMetrics();
        float drawOffsetX = 0.f;
        float drawOffsetY = 0.f;
        final float lineHeight = paint.getFontMetrics(mFontMetricsBuffer);
        paint.getTextBounds(text, 0, text.length(), mDrawTextRectBuffer);

        drawOffsetX -= mDrawTextRectBuffer.left;

        drawOffsetY += -mFontMetricsBuffer.ascent;

        if (angleDegrees != 0.f) {
            drawOffsetX -= mDrawTextRectBuffer.width() * 0.5f;
            drawOffsetY -= lineHeight * 0.5f;
        } else {
            if (anchor.x != 0.f || anchor.y != 0.f) {
                drawOffsetX -= mDrawTextRectBuffer.width() * anchor.x;
                drawOffsetY -= lineHeight * anchor.y;
            }
            drawOffsetX += x;
            drawOffsetY += y;
        }
        return new RadarPointBean(drawOffsetX, drawOffsetY, mDrawTextRectBuffer);
    }



 

其中RadarPointBean為儲存每個lable的起始點及Rect,其屬性為:

public class RadarPointBean {
    float startX;
    float startY;
    Rect rect;
    public RadarPointBean(float startX, float startY, Rect rect) {
        this.startX = startX;
        this.startY = startY;
        this.rect = rect;
    }

}

這裡需要一個mAxisLabelPaint,繼續檢視,mAxisLabelPaint是AxisRenderer中的屬性,

然而RadarChart中有mXAxisRenderer屬性,

並且XAxisRendererRadarChart繼承與XAxisRenderer,


因此只要回去到RadarChart中的,便能夠獲取到Paint,但是mXAxisRenderer是protected屬性,而且RadarChart中沒有提供外部呼叫方法,我們可以通過反射或者定義RadarChart子類的方法獲取到XAxisRendererChart,這裡我採用反射獲取,從效率來講,定義RadarChart子類比反射獲取更好;

 

/**
 * 反射獲取XAxisRendererRadarChart
 * @param compositeRadar
 * @return
 */
private static XAxisRendererRadarChart getXAxisRendererRadarChart(RadarChart compositeRadar) {
    try {
        Field field = compositeRadar.getClass().getDeclaredField("mXAxisRenderer");
        field.setAccessible(true);
        XAxisRendererRadarChart mXAxisRenderer = (XAxisRendererRadarChart) field.get(compositeRadar);
        return mXAxisRenderer;
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;

}

然後修改computePosition方法:

 

/**
 * 計算位置
 * @param compositeRadar
 * @return
 */
public static List<RadarPointBean> computePosition(RadarChart compositeRadar) {
    List<RadarPointBean> pointBeans = new ArrayList<>();
    XAxis xAxis = compositeRadar.getXAxis();
    final float labelRotationAngleDegrees = xAxis.getLabelRotationAngle();
    final MPPointF drawLabelAnchor = MPPointF.getInstance(0.5f, 0.25f);
    XAxisRendererRadarChart mXAxisRenderer = getXAxisRendererRadarChart(compositeRadar);
    float sliceangle = compositeRadar.getSliceAngle();
    float factor = compositeRadar.getFactor();
    MPPointF center = compositeRadar.getCenterOffsets();
    MPPointF pOut = MPPointF.getInstance(0, 0);

    for (int i = 0; i < compositeRadar.getData().getMaxEntryCountSet().getEntryCount(); i++) {
        String label = xAxis.getValueFormatter().getFormattedValue(i, xAxis);

        float angle = (sliceangle * i + compositeRadar.getRotationAngle()) % 360f;

        Utils.getPosition(center, compositeRadar.getYRange() * factor
                + xAxis.mLabelRotatedWidth / 2f, angle, pOut);

        pointBeans.add(computeStartPoint(label, pOut.x, pOut.y - xAxis.mLabelRotatedHeight / 2.f,
                mXAxisRenderer.getPaintAxisLabels(), drawLabelAnchor, labelRotationAngleDegrees));
    }
    return pointBeans;
}

這樣便獲取到了每個lable的繪製起點及繪製區域,然後新增事件:

 

float x = 0;//臨時儲存點選座標
float y = 0;//臨時儲存點選座標

 

        compositeRadar.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {

                List<RadarPointBean> pointBeans = RadarUtil.computePosition(compositeRadar);

                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        x = event.getX();
                        y = event.getY();
                        break;
                    case MotionEvent.ACTION_UP:
                        for (int i = 0; i < pointBeans.size(); i++) {
                            RadarPointBean pointBean = pointBeans.get(i);
                            if (pointBean.isIn(x, y)) {
                                if (radarDatas != null && radarDatas.size() >= i) {
                                    ToastUtils.showShort("點選第" + i + "個lable");
                                    LogUtils.i("點選第" + i + "個lable");
                                    StudentDimensionBean bean = radarDatas.get(i);
                                    gotoCompositeDetail(bean);
                                }
                                return true;
                            }
                        }
                        break;
                    default:

                        break;
                }
                return false;
            }
        });

 

RadarPointBean中的isIn方法為:

 

private static final int DEF_PADDING = 25;//為文字增加點選區域 相當於padding

public boolean isIn(float x, float y) {
    float endX = startX + Math.abs(rect.right - rect.left) + DEF_PADDING;
    float endY = startY - Math.abs(rect.bottom - rect.top) - DEF_PADDING;
    float startX = getStartX() - DEF_PADDING;
    float startY = getStartY() + DEF_PADDING;
    return startX < x && x < endX && startY > y && y > endY;
}

效果

結語

獲取XAxisRendererRadarChart從效率上建議使用定義RadarChart子類方式:

 

public class CustomRadarChart extends RadarChart {
    public CustomRadarChart(Context context) {
        super(context);
    }
    public XAxisRendererRadarChart getXAxisRenderer(){
        return mXAxisRenderer;
    }
}

如有其它更好的方法,歡迎探討.

demo地址:戳這裡