美文网首页
Android扇形图自定义

Android扇形图自定义

作者: 2012lc | 来源:发表于2018-12-04 13:48 被阅读0次

扇形图自定义

示例.png 示例 (2).png 示例 (3).png 示例 (4).png 示例 (5).png

实现

总体思路:根据每个数据所占的比例*360得到其在圆中所属的弧度,然后调用drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,@NonNull Paint paint)画出对应区域,即可成为扇形图。字体需要调用drawText(@NonNull String text, float x, float y, @NonNull Paint paint),计算出坐标,然后画出。
1.在设置数据时拿到所有数据的总和;

  if (data.isNotEmpty()) {
            for (i in 0 until data.size) {
                mTotalNum = data[i].num!! + mTotalNum!!
            }
        }

2.重写onSizeChanged函数,得到圆形的圆心位置以及半径,算出字体所在半径,算出画圆时需要的矩形坐标;

//圆心位置
                centerPosition!!.x = w / 2
                centerPosition!!.y = h / 2
                //半径
                minWidth =
                        Math.min(w - paddingLeft - paddingRight, h - paddingBottom - paddingTop)
raduis=(minWidth!! / 2).toFloat()
 dataRaduis = raduis!! * 3 / 4
        //矩形坐标
        mRectF!!.left = centerPosition!!.x - raduis!!
        mRectF!!.top = centerPosition!!.y - raduis!!
        mRectF!!.right = centerPosition!!.x + raduis!!
        mRectF!!.bottom = centerPosition!!.y + raduis!!

3.重写onDraw函数,计算每一个块数据对应的开始弧度和滑过的弧度,并画出对应的区域,计算字体所在的x和y坐标,画出;

