自定义View能力雷达图

news/2024/7/8 6:02:35 标签: 扩展, 继承, 图形, 数据

你可能看见过这个图。

这里写图片描述

2. 实现思路

  1. 继承View,复写onDraw。
  2. 确定N边形和每个边对应的角度;
  3. 确定多边形外接圆的半径以及圆心(也就是中心点)
  4. 确定每条半径上的所有点的坐标。
  5. 确定每条数据图形上的坐标;
  6. 确定文字在图形上的位置;
  7. 采用合适的绘制方式绘制;

3. 实现

3.1 定义自定义属性

主要定义这几个属性,可以根据需要继续扩展

    <declare-styleable name="CustomRadarChart">
        <!-- 蜘蛛网线条宽度 -->
        <attr name="radarLineWidth" format="dimension"/>
        <!-- 蜘蛛网颜色 -->
        <attr name="radarLineColor" format="color"/>
        <!-- 半径分成N段-->
        <attr name="radarLineSegments" format="integer"/>
        <!-- 文字颜色 -->
        <attr name="radarTextColor" format="color"/>
        <!-- 文字字体大小-->
        <attr name="radarTextSize" format="integer"/>
        <!-- 数据展示覆盖区域颜色 -->
        <attr name="radarCoverColor" format="color"/>
    </declare-styleable>

3.2 自定义View代码

注释代码中都有写出来,主要是看下实现的方式。没有过多的修饰和精简。

public class CustomRadarChart extends View {

    /**
     * N 边形,默认为6边形
     */
    private final int DEFAULT_PIECE_NUMBER = 7;

    /**
     * 线条宽度,默认为10px
     */
    private final int DEFAULT_LINE_WIDTH = 4;

    /**
     * 线条颜色,默认为灰色
     */
    private final int DEFAULT_LINE_COLOR = 0xffd0d6dc;

    /**
     * 半径分成N段,默认为4段,圆心算一段
     */
    private final int DEFAULT_LINE_SEGMENTS = 4;

    /**
     * 外接圆半径,默认为50px
     */
    private final int DEFAULT_RADIUS = 50;

    /**
     * 文本颜色和文本字体, 默认为黑色,10px
     */
    private final int DEFAULT_TEXT_COLOR = 0xff647d91;
    private final int DEFAULT_TEXT_SIZE = 10;

    /**
     * 覆盖面绘制颜色
     */
    private final int DEFAULT_COVER_COLOR = 0x55ced6dc;

    private int mPieceNumber = DEFAULT_PIECE_NUMBER;
    private int mRadius = DEFAULT_RADIUS;

    private int mLineWidth = DEFAULT_LINE_WIDTH;
    private int mLineColor = DEFAULT_LINE_COLOR;
    private int mLineSegments = DEFAULT_LINE_SEGMENTS;
    private int mTextColor = DEFAULT_TEXT_COLOR;
    private int mTextSize = DEFAULT_TEXT_SIZE;
    private int mCoverColor = DEFAULT_COVER_COLOR;

    private double mAverageAngle = 0;

    private Paint mRadarPaint;
    private TextPaint mTextPaint;
    private Paint mCoverPaint;
    private Path mCoverPath;

    List<RadarPoints> mRadarPointses = new ArrayList<>();
    List<RadarEntry> mRadarEntries = new ArrayList<>();
    List<PointF> mCoverPoints = new ArrayList<>();
    List<PointF> mTextPoints = new ArrayList<>();

    /**
     * 外接圆中心位置
     */
    private int mPositionX = 0;
    private int mPositionY = 0;

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

    public CustomRadarChart(Context context,
            @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomRadarChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.CustomRadarChart);

        mLineWidth = (int) attributes.getDimension(R.styleable.CustomRadarChart_radarLineWidth, DEFAULT_LINE_WIDTH);
        mLineColor = attributes.getColor(R.styleable.CustomRadarChart_radarLineColor, DEFAULT_LINE_COLOR);
        mLineSegments = attributes.getInteger(R.styleable.CustomRadarChart_radarLineSegments, DEFAULT_LINE_SEGMENTS);
        mTextColor = attributes.getColor(R.styleable.CustomRadarChart_radarTextColor, DEFAULT_TEXT_COLOR);

        mTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                attributes.getInteger(R.styleable.CustomRadarChart_radarTextSize, DEFAULT_TEXT_SIZE), getResources().getDisplayMetrics());
        mCoverColor = (int) attributes.getColor(R.styleable.CustomRadarChart_radarCoverColor, DEFAULT_COVER_COLOR);

        init();
    }

    private void init() {
        /**
         * 蜘蛛网Paint初始化
         */
        mRadarPaint = new Paint();
        mRadarPaint.setColor(mLineColor);
        mRadarPaint.setStrokeWidth(mLineWidth);
        mRadarPaint.setAntiAlias(true);
        mRadarPaint.setStyle(Paint.Style.STROKE);

        /**
         * 文字绘制Paint初始化
         */
        mTextPaint = new TextPaint();
        mTextPaint.setColor(mTextColor);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStyle(Paint.Style.STROKE);

        /**
         *覆盖面绘制Paint初始化
         */
        mCoverPaint = new Paint();
        mCoverPaint.setColor(mCoverColor);
        mCoverPaint.setAntiAlias(true);
        mCoverPaint.setStyle(Paint.Style.FILL);
        mCoverPath = new Path();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mPositionX = w / 2;
        mPositionY = h / 2;
        mAverageAngle = 360.0 / mPieceNumber;

        int max = 0;
        for(RadarEntry entry : mRadarEntries) {
            Rect textBound = new Rect();
            mTextPaint.getTextBounds(entry.title, 0, entry.title.length(),
                    textBound);
            max = Math.max(textBound.width(), max);
        }
        mRadius = Math.min(w / 2 - max, h / 2);

        if (mRadarEntries==null || mRadarEntries.size()==0) {
            throw new NullPointerException("请先设置数据集");
        }
        /**
         * 计算每一条轴线上的所有点
         */
        for (int i = 0; i < mPieceNumber; i++) {
            List<PointF> pointFs = new ArrayList<>();
            for (int j = 0; j < mLineSegments; j++) {
                PointF point = new PointF();
                double percent = j * 1.0 / (mLineSegments - 1);
                point.set(getPloygonX(mAverageAngle * i, percent),
                        getPloygonY(mAverageAngle * i, percent));
                pointFs.add(point);
            }
            RadarPoints radarPoints = new RadarPoints(i, pointFs);
            mRadarPointses.add(radarPoints);
        }

        /**
         * 根据数据集计算覆盖多变形的点
         */
        for (int m = 0; m < mPieceNumber; m++) {
            PointF pointF = new PointF();
            double percent = mRadarEntries.get(m).level / 100.0;
            pointF.set(getPloygonX(mAverageAngle * m, percent),
                    getPloygonY(mAverageAngle * m, percent));
            mCoverPoints.add(pointF);
        }

        /**
         * 设置文字显示位置
         */
        for (int m = 0; m < mPieceNumber; m++) {
            PointF pointF = new PointF();
            String title = mRadarEntries.get(m).title;
            Rect textBound = new Rect();
            mTextPaint.getTextBounds(title, 0, title.length(),
                    textBound);
            float boundx = mRadarPointses.get(m).getPointFs().get(mLineSegments -1).x;
            float boundy = mRadarPointses.get(m).getPointFs().get(mLineSegments -1).y;
            if( boundx > mRadius && boundy <= mRadius) {
                pointF.set(getPloygonX(mAverageAngle * m, 1),
                        getPloygonY(mAverageAngle * m, 1) - textBound.height()*2) ;
            } else if ( boundx <= mRadius && boundy <= mRadius){
                pointF.set(getPloygonX(mAverageAngle * m, 1) - textBound.width(),
                        getPloygonY(mAverageAngle * m, 1) - textBound.height()*2);
            } else if( boundx <= mRadius && boundy > mRadius) {
                pointF.set(getPloygonX(mAverageAngle * m, 1) - textBound.width(),
                        getPloygonY(mAverageAngle * m, 1) );
            } else {
                pointF.set(getPloygonX(mAverageAngle * m, 1),
                        getPloygonY(mAverageAngle * m, 1));
            }
            mTextPoints.add(pointF);
        }
    }

    /**
     * 设置数据集,数据集的index决定位置,顺时针方向,起始角度为0度
     */
    public void setRadatEntries(List<RadarEntry> entries) {
        this.mRadarEntries = entries;
        mPieceNumber = entries.size();
        postInvalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 绘制中心点
         */
        canvas.drawPoint(mPositionX, mPositionY, mRadarPaint);

        /**
         * 绘制蜘蛛网
         */
        for (int i = 0; i < mLineSegments; i++) {
            for (int j = 0; j < mPieceNumber - 1; j++) {
                canvas.drawLine(mRadarPointses.get(j).getPointFs().get(i).x, mRadarPointses.get(
                        j).getPointFs().get(i).y,
                        mRadarPointses.get(j + 1).getPointFs().get(i).x, mRadarPointses.get(
                                j + 1).getPointFs().get(i).y, mRadarPaint);
            }
            canvas.drawLine(mRadarPointses.get(mPieceNumber - 2).getPointFs().get(i).x,
                    mRadarPointses.get(mPieceNumber - 2).getPointFs().get(i).y,
                    mRadarPointses.get(mPieceNumber - 1).getPointFs().get(i).x, mRadarPointses.get(
                            mPieceNumber - 1).getPointFs().get(i).y, mRadarPaint);

            canvas.drawLine(mRadarPointses.get(mPieceNumber - 1).getPointFs().get(i).x,
                    mRadarPointses.get(mPieceNumber - 1).getPointFs().get(i).y,
                    mRadarPointses.get(0).getPointFs().get(i).x, mRadarPointses.get(
                            0).getPointFs().get(i).y, mRadarPaint);
        }

        /**
         * 绘制轴线
         */
        for (int k = 0; k < mPieceNumber; k++) {
            canvas.drawLine(mRadarPointses.get(k).getPointFs().get(0).x,
                    mRadarPointses.get(k).getPointFs().get(0).y,
                    mRadarPointses.get(k).getPointFs().get(mLineSegments - 1).x, mRadarPointses.get(
                            k).getPointFs().get(mLineSegments - 1).y, mRadarPaint);
        }

        /**
         * 绘制数据
         */
        if (mCoverPoints != null && mCoverPoints.size() == mPieceNumber) {
            mCoverPath.reset();
            mCoverPath.moveTo(mCoverPoints.get(0).x, mCoverPoints.get(0).y);
            for (int i = 1; i < mPieceNumber; i++) {
                mCoverPath.lineTo(mCoverPoints.get(i).x, mCoverPoints.get(i).y);
            }
            mCoverPath.close();
            canvas.drawPath(mCoverPath, mCoverPaint);
        } else {
            throw new NullPointerException("请先设置数据集");
        }

        /**
         * 绘制文字,使用StaticLayout进行换行文字的绘制
         */
        for (int i = 0; i < mPieceNumber; i++) {
            canvas.save();
            String str= mRadarEntries.get(i).title + "\r\n" + Math.floor(mRadarEntries.get(i).level*10)/10;
            StaticLayout layout = new StaticLayout(str, mTextPaint, 300,
                    Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
            canvas.translate(mTextPoints.get(i).x,mTextPoints.get(i).y);
            layout.draw(canvas);
            canvas.restore();
        }

    }

    public float getPloygonX(double angle, double percent) {
        return Float.parseFloat(
                String.valueOf(
                        mPositionX + Math.cos(angle / 360.0 * 2 * Math.PI) * mRadius * percent));
    }

    public float getPloygonY(double angle, double percent) {
        return Float.parseFloat(String.valueOf(
                mPositionY + Math.sin(angle / 360.0 * 2 * Math.PI) * mRadius * percent));
    }

    /**
     * 雷达图数据载体
     */
    public static class RadarEntry {
        private String title;
        private Float level;

        public RadarEntry(String title, float level) {
            this.title = title;
            this.level = level;
        }
    }

    /**
     * 每一条线上的所有点集合
     */
    public class RadarPoints {
        int index;
        List<PointF> mPointFs;

        public RadarPoints(int index, List<PointF> pointFs) {
            this.index = index;
            mPointFs = pointFs;
        }

        public int getIndex() {
            return index;
        }

        public void setIndex(int index) {
            this.index = index;
        }

        public List<PointF> getPointFs() {
            return mPointFs;
        }

        public void setPointFs(List<PointF> pointFs) {
            mPointFs = pointFs;
        }
    }

}

