jjzjj

安卓 donut chart

coder 2023-12-22 原文

我创建了一个圆环图,如下所示:

我生成的 donut chart 应采用以下方式:

我的问题是,我怎样才能用图像实现线条(它们在第二个屏幕截图中四舍五入)

供引用,这是我编写的代码:

public class PieChartView extends View {

private int[] values = {30, 60, 90, 100, 150};
private int c[] = {Color.MAGENTA,Color.BLUE,Color.RED,Color.CYAN,Color.YELLOW};
private int valuesLength = values.length;
private RectF rectF;
private Paint slicePaint, textPaint;
private Path path;

public PieChartView(Context context, AttributeSet attrs) {
    super(context, attrs);

    valuesLength = values.length;
    slicePaint = new Paint();
    slicePaint.setAntiAlias(true);
    slicePaint.setDither(true);
    slicePaint.setStyle(Paint.Style.FILL);

    path = new Path();
}

@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
    if(values != null) {
        int startTop = 0;
        int startLeft = 0;
        int endBottom = getHeight();
        int endRight = endBottom;// This makes an equal square.

        rectF = new RectF(startLeft, startTop, endRight, endBottom);

        float[] scaledValues = scale();
        float sliceStartPoint = 0;
        path.addCircle(rectF.centerX(), rectF.centerY(), 125, Direction.CW);
        canvas.clipPath(path, Op.DIFFERENCE);

        for(int i = 0; i < valuesLength; i++) {
            slicePaint.setColor(c[i]);
            path.reset();
            path.addArc(rectF, sliceStartPoint,  scaledValues[i]);
            path.lineTo(rectF.centerX(), rectF.centerY());
            canvas.drawPath(path, slicePaint);
            sliceStartPoint += scaledValues[i];//This updates the starting point of next slice.
        }
    }
}
private float[] scale() {
    float[] scaledValues = new float[this.values.length];
    float total = getTotal(); //Total all values supplied to the chart
    for (int i = 0; i < this.values.length; i++) {
        scaledValues[i] = (this.values[i] / total) * 360; //Scale each value
    }
    return scaledValues;
}

 private float getTotal() {
        float total = 0;
        for (float val : this.values)
            total += val;
        return total;
    }

另外,如何从一个角度(起始角或扫描角)找出坐标。如果我想从圆心到坐标画一条线?

最佳答案

以下是我在这个图书馆的帮助下搜索了两天后最终完成的方法 https://github.com/Ken-Yang/AndroidPieChart

在我的 friend 的帮助下和大量的搜索中完成了将文本居中的方程式

在 MainActivity onCreate 或 oncreateView 上,如果您使用的是 fragment :

PieChart pie = (PieChart) rootView.findViewById(R.id.pieChart);

    ArrayList<Float> alPercentage = new ArrayList<Float>();
    alPercentage.add(2.0f);
    alPercentage.add(8.0f);
    alPercentage.add(20.0f);
    alPercentage.add(10.0f);
    alPercentage.add(10.0f);
    alPercentage.add(10.0f);
    alPercentage.add(10.0f);
    alPercentage.add(10.0f);
    alPercentage.add(10.85f);
    alPercentage.add(9.15f);
    try {
        // setting data
        pie.setAdapter(alPercentage);

        // setting a listener
        pie.setOnSelectedListener(new OnSelectedLisenter() {
            @Override
            public void onSelected(int iSelectedIndex) {
                Toast.makeText(getActivity(),
                        "Select index:" + iSelectedIndex,
                        Toast.LENGTH_SHORT).show();
            }
        });
    } catch (Exception e) {
        if (e.getMessage().equals(PieChart.ERROR_NOT_EQUAL_TO_100)) {
            Log.e("kenyang", "percentage is not equal to 100");
        }
    }