mStartAngle = 0f
        mSweepAngle = 0f
        for (i in 0 until mData!!.size) {
            mPieChartPaint!!.color = mData!![i].color!!

            mSweepAngle = (mData!![i].num!! / mTotalNum!!) * 360 * mPercent!!

            //画圆
            canvas.drawArc(mRectF!!, mStartAngle!!, mSweepAngle!!, true, mPieChartPaint!!)
            mStartAngle = mStartAngle!! + mSweepAngle!!
            if (mLayoutType == "pointingInstructions") {
                //指向说明
                pointData(canvas, i)
            } else {
                //画数据
                drawData(canvas, i)
            }
        }
 private fun drawData(canvas: Canvas, i: Int) {
        val x = centerPosition!!.x + dataRaduis!! *
                Math.sin(Math.toRadians((90 + mStartAngle!! - mSweepAngle!! / 2).toDouble())).toFloat()
        val y = centerPosition!!.y - dataRaduis!! *
                Math.cos(Math.toRadians((90 + mStartAngle!! - mSweepAngle!! / 2).toDouble())).toFloat()
 canvas.drawText(mData!![i].name!!, x, y, mDataPaint!!)
                canvas.drawText(
                    mData!![i].num!!.toString() + mData!![i].unit,
                    x,
                    y - mDataPaint!!.ascent() + 5,
                    mUnitPaint!!
    }

扩展

以上,一个基本的扇形图算是完成了,但是在实际的运用当中,一个有扩展的扇形图很有必要,所以在以上的基础上我们扩展一下。
1.加入动画,毕竟一个有动画的扇形图比没动画图的扇形图更加让人赏心悦目,所以加入动画很有必要了。每次得到绘画的进度mPercent,在绘画扇形时每次重新计算对应的弧度的进度mSweepAngle 和起始弧度mStartAngle ;

 private fun startAnim(animTime: Int) {
        mAnimator = ValueAnimator.ofFloat(0f, 1f)
        mAnimator!!.duration = animTime.toLong()
        mAnimator!!.addUpdateListener {
            mPercent = it.animatedValue as Float?
            postInvalidate()
        }
        mAnimator!!.start()
    }
 mSweepAngle = (mData!![i].num!! / mTotalNum!!) * 360 * mPercent!!
mStartAngle = mStartAngle!! + mSweepAngle!!

2.当数据比较小,导致无法在对应的扇形图中填入对应的信息,补充以下解决办法:
a.选择指向说明,引申出在外部说明,如示例.png。调用drawLine画出两条线,在最后一条线的末尾坐标画出对应的说明。

 private fun pointData(canvas: Canvas, i: Int) {
        val xP = centerPosition!!.x + raduis!! *
                Math.sin(Math.toRadians((90 + mStartAngle!! - mSweepAngle!! / 2).toDouble())).toFloat()
        val yP = centerPosition!!.y - raduis!! *
                Math.cos(Math.toRadians((90 + mStartAngle!! - mSweepAngle!! / 2).toDouble())).toFloat()
        val xEdP = centerPosition!!.x + (raduis!! + 20) *
                Math.sin(Math.toRadians((90 + mStartAngle!! - mSweepAngle!! / 2).toDouble())).toFloat()
        val yEdP = centerPosition!!.y - (raduis!! + 20) *
                Math.cos(Math.toRadians((90 + mStartAngle!! - mSweepAngle!! / 2).toDouble())).toFloat()
        var xLast = 0f
        xLast = if (mStartAngle!! - mSweepAngle!! / 2 >= 270 || mStartAngle!! - mSweepAngle!! / 2 <= 90) {
            xEdP + 30
        } else {
            xEdP - 30
        }
        canvas.drawLine(xP, yP, xEdP, yEdP, mPointingPaint!!)
        canvas.drawLine(xEdP, yEdP, xLast, yEdP, mPointingPaint!!)
        canvas.drawText(mData!![i].name!!, xLast, yEdP, mDataPaint!!)
        canvas.drawText(
            mData!![i].num!!.toString() + mData!![i].unit,
            xLast,
            yEdP - mDataPaint!!.ascent() + 5,
            mUnitPaint!!
        )
    }

b.在右侧或者底部说明,动态加入RecyclerView,在RecyclerView中填入说明,在扇形图中填入对应的数字或者比列即可。让MyPieChartView继承FrameLayout,动态加入RecyclerView控件,匹配对应的adapter

 private fun addHorizontal() {
        mRecyclerView = RecyclerView(context)
        mRecyclerView!!.layoutManager = LinearLayoutManager(context)
        mRecyclerView!!.isNestedScrollingEnabled = false
        if (adapter == null) {
            adapter = mAdapter(mContext!!, mData)
            mRecyclerView!!.adapter = adapter
        } else {
            adapter!!.notifyDataSetChanged()
        }

        val relativeLayout = RelativeLayout(context)
        val params = LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup
                .LayoutParams.WRAP_CONTENT
        )
        params.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
        relativeLayout.layoutParams = params
        val p2 = RelativeLayout.LayoutParams(
            ViewGroup.LayoutParams
                .WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
        )
        relativeLayout.addView(mRecyclerView, p2)
        addView(relativeLayout)
    }

    private fun addVertical() {
        mRecyclerView = RecyclerView(context)
        mRecyclerView!!.layoutManager = GridLayoutManager(context, 3)
        mRecyclerView!!.isNestedScrollingEnabled = false
        if (adapter == null) {
            adapter = mAdapter(mContext!!, mData)
            mRecyclerView!!.adapter = adapter
        } else {
            adapter!!.notifyDataSetChanged()
        }

        val relativeLayout = RelativeLayout(context)
        val params = LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup
                .LayoutParams.WRAP_CONTENT
        )
        params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
        relativeLayout.layoutParams = params
        val p2 = RelativeLayout.LayoutParams(
            ViewGroup.LayoutParams
                .MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
        )
        relativeLayout.addView(mRecyclerView, p2)
        addView(relativeLayout)
    }

相关api

属性 名称
dataSize 数据大小
dataColor 数据颜色
numSize 数字或者比例大小
numColor 数字或者比例颜色
layoutType 布局方式(default 普通样式 vertical 竖向布局 horizontal 横向布局 pointingInstructions 指向说明)
verticalMargin 竖向布局时扇形与说明的间距
horiMargin 横向布局时扇形与说明的间距
animTime 动画时长
pointingColor 指向说明的线的颜色
pointingWidth 指向说明的线的宽度

使用方法

将MyPieChartView,Util,PieChartData,PieChartType,PieChartConstant,attrs.xml下的MyPieChartView拷贝到自己的项目中即可使用。
github:https://github.com/2016lc/MyCircleProgress

相关文章

网友评论

      本文标题:Android扇形图自定义

      本文链接:https://www.haomeiwen.com/subject/dbehcqtx.html