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

前几天简单熟悉了下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 / 设备密度
    • 常规设备密度: 320x240(0.75), 480x320(1), 800x480(1.5), 1280x720(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 屏幕适配 屏幕适配的概念 碎片化既是 Android 的优势和弱点,也是开发者们头疼的问题,同时也为 Androi...
    s酸菜阅读 9,723评论 9 58
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 《书都不会读,你还想成功》 二志成 🌟🌟🌟 如果将养成读书的习惯比作是跑步习惯的养成,这本书对于很长一段时间不喜欢...
    didimay阅读 152评论 0 0
  • 《如梦令》 时似流光而失, 岁月静安如昔。 空洞夜苍茫, 追梦往时足迹。 依惜,依惜, 一切宛如昨日。
    黄尚彪阅读 255评论 0 2
  • 可是要过好久好久 才知道自己也能过得好好 干嘛去迎合所有人 真棒!
    蛋蛋旅安阅读 184评论 0 0