public class PieChart extends View {

public interface OnSelectedLisenter {
    public abstract void onSelected(int iSelectedIndex);
}

private OnSelectedLisenter onSelectedListener = null;

private static final String TAG = PieChart.class.getName();
public static final String ERROR_NOT_EQUAL_TO_100 = "NOT_EQUAL_TO_100";
private static final int DEGREE_360 = 360;
private static String[] PIE_COLORS = null;
private static int iColorListSize = 0;
ArrayList<Float> array;
private Paint paintPieFill;
private Paint paintPieBorder;
private Paint paintCenterCircle;
private ArrayList<Float> alPercentage = new ArrayList<Float>();
private int mCenterX = 320;
private int mCenterY = 320;
private int iDisplayWidth, iDisplayHeight;
private int iSelectedIndex = -1;
private int iCenterWidth = 0;
private int iShift = 0;
private int iMargin = 0; // margin to left and right, used for get Radius
private int iDataSize = 0;
private Canvas canvas1;
private RectF r = null;
private RectF centerCircle = null;
private float fDensity = 0.0f;
private float fStartAngle = 0.0f;
private float fEndAngle = 0.0f;
float fX;
float fY;

public PieChart(Context context, AttributeSet attrs) {
    super(context, attrs);
    PIE_COLORS = getResources().getStringArray(R.array.colors);
    iColorListSize = PIE_COLORS.length;
    array = new ArrayList<Float>();
    fnGetDisplayMetrics(context);
    iShift = (int) fnGetRealPxFromDp(30);
    iMargin = (int) fnGetRealPxFromDp(40);
    centerCircle = new RectF(200, 200, 440, 440);
    // used for paint circle
    paintPieFill = new Paint(Paint.ANTI_ALIAS_FLAG);
    paintPieFill.setStyle(Paint.Style.FILL);
    // used for paint centerCircle
    paintCenterCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
    paintCenterCircle.setStyle(Paint.Style.FILL);
    paintCenterCircle.setColor(Color.WHITE);
    // used for paint border
    paintPieBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
    paintPieBorder.setStyle(Paint.Style.STROKE);
    paintPieBorder.setStrokeWidth(fnGetRealPxFromDp(3));
    paintPieBorder.setColor(Color.WHITE);
    Log.i(TAG, "PieChart init");

}

// set listener
public void setOnSelectedListener(OnSelectedLisenter listener) {
    this.onSelectedListener = listener;
}

float temp = 0;

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Log.i(TAG, "onDraw");
    float centerX = (r.left + r.right) / 2;
    float centerY = (r.top + r.bottom) / 2;
    float radius1 = (r.right - r.left) / 2;
    radius1 *= 0.5;
    float startX = mCenterX;
    float startY = mCenterY;
    float radius = mCenterX;
    float medianAngle = 0;
    Path path = new Path();

    for (int i = 0; i < iDataSize; i++) {

        // check whether the data size larger than color list size
        if (i >= iColorListSize) {
            paintPieFill.setColor(Color.parseColor(PIE_COLORS[i
                    % iColorListSize]));
        } else {
            paintPieFill.setColor(Color.parseColor(PIE_COLORS[i]));
        }

        fEndAngle = alPercentage.get(i);

        // convert percentage to angle
        fEndAngle = fEndAngle / 100 * DEGREE_360;

        // if the part of pie was selected then change the coordinate
        if (iSelectedIndex == i) {
            canvas.save(Canvas.MATRIX_SAVE_FLAG);
            float fAngle = fStartAngle + fEndAngle / 2;
            double dxRadius = Math.toRadians((fAngle + DEGREE_360)
                    % DEGREE_360);
            fY = (float) Math.sin(dxRadius);
            fX = (float) Math.cos(dxRadius);
            canvas.translate(fX * iShift, fY * iShift);
        }

        canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieFill);
        float angle = (float) ((fStartAngle + fEndAngle / 2) * Math.PI / 180);
        float stopX = (float) (startX + (radius/2) * Math.cos(angle));
        float stopY = (float) (startY + (radius/2) * Math.sin(angle));


        // if the part of pie was selected then draw a border
        if (iSelectedIndex == i) {
            canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieBorder);
             canvas.drawLine(startX, startY, stopX, stopY, paintPieFill);
            canvas.restore();
        }
        fStartAngle = fStartAngle + fEndAngle;
    }

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    // get screen size
    iDisplayWidth = MeasureSpec.getSize(widthMeasureSpec);
    iDisplayHeight = MeasureSpec.getSize(heightMeasureSpec);

    if (iDisplayWidth > iDisplayHeight) {
        iDisplayWidth = iDisplayHeight;
    }

    /*
     * determine the rectangle size
     */
    iCenterWidth = iDisplayWidth / 2;
    int iR = iCenterWidth - iMargin;
    if (r == null) {
        r = new RectF(iCenterWidth - iR, // top
                iCenterWidth - iR, // left
                iCenterWidth + iR, // right
                iCenterWidth + iR); // bottom
    }
    if (centerCircle == null) {
        // centerCircle=new RectF(left, top, right, bottom);

    }
    setMeasuredDimension(iDisplayWidth, iDisplayWidth);
}

