Android开发记录(7)-自定义百分比动态折线图

要求实现一个折线(曲线)图来查看android设备的内存和CPU占用情况。
场景主要用于设备测试和实时监控。

先看下实现效果

效果图.gif

这里用的是随机生成数据演示,当然实际上CPU和内存使用情况不会如此大幅度变化。

实现过程

一、自定义属性

自定义属性的好处主要体现在适配不同分辨率的设备,因为自定义View的绘画单位是px,而我们指定设置dp值转为px就能让视图在不同设备中展示出一样的效果。
在values目录下新建attrs.xml文件,自定义属性如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="LineChartView">
        <attr name="XPoint" format="reference|dimension"/>
        <attr name="XScale" format="reference|dimension"/>
        <attr name="YScale" format="reference|dimension"/>
        <attr name="XLength" format="reference|dimension"/>
        <attr name="YLength" format="reference|dimension"/>

    </declare-styleable>
</resources>

二、自定义view

直接上代码

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;

public class LineChartView extends View{
    private Paint paint;
    private float XPoint = 0;
    private float XScale = 0;
    private float YScale = 0;
    private float XLength = 0;
    private float YLength = 0;
    private float DEFAULT_XPoint = 50;
    private float DEFAULT_XScale = 8;
    private float DEFAULT_YScale = 60;
    private float DEFAULT_XLength = 320;
    private float DEFAULT_YLength = 300;
    private int MaxDataSize = 0 ;
    //内存数据
    private List<Float> dataMemory = new ArrayList<Float>();
    //CPU数据
    private List<Float> dataCPU = new ArrayList<Float>();
    //刻度
    private String[] YLabel = new String[]{"0","25","50","75","100"};

    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // TODO Auto-generated method stub
            switch (msg.what) {
                case 1:
                LineChartView.this.invalidate();
            }
            return false;
        }
    });

    public LineChartView(Context context) {
        super(context);
        init();
    }

    public LineChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAttr(context, attrs);
        init();
    }

    public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs);
        init();
    }

    /**
     * 获得属性值
     * @param context
     * @param attrs
     */
    private void initAttr(Context context, AttributeSet attrs) {
        if (attrs == null) {
            return;
        }
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LineChartView);
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i);
            if (attr == R.styleable.LineChartView_XPoint) {
                XPoint = array.getDimension(attr, dp2px(context, DEFAULT_XPoint));
            } else if (attr == R.styleable.LineChartView_XScale) {
                XScale = array.getDimension(attr, dp2px(context, DEFAULT_XScale));
            } else if (attr == R.styleable.LineChartView_YScale) {
                YScale = array.getDimension(attr, dp2px(context, DEFAULT_YScale));
            }else if (attr == R.styleable.LineChartView_XLength) {
                XLength = array.getDimension(attr, dp2px(context, DEFAULT_XLength));
            }else if (attr == R.styleable.LineChartView_YLength) {
                YLength = array.getDimension(attr, dp2px(context, DEFAULT_YLength));
            }
        }
        array.recycle();
    }

    private void init(){
        paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);
        //刻度
        YLabel[0]="0";
        YLabel[1]="25";
        YLabel[2]="50";
        YLabel[3]="75";
        YLabel[4]="100";
        //计算最大数据数
        MaxDataSize = Math.round(XLength / XScale);
    }

    /**
     * 更新内存、CPU数据
     * @param newDataMemory
     * @param newDataCPU
     */
    public void setNewData(Float newDataMemory,Float newDataCPU){
        //memory
        if(dataMemory.size() >= MaxDataSize){
            dataMemory.remove(0);
        }
        dataMemory.add(newDataMemory);
        //cpu
        if(dataCPU.size() >= MaxDataSize){
            dataCPU.remove(0);
        }
        dataCPU.add(newDataCPU);
        //update
        handler.sendEmptyMessage(1);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setColor(Color.BLUE);
        //画Y轴
        canvas.drawLine(XPoint, 0, XPoint, YLength, paint);
        //Y轴箭头
        canvas.drawLine(XPoint, 0, XPoint - 3, 6, paint);  //箭头
        canvas.drawLine(XPoint, 0, XPoint + 3, 6 ,paint);
        //添加刻度和文字
        for(int i=0; i * YScale < YLength; i++) {
            canvas.drawLine(XPoint, YLength - i * YScale, XPoint + 5, YLength - i * YScale, paint);  //刻度
            canvas.drawText(YLabel[i], XPoint - 30, YLength - i * YScale, paint);//文字
        }
        //画X轴
        canvas.drawLine(XPoint, YLength, XPoint + XLength, YLength, paint);
        //画标识
        paint.setColor(Color.RED);
        canvas.drawText("内存百分比", XLength, 15, paint);//文字
        canvas.drawLine(XLength-20, 15, XLength-10, 15, paint);
        paint.setColor(Color.MAGENTA);
        canvas.drawText("CPU百分比", XLength, 30, paint);//文字
        canvas.drawLine(XLength-20, 30, XLength-10, 30, paint);

        //画内存数据
        paint.setColor(Color.RED);
        if(dataMemory.size() > 1){
            for(int i=1; i<dataMemory.size(); i++){
                canvas.drawLine(XPoint + (i-1) * XScale, YLength - dataMemory.get(i-1) * (YScale*4),
                        XPoint + i * XScale, YLength - dataMemory.get(i) * (YScale*4), paint);
            }
        }
        //画CPU数据
        paint.setColor(Color.MAGENTA);
        if(dataCPU.size() > 1){
            for(int i=1; i<dataCPU.size(); i++){
                canvas.drawLine(XPoint + (i-1) * XScale, YLength - dataCPU.get(i-1) * (YScale*4),
                        XPoint + i * XScale, YLength - dataCPU.get(i) * (YScale*4), paint);
            }
        }
    }

    /**
     * sp2px
     * @param context
     * @param spValue
     * @return
     */
    private int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    /**
     * dp2px
     * @param context
     * @param dpValue
     * @return
     */
    private int dp2px(Context context, float dpValue) {
        final float densityScale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * densityScale + 0.5f);
    }
}

具体的注释应该还算清晰。

三、XML中使用

    <com.components.LineChartView
        android:id="@+id/linechart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:XPoint="50dp"
        app:XScale="10dp"
        app:YScale="40dp"
        app:XLength="200dp"
        app:YLength="200dp"
        >
    </com.components.LineChartView>

四、Activity中使用

请根据实际使用填充数据,这里只以演示效果2秒随机生成数值填充。如效果图所示。

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.components.LineChartView;

import butterknife.BindView;
import butterknife.ButterKnife;

public class TestActivity extends AppCompatActivity {
    @BindView(R.id.linechart)
    LineChartView linechart;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        ButterKnife.bind(this);

        new Thread(){
            public void run(){
                while (true){
                    try {
                        Thread.sleep(2000);
                        linechart.setNewData((float)Math.random(),(float)Math.random());
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

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