Android 自定义坐标曲线图(二)

简介: 继上一篇文章,本次改进了折线图点击显示提示信息的方式。原来使用popupwindow或dialog,但这种方式控制位置困难,特别是当需要精确显示在点击点上方时。现在,作者通过自定义XML布局实现了更灵活的提示框。

https://developer.aliyun.com/article/1382625?spm=a2c6h.13148508.setting.14.74ce4f0el8Q6l2
image.png

image.png

继上一篇文章,点击折线图上的点,显示提示信息进行修改,之前通过回调,调用外部方法,使用popupwindow或dialog来显示,但是这种方法对于弹框显示的位置很难控制,而且采用popupwindow或dialog是具有唯一性的,也就是显示后,必须先关闭,才能显示下一个点的弹框,这种在某些需求上是不符合的,这种只适合每次只弹一个弹框,且固定在底部,或者居中显示,就可以,实现起来简单。这种方式只适合在页面只有一个折线图的情况下,不适合运用到RecyclerView中,每个item都出现折线图的情况。

如果是要显示在点击到的点的上方,就很难控制,无法精准,并且在分辨率不同的手机会出现较大的差异。因此做了以下修改:

更新如下(20240329):点击点提示信息,不再使用popupwindow或dialog,还是通过自定义,引入xml布局来实现,适合运用到页面只有一个折线图,也适合RecyclerView中出现多个折线图的情况。具体实现代码如下:

public void showDialog(Canvas c, Point point) {
        c.save();
        c.translate((point.x - dip2px(45f)), (point.y - dip2px(30f) - CIRCLE_SIZE / 2f));
        FrameLayout frameLayout = new FrameLayout(mContext);
        frameLayout.setLayoutParams(new ViewGroup.LayoutParams(200, 200));
        LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = li.inflate(R.layout.dialog_valuation_tracker, null);
        v.setLayoutParams(new
                FrameLayout.LayoutParams(dip2px(90f), dip2px(26f)));
        frameLayout.addView(v);
        frameLayout.measure(bWidth, bHeight);
        frameLayout.layout(100, 100, 100, 100);
        frameLayout.draw(c);
        c.restore();
    }

可以看到,是通过引入xml的形式来实现,使用xml能更加的实现多样化样式,要显示什么样子的提示框,可自行在xml里面修改,比如可以加入图片等;并且可以更好的控制显示的位置。可以通过再添加一些方法给外部调用即可

完整代码如下

public class BrokenLineView extends View {
    private static final int CIRCLE_SIZE = 40;

    private static enum LineStyle {LINE, CURVE}

    private static enum YLineStyle {DASHES_LINE, FULL_LINE, NOT_LINE}

    private static enum ShaderOrientationStyle {ORIENTATION_H, ORIENTATION_V}

    private final Context mContext;
    private OnClickListener listener;
    private LineStyle mStyle = LineStyle.LINE;
    private YLineStyle mYLineStyle = YLineStyle.NOT_LINE;
    private ShaderOrientationStyle mShaderOrientationStyle = ShaderOrientationStyle.ORIENTATION_V;
    private int canvasWidth;
    private int bHeight = 0;
    private int bWidth = 0;
    private int marginLeft;
    private int marginRight;
    private boolean isMeasure = true;
    private int xTextWidth = 0;//Y text
    private int spacingHeight;
    private double averageValue;
    private int marginTop = 0;
    private int marginBottom = 0;
    /**
     * data
     */
    private Point[] mPoints;
    private List<String> yRawData = new ArrayList<>();
    private ValuationTrackerPointData pointData;
    private List<String> xRawData = new ArrayList<>();
    private final List<Double> dataList = new ArrayList<>();
    private final List<Integer> xList = new ArrayList<>();// x value
    private final Map<String, Integer> xMap = new HashMap<>();
    /**
     * paint color
     */
    private int xTextPaintColor;
    private int yTextPaintColor;
    private int startShaderColor;
    private int endShaderColor;
    private int mCanvasColor;
    private int mXLinePaintColor;
    /**
     * paint size
     */
    private int xTextSize = 12;
    private int yTextSize = 12;
    private Point mSelPoint;