@Override
public boolean onTouchEvent(MotionEvent event) {

    // get degree of the touch point
    double dx = Math.atan2(event.getY() - iCenterWidth, event.getX()
            - iCenterWidth);
    float fDegree = (float) (dx / (2 * Math.PI) * DEGREE_360);
    fDegree = (fDegree + DEGREE_360) % DEGREE_360;

    // get the percent of the selected degree
    float fSelectedPercent = fDegree * 100 / DEGREE_360;

    // check which pie was selected
    float fTotalPercent = 0;
    for (int i = 0; i < iDataSize; i++) {
        fTotalPercent += alPercentage.get(i);
        if (fTotalPercent > fSelectedPercent) {
            iSelectedIndex = i;
            break;
        }
    }
    if (onSelectedListener != null) {
        onSelectedListener.onSelected(iSelectedIndex);
    }
    invalidate();
    return super.onTouchEvent(event);
}

private void fnGetDisplayMetrics(Context cxt) {
    final DisplayMetrics dm = cxt.getResources().getDisplayMetrics();
    fDensity = dm.density;
}

private float fnGetRealPxFromDp(float fDp) {
    return (fDensity != 1.0f) ? fDensity * fDp : fDp;
}

public void setAdapter(ArrayList<Float> alPercentage) throws Exception {
    this.alPercentage = alPercentage;
    iDataSize = alPercentage.size();
    float fSum = 0;
    for (int i = 0; i < iDataSize; i++) {
        fSum += alPercentage.get(i);
    }
    if (fSum != 100) {
        Log.e(TAG, ERROR_NOT_EQUAL_TO_100);
        iDataSize = 0;
        throw new Exception(ERROR_NOT_EQUAL_TO_100);
    }

}


<com.example.piecharts.PieChart
        android:id="@+id/pieChart"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </com.example.piecharts.PieChart>

关于安卓 donut chart ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22933351/