可能画图过程中的实现方式优点粗糙,大家可以根据自己的方式,改写即可。 
个人是先将每条半径上的端点全部计算出来进行保存,然后一次连接,然后把每个半径首尾端点连接,然后绘制覆盖区域,覆盖区域也是采用先计算点位置,然后连接的方式实现,最后确定文本的显示位置和文字的绘制。由于这里涉及到绘制换行文字,使用到来StaticLayout。使用方法请查阅对应API。

4. 效果



http://www.niftyadmin.cn/n/862253.html

相关文章

韩国通关号免费查询系统,韩国清关码校验

今天给大家带来的是韩国通关码反查纠错系统于2022年9月26日的升级内容介绍。 第一&#xff0c;查询网址&#xff1a;韩国通关码查询 第二&#xff0c;系统再次启用了批量通关码信息校验的功能&#xff0c;我们的老用户可能并不陌生&#xff0c;不过呢这次启用后&#xff0c;还…

自动轮播RollPagerView

实现轮播图防止浪费大量时间 1.添加依赖 compile ‘com.jude:rollviewpager:1.2.9’ 2.在xml布局中添加xml <com.jude.rollviewpager.RollPagerViewandroid:id"id/ropagerView"android:layout_width"match_parent"android:layout_height"match_p…

登录注册并记住状态