    public BrokenLineView(Context context) {
        this(context, null);
    }

    public BrokenLineView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        initView();
    }

    private void initView() {
        xTextPaintColor = getColor(mContext, R.color.cl_858585);
        yTextPaintColor = getColor(mContext, R.color.cl_858585);
        startShaderColor = getColor(mContext, R.color.cl_c53355_30);
        endShaderColor = getColor(mContext, R.color.cl_c53355_5);
        mCanvasColor = getColor(mContext, R.color.white);
        mXLinePaintColor = getColor(mContext, R.color.cl_EBEBEB);
    }

    public void setData(ValuationTrackerPointData pointData) {
        this.pointData = pointData;
        averageValue = pointData.getyAverageValue();
        xRawData.clear();
        yRawData.clear();
        dataList.clear();
        xRawData = pointData.getxAxis();
        xRawData.add(0, "");
        yRawData = pointData.getyAxis();
        for (int i = 0; i < pointData.getPointInfo().size(); i++) {
            dataList.add(pointData.getPointInfo().get(i).getPrice());
        }
        if (null != dataList) {
            mPoints = new Point[dataList.size()];
        }
        if (null != yRawData) {
            spacingHeight = yRawData.size();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        if (isMeasure) {
            marginLeft = dip2px(20);
            marginRight = dip2px(10);
            marginTop = dip2px(5);
            marginBottom = dip2px(40);
            int canvasHeight = getHeight();
            this.canvasWidth = getWidth();
            if (bHeight == 0) {
                bHeight = canvasHeight - marginBottom - marginTop;
            }
            if (bWidth == 0) {
                bWidth = canvasWidth - marginLeft - marginRight;
            }
            isMeasure = false;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(mCanvasColor);//canvas color
        //draw X line
        drawAllXLine(canvas);
        if (YLineStyle.DASHES_LINE == mYLineStyle) {
            drawPathYDashesLine(canvas);//draw Y dashes line
        } else if (YLineStyle.FULL_LINE == mYLineStyle) {
            drawAllYLine(canvas);// draw Y line
        } else {
            noDrawYLine(canvas);
        }
        // point init
        mPoints = getPoints();
        //draw cure line
        drawCurve(canvas);
        //draw Polygon bg color
        drawPolygonBgColor(canvas);
        // is click point
        if (null == mSelPoint) {
            drawDot(canvas);// draw dot
        } else {
            clickUpdateDot(canvas);// update dot after click
        }
    }

    private void drawCurve(Canvas c) {
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(getColor(mContext, R.color.cl_c53355));
        p.setStrokeWidth(dip2px(1f));
        p.setStyle(Paint.Style.STROKE);
        if (mStyle == LineStyle.CURVE) {
            drawScrollLine(c, p);
        } else {
            drawLine(c, p);
        }
    }

    private void drawDot(Canvas c) {
        if (null == mPoints || mPoints.length == 0) {
            return;
        }
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setStyle(Paint.Style.FILL);
        for (Point point : mPoints) {
            p.setColor(getColor(mContext, R.color.cl_c53355));
            c.drawCircle(point.x, point.y, CIRCLE_SIZE / 2f, p);
            p.setColor(getColor(mContext, R.color.cl_d77188));
            c.drawCircle(point.x, point.y, CIRCLE_SIZE / 3f, p);
        }
    }

    private void clickUpdateDot(Canvas c) {
        if (null == mPoints || mPoints.length == 0) {
            return;
        }
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setStyle(Paint.Style.FILL);
        for (Point point : mPoints) {
            if (null != mSelPoint && mSelPoint.x == point.x && mSelPoint.y == point.y) {
                p.setColor(getColor(mContext, R.color.cl_c53355));
                c.drawCircle(point.x, point.y, CIRCLE_SIZE / 1.5f, p);
                p.setColor(getColor(mContext, R.color.cl_d77188));
                c.drawCircle(point.x, point.y, (CIRCLE_SIZE / 2f), p);
                showDialog(c, point);
            } else {
                p.setColor(getColor(mContext, R.color.cl_c53355));
                c.drawCircle(point.x, point.y, CIRCLE_SIZE / 2f, p);
                p.setColor(getColor(mContext, R.color.cl_d77188));
                c.drawCircle(point.x, point.y, CIRCLE_SIZE / 3f, p);
            }
        }
    }

    private void drawPolygonBgColor(Canvas c) {
        if (null == mPoints || mPoints.length == 0) {
            return;
        }
        Path p = new Path();
        float startX = 0;
        float endX = 0;
        int endPoint = mPoints.length - 1;
        for (int i = 0; i < mPoints.length; i++) {
            if (i == 0) {
                startX = mPoints[i].x;
                p.moveTo(mPoints[i].x, 0);
                p.lineTo(mPoints[i].x, mPoints[i].y);
            } else {
                p.lineTo(mPoints[i].x, mPoints[i].y);
                if (i == endPoint) {
                    endX = mPoints[i].x;
                }
            }
        }
        p.lineTo(endX, (bHeight + marginTop));
        p.lineTo(startX, (bHeight + marginTop));
        p.close();
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        Shader shader = null;
        if (mShaderOrientationStyle == ShaderOrientationStyle.ORIENTATION_H) {
            shader = new LinearGradient(endX, (bHeight + marginTop), startX, (bHeight + marginTop),
                    startShaderColor, endShaderColor, Shader.TileMode.REPEAT);
        } else {
            Point point = getYBiggestPoint();
            if (null != point) {
                shader = new LinearGradient(point.x, point.y, endX, (bHeight + marginTop),
                        startShaderColor, endShaderColor, Shader.TileMode.REPEAT);
            }
        }
        paint.setShader(shader);
        c.drawPath(p, paint);
    }

    private Point getYBiggestPoint() {
        Point p = null;
        if (null != mPoints && mPoints.length > 0) {
            p = mPoints[0];
            for (int i = 0; i < mPoints.length - 1; i++) {
                if (p.y > mPoints[i + 1].y) {
                    p = mPoints[i + 1];
                }
            }
        }
        return p;
    }

    private void drawPathYDashesLine(Canvas canvas) {
        if (null == xRawData || xRawData.isEmpty()) {
            return;
        }
        Path path = new Path();
        int dashLength = 16;
        int blankLength = 16;
        Paint p = new Paint();
        p.setStyle(Paint.Style.STROKE);
        p.setStrokeWidth(4);
        p.setColor(getColor(mContext, R.color.colorGray));
        p.setPathEffect(new DashPathEffect(new float[]{dashLength, blankLength}, 0));
        for (int i = 0; i < xRawData.size(); i++) {
            drawTextY(xRawData.get(i), (getMarginWidth() + getBWidth() / xRawData.size() * i) - dip2px(8), bHeight + marginTop + dip2px(26),
                    canvas);
            if (null != xMap) {
                xMap.put(xRawData.get(i), getMarginWidth() + getBWidth() / xRawData.size() * i);
            }
            int startX = (getMarginWidth() + getBWidth() / xRawData.size() * i);
            int startY = marginTop;
            int endY = bHeight + marginTop;
            path.moveTo(startX, startY);
            path.lineTo(startX, endY);
            canvas.drawPath(path, p);
        }
        getPointX();
    }

    /**
     * draw Y
     */
    private void drawAllYLine(Canvas canvas) {
        if (null == xRawData || xRawData.isEmpty()) {
            return;
        }
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(getColor(mContext, R.color.colorBlack));
        for (int i = 0; i < xRawData.size(); i++) {
            int w = (getMarginWidth() + getBWidth() / xRawData.size()) * i;
            canvas.drawLine(w, marginTop, w, (bHeight + marginTop), p);
            drawTextY(xRawData.get(i), getMarginWidth() + getBWidth() / xRawData.size() * i - dip2px(8), bHeight + marginTop + dip2px(26),
                    canvas);
            if (null != xMap) {
                xMap.put(xRawData.get(i), getMarginWidth() + getBWidth() / xRawData.size() * i);
            }
        }
        getPointX();
    }

    private void noDrawYLine(Canvas canvas) {
        if (null == xRawData || xRawData.isEmpty()) {
            return;
        }
        for (int i = 0; i < xRawData.size(); i++) {
            drawTextY(xRawData.get(i), (getMarginWidth() + getBWidth() / xRawData.size() * i) - dip2px(8), bHeight + marginTop + dip2px(26),
                    canvas);
            if (null != xMap) {
                xMap.put(xRawData.get(i), getMarginWidth() + getBWidth() / xRawData.size() * i);
            }
        }
        getPointX();
    }

    private void getPointX() {
        if (null == xMap || xMap.size() == 0) {
            return;
        }
        if (null != pointData && !pointData.getPointInfo().isEmpty()) {
            for (ValuationTrackerPointData.PointInfo info : pointData.getPointInfo()) {
                for (Map.Entry<String, Integer> entry : xMap.entrySet()) {
                    if (entry.getKey().equals(info.getMouth())) {
                        xList.add(xMap.get(entry.getKey()));
                    }
                }
            }
        }
    }

    /**
     * draw x
     */
    private void drawAllXLine(Canvas canvas) {
        if (null == yRawData || yRawData.isEmpty()) {
            return;
        }
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(mXLinePaintColor);
        p.setStrokeWidth(dip2px(1f));
        p.setStyle(Paint.Style.FILL);
        int h = bHeight / spacingHeight;
        for (int i = 0; i < yRawData.size(); i++) {
            drawTextX(yRawData.get(i), marginLeft / 2,
                    bHeight - (bHeight / spacingHeight) * i + marginTop + dip2px(2), canvas);
            canvas.drawLine(getMarginWidth(), (bHeight - h * i + marginTop), (canvasWidth - marginRight),
                    (bHeight - h * i + marginTop), p);
        }
    }

    private void drawScrollLine(Canvas canvas, Paint paint) {
        if (null == mPoints || mPoints.length == 0) {
            return;
        }
        Point startP;
        Point endP;
        for (int i = 0; i < mPoints.length - 1; i++) {
            startP = mPoints[i];
            endP = mPoints[i + 1];
            int wt = (startP.x + endP.x) / 2;
            Point p3 = new Point();
            Point p4 = new Point();
            p3.y = startP.y;
            p3.x = wt;
            p4.y = endP.y;
            p4.x = wt;
            Path path = new Path();
            path.moveTo(startP.x, startP.y);
            path.cubicTo(p3.x, p3.y, p4.x, p4.y, endP.x, endP.y);
            canvas.drawPath(path, paint);
        }
    }

    private void drawLine(Canvas canvas, Paint paint) {
        if (null == mPoints || mPoints.length == 0) {
            return;
        }
        Point startP;
        Point endP;
        for (int i = 0; i < mPoints.length - 1; i++) {
            startP = mPoints[i];
            endP = mPoints[i + 1];
            canvas.drawLine(startP.x, startP.y, endP.x, endP.y, paint);
        }
    }

    private void drawTextY(String text, int x, int y, Canvas canvas) {
        if (null == yRawData || yRawData.isEmpty()) {
            return;
        }
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setTextSize(dip2px(yTextSize));
        p.setColor(yTextPaintColor);
        p.setTextAlign(Paint.Align.LEFT);
        canvas.drawText(text, x, y, p);
    }

    private void drawTextX(String text, int x, int y, Canvas canvas) {
        if (null == xRawData || xRawData.isEmpty()) {
            return;
        }
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setTextSize(dip2px(xTextSize));
        p.setColor(xTextPaintColor);
        p.setTextAlign(Paint.Align.LEFT);
        xTextWidth = (int) p.measureText(text);
        canvas.drawText(text, x, y, p);
    }

    private Point[] getPoints() {
        Point[] points = new Point[dataList.size()];
        for (int i = 0; i < dataList.size(); i++) {
            int ph = bHeight - (int) (((dataList.get(i) - pointData.getyAxisSmallValue()) / averageValue) * (bHeight * 1.0f / spacingHeight));
            points[i] = new Point(xList.get(i), ph + marginTop);
        }
        return points;
    }

    private int getMarginWidth() {
        if (xTextWidth == 0) {
            return marginLeft;
        } else {
            return xTextWidth + marginLeft;
        }
    }

    private int getBWidth() {
        if (xTextWidth == 0) {
            return bWidth;
        } else {
            return bWidth - xTextWidth;
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            dealClick(x, y);
        }
        return true;
    }

    private void dealClick(int x, int y) {
        if (null != mPoints && mPoints.length > 0) {
            for (Point p : mPoints) {
                if ((p.x - CIRCLE_SIZE) < x && x < (p.x + CIRCLE_SIZE) &&
                        (p.y - CIRCLE_SIZE) < y && y < (p.y + CIRCLE_SIZE)) {
                    mSelPoint = p;
                    invalidate();
                    if (null != listener) {
                        listener.onClick(this, p.x, p.y);
                    }
                }
            }
        }
    }

    public void showDialog(Canvas c, Point point) {
        c.save();
        c.translate((point.x - dip2px(45f)), (point.y - dip2px(30f) - CIRCLE_SIZE / 2f));
        FrameLayout frameLayout = new FrameLayout(mContext);
        frameLayout.setLayoutParams(new ViewGroup.LayoutParams(200, 200));
        LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = li.inflate(R.layout.dialog_valuation_tracker, null);
        v.setLayoutParams(new
                FrameLayout.LayoutParams(dip2px(90f), dip2px(26f)));
        frameLayout.addView(v);
        frameLayout.measure(bWidth, bHeight);
        frameLayout.layout(100, 100, 100, 100);
        frameLayout.draw(c);
        c.restore();
    }

    public void setAverageValue(int averageValue) {
        this.averageValue = averageValue;
    }

    public void setMarginTop(int marginTop) {
        this.marginTop = marginTop;
    }

    public void setMarginBottom(int marginBottom) {
        this.marginBottom = marginBottom;
    }

    public void setMStyle(LineStyle mStyle) {
        this.mStyle = mStyle;
    }

    public void setMYLineStyle(YLineStyle style) {
        this.mYLineStyle = style;
    }

    public void setShaderOrientationStyle(ShaderOrientationStyle shaderOrientationStyle) {
        this.mShaderOrientationStyle = shaderOrientationStyle;
    }

    public void setBHeight(int bHeight) {
        this.bHeight = bHeight;
    }

    public void setXTextPaintColor(int xTextPaintColor) {
        this.xTextPaintColor = xTextPaintColor;
    }


    public void setYTextPaintColor(int yTextPaintColor) {
        this.yTextPaintColor = yTextPaintColor;
    }


    public void setXTextSize(int xTextSize) {
        this.xTextSize = xTextSize;
    }

    public void setYTextSize(int yTextSize) {
        this.yTextSize = yTextSize;
    }

    public void setXLinePaintColor(int color) {
        mXLinePaintColor = color;
    }

    public void setShaderColor(int startColor, int endColor) {
        this.startShaderColor = startColor;
        this.endShaderColor = endColor;
    }

    private int dip2px(float dpValue) {
        float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public interface OnClickListener {
        void onClick(View v, int x, int y);
    }

    public void setListener(OnClickListener listener) {
        this.listener = listener;
    }
}
相关文章
|
4月前
|
API Android开发 iOS开发
Android 自定义坐标曲线图
自定义View,使用paint、point、path,画点、点与点连接成线、然后闭合起来就是一个多边形,画坐标,实现坐标曲线图
Android 自定义坐标曲线图
|
Android开发
android4.2 修改设置背景
android4.2 修改设置背景
57 0
|
Java Android开发 Kotlin
android 自定义步骤栏
今天自定义了一个操作步骤栏,虽然在平常的项目中用处不大,但是也值得记录一下,先看下效果图
177 0
android 自定义步骤栏
|
Android开发
【Android 应用开发】Android 图表绘制 achartengine 示例解析(二)
【Android 应用开发】Android 图表绘制 achartengine 示例解析(二)
160 0
【Android 应用开发】Android 图表绘制 achartengine 示例解析(二)
|
Android开发 数据格式 XML