有关安卓 donut chart的更多相关文章

  1. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  2. (一)专题介绍:移动端安卓手机改造成linux服务器&linux服务器中安装软件、部署前后端分离项目实战 - 2

    快捷目录前言一、涉及到的相关技术简介二、具体实现过程及踩坑杂谈1.安卓手机改造成linux系统实现方案2.改造后的手机Linux中软件的安装3.手机Linux中安装MySQL5.7踩坑实录4.手机Linux中安装软件的正确方法三、Linux服务器部署前后端分离项目流程1.前提准备(安装必要软件,搭建环境):2.前后端分离项目的详细部署过程:总结前言总体概述:本篇文章隶属于“手机改造服务器部署前后端分离项目”系列专栏,该专栏将分多个板块,每个板块独立成篇来详细记录:手机(安卓)改造成个人服务器(Linux)、Linux中安装软件、配置开发环境、部署JAVA+VUE+MySQL5.7前后端分离项目

  3. 微信小程序安卓视频播放卡顿问题 - 2

    在微信小程序开发中遇到在video组件的两个问题1.安卓手机里播放视频会有明显的卡顿问题刚开始以为是网络问题,或者是视频文件问题。排查了一下发现都没问题最后加了个属性就OK了uniapp和原生小程序方法:custom-cache="false"custom-cache={{false}}video组件兼容iOS手机custom-cache加了这个属性会让2.iOS手机第一次播放视频会有几秒黑屏问题因此我加了当前手机型号的判断uni.getDeviceInfo().deviceType获取当前设备api当为iPhone时不加custom-cache属性,否则加上custom-cache=“fal

  4. 安卓渐变的背景框实现 - 2

    安卓渐变的背景框实现1.背景实现方法1.利用PorterDuffXfermode进行图层的混合,这是最推荐的方法,也是最有效的。2.利用canvas裁剪实现,这个方法有个缺陷,就是圆角会出现毛边,也就是锯齿。3.利用layer绘制边框1.背景万恶的设计小姐姐又来搞事情啦,你说好好的设计一个纯色的背景框框不好嘛,非要把一个框框弄成渐变的,如果不拿出放大镜估计没几个人能看出来它是渐变的。来,我让你看看是啥样框子是从左到右渐变的,设计应该是做了一个底图,然后上面盖了一个白色圆角矩形。那么我们该怎么去实现它呢?实现方法下面介绍三种实现它的方法。先贴上源码地址,大家记得给个starhttps://git

  5. 安卓手机浏览器:远程调试 - 2

    简介:有时我们需要调试手机浏览器网页信息,这时除了使用fiddler抓包拦截篡改,还可以通过USB连接,通过PC远程调试手机上的浏览器信息,进行映射。历史攻略:adb:安卓手机USB调试模式前置准备:网页内容在移动设备上的体验可能和电脑上完全不同。ChromeDevTools提供远程调试功能安卓远程调试支持:在浏览器选项卡中调试网站。在原生安卓应用中调试网页内容。将屏幕从你的安卓设备上投影到你的开发机器上。使用端口转发和虚拟主机映射来让安卓设备访问开发使用的服务器。操作步骤:1、手机通过USB连接电脑。2、开启手机调试模式。3、PC电脑edge输入:edge://inspect/#device

  6. 安卓性能优化之内存优化 - 2

    Java对象生命周期:创建:为对象分配内存空间,构造对象应用:此时对象至少被一个强引用持有不可见:未被任何强引用持有,进行可达性分析不可达:可达性分析为不可达,进入下一阶段收集:当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。终结:当对象执行完finalize()方法后仍然处于不可达状态时(可达性分析垃圾回收算法被回收前,会有两次标记过程,判断是否执行lfinalize()方法,执行完之后判断是否GCROOT可达,如果仍不可达,则准备回收),则

  7. Qt安卓开发:调用java代码的获取usb权限 - 2

    最近换了工作,新工作是负责用qml做qt安卓开发。工作中遇到一个问题:安卓设备有USB口,需要插入一个U盘在程序里读写U盘中的文件,由于安卓系统的安全性的问题导致QFile、c++的文件操作相关方法都不能读写成功,想要读写成功只能调用java代码,在java代码里面使用安卓的DocumentFile库。经过一番探索,成功解决了问题。qt如何添加java代码不说了,网上有。下面是具体的java代码:packagecom.example.myapplication;importandroid.annotation.TargetApi;importandroid.content.Context;im

  8. Obsidian安卓端同步及使用(Remotely Save+阿里云同步S3) - 2

    Obsidian安卓端同步及使用(RemotelySave+阿里云同步S3)强烈推荐的obsidian的markdown教程​obsidian这款软件很不错,最近刚入门,用来做笔记,喜欢在电脑上做笔记,手机端能随时查看,故捣鼓了一下安卓端的同步及安卓端的使用1.安装包获取​不能科学上网,我是到官方中文论坛上找到的,网址如下:移动端v1.4.1开始测试-Obsidian中文论坛2.电脑端同步+阿里云配置​我使用的是RemotelySave插件​首先,电脑端关闭安全模式,下载这个第三方插件,登不上的看这里,网址如下:完美解决obsidian无法加载第三方插件(社区插件)的问题​然后就是阿里云的同步

  9. 安卓 XML 错误 : no resource identifier found for attribute 'xmlns' in package 'android' - 2

    我知道这里有一百个问题和我一样,但似乎没有一个适合我的具体问题,所以我要问一个新问题。以防万一这是重复,我很抱歉。所以,我正在构建一个应用程序,布局给我带来了一些问题。这是我的XML代码:(尚未完成)我得到的错误是在代码的第一行它说“错误:在包'android'中找不到属性'xmlns'的资源标识符我一遍又一遍地检查代码,尝试刷新/重建项目,尝试删除该特定行等等,但似乎没有任何解决办法。那么,如果有人有一些想法?谢谢! 最佳答案 删除android:xmlns="http://schemas.android.com/apk/res/

  10. java - 安卓工作室错误 : cannot resolve symbol in Xml - 2

    我正在关注googleAndroidStudio第一个android应用程序教程。但是当我尝试向我的应用程序添加搜索栏时,我现在收到3个奇怪的错误。我现在就在这里,我按照教程添加了XML代码。http://developer.android.com/training/basics/actionbar/adding-buttons.html我得到的错误:Error:(5,23)Noresourcefoundthatmatchesthegivenname(at'icon'withvalue'@drawable/ic_action_search').Error:(6,24)Noresourc

随机推荐