点击由此页面调到登录页面 public class HomeActivity extends FragmentActivity { List《Fragment》 list; ViewPager vp; RadioGroup rg; RadioButton rb1,rb2,rb3,rb4,rb5; Fragment_sy fragment_sy; Fragment_wt fragment_wt; Fragment_fl fragment_xx; Fragment_…

2011两部最新著作目录抢先爆光

自从本人在QQ读者群和微博中发布本人即将于今年6月份&#xff08;也可能会提前到5月份&#xff09;出版两本专门的网管职业指南图书&#xff1a;《揭密&#xff1a;把脉网管》和《高薪网管之路》后&#xff0c;就有许多读者朋友急不可待地想了解这两部书到底讲些什么&#xff0…

属性动画图片从上移动到屏幕中间,放大图片的二倍再缩小到原来,自定义圆实现倒计时,解析数据显示,点击条目实现js交互

//主页面 public class MainActivity extends AppCompatActivity { private ProgressBarView pbv; private int progress 120; private int time 3; private Handler handler new Handler(){ Override public void handleMessage(Message …

Android 属性动画:这是一篇很详细的 属性动画

前言 动画的使用 是 Android 开发中常用的知识本文将详细介绍 Android 动画中 属性动画的原理 & 使用 目录 目录1. 属性动画出现的原因 属性动画&#xff08;Property Animation&#xff09;是在 Android 3.0&#xff08;API 11&#xff09;后才提供的一种全新动画模式那…

中国包裹通关韩国,通关码协助通关,快速拯救通关码清关码不对错误难题

韩国海淘&#xff0c;除了产品的真伪&#xff0c;最关心的另一个问题就是清关的时效了。 以往&#xff0c;海关清关都是随机的&#xff0c;清关时间可长可短&#xff0c;有的运气好&#xff0c;最快1、2天就能出关&#xff0c;被抽检到&#xff0c;慢的话&#xff0c;7、8个工…

人月神话

扫码免费领红包 THE MYTHICAL MAN-MONTH 人月神话 FREDERICK P. BROOKS, JR. 翻译&#xff1a;Adams Wang 关于作者 Frederick P. Brooks&#xff0c;Jr . 是北卡罗来纳大学Kenan-Flagler 商学院的计算机科学教授&#xff0c;北卡来罗来纳大学位于美国北卡来罗来纳州的查布尔…