完全自定义控件-适配屏幕(好麻烦)的饼图控件

前几天简单熟悉了下canvas的API,今天再来做个小demo巩固下
回顾完全自定义控件-Canvas之绘制基本形状

自定义饼图

效果展示

《完全自定义控件-适配屏幕(好麻烦)的饼图控件》 控件效果展示
《完全自定义控件-适配屏幕(好麻烦)的饼图控件》 控件布局分析

不用看懂。大致示意图

思路

  • 通过控件所占的高度来确定圆的半径
  • 通过圆、小方块、文字的大小,及他们的间距确认控件内部的布局
  • 根据得到的数据算出所占角度,画出相应的扇形、小方块、文字

实现步骤

  1. 新建PieView类继承View。
  2. 重写View的三个构造函数,在构造函数中初始化数据。
  3. 在onSizeChanged()方法中,定义控件中各个部分的尺寸
  4. 在onDraw()方法中取得数据,并进行绘制。
  5. 在布局中使用控件
  6. 新建Pie对象,封装所需的颜色、占值、文字数据。
  7. 在MainActivity中调用控件的SetPie()方法完成数据的绑定

2. 重写View的三个构造函数,在构造函数中初始化数据

public PieView(Context context) {
        this(context, null);
    }
 
    public PieView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public PieView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
 
    private void init() {
        mPieColorList = new ArrayList<>(); //存放颜色
        mPieValue = new ArrayList<>(); //存放所占值
        mSrtingList = new ArrayList<>(); //存放文字
        mPaint = new Paint();
        mMaxString = "";  //最长的字符串,用于测量控件内容最大宽度
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(20);//画笔宽度
        mPaint.setAntiAlias(true);//抗锯齿
    }

3. 控件中各个物件尺寸的确定

通过控件的高度来决定饼图的大小
饼图直径=控件高度-任意值(比如10)

控件宽度=控件内容宽度+两边的间隔
控件内容宽度=饼图直径+饼图和矩形的距离+矩形的宽度+矩形和文字的距离+文字的宽度(只有文字的宽度由最长字符串测量决定,其他都固定)

控件内容的左边距=(控件宽度-内容宽度)/2
矩形的左边距=内容左边距+饼图直径+饼图和矩形的距离
文字的左边距=矩形的左边距+矩形宽度+矩形和文字的距离

饼图的圆心=(内容的左边距+圆半径,半高)

 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //文字宽度
        mTextWidth = (int) mPaint.measureText(mMaxString);
 
        mControlHalfHeight = h / 2;
        //饼图半径
        mPieRadios = mControlHalfHeight - 5;
        //控件内容宽度
        int contentWidth = mPieRadios * 2 + PIE_RECT_PADDING + RECT_WIDTH + RECT_TEXT_PADDING + mTextWidth;
        //内容的左边距
        int contentMarginLeft = (w - contentWidth) / 2;
        //矩形的左边距
        mRectMarginLeft = contentMarginLeft + mPieRadios * 2 + PIE_RECT_PADDING;
        //文字的左边距
        mTextMarginLeft = mRectMarginLeft + RECT_WIDTH + RECT_TEXT_PADDING;
        //第一个文字和控件顶部的距离
        mPadding = h / mPieArrayList.size() * 0.8f;
        //控制画圆的范围,圆心为(左边距+圆半径,半高)
        oval = new RectF(contentMarginLeft, mControlHalfHeight - mPieRadios,
                contentMarginLeft + mPieRadios * 2, mControlHalfHeight + mPieRadios);
    }

为了不让控件在不同的屏幕尺寸下发生太大的改变,我们需要做一点适配工作,使用dp进行布局。

  • 尺寸(dimens)适配
    • 获取设备密度:
      float density = getResources().getDisplayMetrics().density;
    • dp = px / 设备密度
    • 常规设备密度: 320×240(0.75), 480×320(1), 800×480(1.5), 1280×720(2)
    • 通过设置dp值, 让控件在不同的屏幕上显示的比例是一样的
    • 在dimens.xml中制定尺寸, 适配屏幕

dimens.xml文件


<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="fab_margin">16dp</dimen>
    <dimen name="pie_rect_padding">30dp</dimen>
    <dimen name="rect_text_padding">6dp</dimen>
    <dimen name="rect_width">15dp</dimen>
    <dimen name="text_size">18sp</dimen>
    <dimen name="text_vertical_padding">25dp</dimen>
 
</resources>

在PieView中使用

public class PieView extends View {
 
    //饼图和矩形的距离
    private final int PIE_RECT_PADDING = getResources().getDimensionPixelSize(R.dimen.pie_rect_padding);
    //矩形的宽度
    private final int RECT_WIDTH = getResources().getDimensionPixelSize(R.dimen.rect_width);
    //矩形和文字的距离
    private final int RECT_TEXT_PADDING = getResources().getDimensionPixelSize(R.dimen.rect_text_padding);
    ......

4. 开始绘制

绘制单个扇形

