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;
    }
}
相关推荐
Yeats_Liao3 分钟前
Spring 定时任务:@Scheduled 注解四大参数解析
android·java·spring
雾里看山2 小时前
【MySQL】 库的操作
android·数据库·笔记·mysql
水瓶丫头站住10 小时前
安卓APP如何适配不同的手机分辨率
android·智能手机
xvch11 小时前
Kotlin 2.1.0 入门教程(五)
android·kotlin
xvch15 小时前
Kotlin 2.1.0 入门教程(七)
android·kotlin
望风的懒蜗牛15 小时前
编译Android平台使用的FFmpeg库
android
浩宇软件开发15 小时前
Android开发,待办事项提醒App的设计与实现(个人中心页)
android·android studio·android开发
ac-er888816 小时前
Yii框架中的多语言支持:如何实现国际化
android·开发语言·php
苏金标17 小时前
The maximum compatible Gradle JVM version is 17.
android
zhangphil17 小时前
Android BitmapShader简洁实现马赛克,Kotlin(一)
android·kotlin