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

Android 自定义坐标曲线图_android 自定义曲线图-CSDN博客

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

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

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

java 复制代码
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里面修改,比如可以加入图片等;并且可以更好的控制显示的位置。可以通过再添加一些方法给外部调用即可

完整代码如下

java 复制代码
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 小时前
MySQL 8.0安装(压缩包方式)
android·mysql·adb
梓仁沐白6 小时前
Android清单文件
android
董可伦8 小时前
Dinky 安装部署并配置提交 Flink Yarn 任务
android·adb·flink
每次的天空8 小时前
Android学习总结之Glide自定义三级缓存(面试篇)
android·学习·glide
恋猫de小郭9 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin
flying robot10 小时前
小结:Android系统架构
android·系统架构
xiaogai_gai10 小时前
有效的聚水潭数据集成到MySQL案例
android·数据库·mysql
鹅鹅鹅呢11 小时前
mysql 登录报错:ERROR 1045(28000):Access denied for user ‘root‘@‘localhost‘ (using password Yes)
android·数据库·mysql
在人间负债^11 小时前
假装自己是个小白 ---- 重新认识MySQL
android·数据库·mysql
Unity官方开发者社区11 小时前
Android App View——团结引擎车机版实现安卓应用原生嵌入 3D 开发场景
android·3d·团结引擎1.5·团结引擎车机版