    private void drawPie(Canvas canvas, int amount) {
        mPaint.setColor(mCurrentColor);
        mPaint.setStyle(Paint.Style.FILL);
        //角度通过值所占的百分比*360度确定
        int angle = (int) (360f * amount / mMaxValue);
        canvas.drawArc(oval, mStartAngle, angle, true, mPaint);
        mStartAngle += angle;
    }

绘制矩形

private void drawRect(Canvas canvas) {
 
        if (mCurrentIndex == 0) {
            RectF rect = new RectF(mRectMarginLeft, mPadding,
                    mRectMarginLeft + RECT_WIDTH, mPadding + RECT_WIDTH);
            canvas.drawRect(rect, mPaint);
        } else {
            //如果矩形不是第一个,还要加上两个矩形的间隔距离
            RectF rect = new RectF(mRectMarginLeft, (mCurrentIndex) * TEXT_VERTICAL_PADDING + mPadding,
                    mRectMarginLeft + RECT_WIDTH, (mCurrentIndex) * TEXT_VERTICAL_PADDING + mPadding + RECT_WIDTH);
            canvas.drawRect(rect, mPaint);
        }
    }

绘制文字

private void drawText(Canvas canvas, String text) {
 
        mPaint.setColor(TEXT_COLOR);
        if (mCurrentIndex == 0) {
            canvas.drawText(text, mTextMarginLeft, mPadding + TEXT_SIZE * 0.8f, mPaint);
        } else {
            canvas.drawText(text, mTextMarginLeft, (mCurrentIndex) * TEXT_VERTICAL_PADDING + mPadding + TEXT_SIZE * 0.8f, mPaint);
        }
    }

进行循环绘制

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
 
        mStartAngle = -90;
        mCurrentIndex = 0;
        mMaxValue = 100;
        mPaint.setStyle(Paint.Style.FILL);
 
        for (int i = 0; i < mPieValue.size(); i++) {
            mCurrentColor = mPieColorList.get(mCurrentIndex);
            drawPie(canvas, mPieValue.get(mCurrentIndex));
            drawRect(canvas);
            drawText(canvas, mSrtingList.get(mCurrentIndex));
            mCurrentIndex++;
        }
    }

5. 在布局中使用控件

<zhj.canvasdemo.PieView
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:id="@+id/PieView"
        >
    <zhj.canvasdemo.PieView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:id="@+id/PieView2"
        android:layout_marginTop="20dp"
        >

6. 新建Pie对象,封装所需数据

public class Pie {
    //颜色
    public int PieColor;
    //所占的值
    public int PieValue;
    //文字
    public String PieString;
 
    public Pie(int pieValue, String pieString, int pieColor) {
        this.PieValue = pieValue;
        this.PieString = pieString;
        this.PieColor = pieColor;
    }
}

7. 在MainActivity中调用控件的SetPie()方法完成数据的绑定

public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        PieView pieView = (PieView) findViewById(R.id.PieView);
        ArrayList<Pie> pieArrayList=new ArrayList<>();
 
        Pie pie=new Pie(50,"JAVA需求",getResources().getColor(R.color.chart_color_1));
        pieArrayList.add(pie);
 
        Pie pie1=new Pie(30,"H5需求",getResources().getColor(R.color.chart_color_2));
        pieArrayList.add(pie1);
 
        Pie pie2=new Pie(10,"iOS需求",getResources().getColor(R.color.chart_color_3));
        pieArrayList.add(pie2);
 
        Pie pie3=new Pie(10,"Android需求",getResources().getColor(R.color.chart_color_4));
        pieArrayList.add(pie3);
 
        pieView.SetPie(pieArrayList);
 
 
        //第二个饼图
        PieView pieView2 = (PieView) findViewById(R.id.PieView2);
        ArrayList<Pie> pieArrayList2=new ArrayList<>();
        pieArrayList2.add(new Pie(20,"Android高级工程师",getResources().getColor(R.color.red)));
        pieArrayList2.add(new Pie(80,"Android小白",getResources().getColor(R.color.blue)));
        pieView2.SetPie(pieArrayList2);
    }
}

getColor方法在6.0中已经过时
可以参考以下方法:
ContextCompat.getColor(context, R.color.my_color)
http://blog.csdn.net/blue_bamboo/article/details/51131584

这里是项目地址

OK终于完成了,感谢原作者谪仙!
参考文章
http://www.cnblogs.com/kimmy/p/4918659.html

    原文作者:hongjay
    原文地址: https://www.jianshu.com/p/2d38e0b51b83
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