zoukankan      html  css  js  c++  java
  • 自己定义View之Chart图标系列(1)——点阵图

    近期要做一些图表类的需求,一開始就去github上看了看,发现开源的图表框架还是蛮多的。可是非常少有全然符合我的需求的。另外就是使用起来比較麻烦。所以就决定自己来造轮子了~~~
    今天要介绍的就是Android图标系列中点阵图(姑且这么叫着吧╮(╯▽╰)╭)的画法。
    效果图例如以下:
    这里写图片描写叙述

    需求:
    1. 给出几个点 画出坐标轴(用虚线)
    2. 画出相应的点 在点的上方标出数值
    3. 下方要显示个数值表示的意义
    4. 重点!!

    !动态计算坐标轴,多余的坐标不能显示。

    前三条好理解。第四条啥意思呢~
    (比方说,我们数据是{10.1,12.5, 20.2, 15.1} 那么我们的坐标轴起点就应该是从10開始画,而不是0!)例如以下图所看到的
    这里写图片描写叙述

    接下来我们就来一步步实现这些需求吧~
    1.定义数据模型

    **
     * Created by JK on 2016/1/18.
     * 点阵折线图的数据模型
     */
    public class DotChartItem {
        public int color;//颜色
        public float value;//值
        public String title;//标题
    
        public DotChartItem(int color, float value, String title) {
            this.color = color;
            this.value = value;
            this.title = title;
    
        }
    
    
    }

    2.自己定义View —— JKLineChart

    首先是初始化部分

    /**
     * Created by JK on 16/1/18.
     * 用毛的开源项目自己写控件之点阵图
     * 这是一个点阵图
     */
    public class JKLineChart extends View {
    
        private static final String TAG = "JKLineChart";
        private Context mContext;
        private Paint mCoordPaint;//坐标线的Paint
        private Paint mIndicPaint;  //点图的Paint
        private Paint mHintTitlePaint; //提示块的Paint
        private int mWidth;
        private int mHeight;
        private int mCoordColor = Color.GRAY; //坐标线的color
        private int mPadding = 20;
        private int mGapHeight = 50;//坐标轴之间的间隔
        private float mTextX = 0f; //提示模块中。第一个方块的X坐标(和坐标上的字分离)
        private float[] mMinAndMaxPointY = new float[2]; //最低点和最高点的Y坐标(參考点 用来计算方块的坐标)
        private List<LineChartItem> mItemList; //数据集合
    
        private List<LineChartItem> mTypeSet;//下方提示模块的数据集合
        private float[] values;
    
        enum IndicatorType {  //点阵图 图形类型
            CIRCLE, //圆
            RECTANGLE //方块
        }
    
        private IndicatorType mIndicatorType = IndicatorType.RECTANGLE;
        private int mGap = 5; //坐标轴之间的间隔
        private int mIndicWidth = 5; //小方块的宽度 或者小圆圈的直径
        //以下2个值用来做动画
        private int mInitWidth = 0; 
        private int mSpeed = 1;
    
    
    
        private boolean isShowHintBlock = true; //是否显示底下的提示模块
        private int mHintTitleHeight = 30; //提示模块高度
        private final double EP = 0.000000001;
    
        public JKLineChart(Context context) {
            this(context, null);
        }
    
        public JKLineChart(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public JKLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.JKLineChart, 0, 0);
            try {
                isShowHintBlock = typedArray.getBoolean(R.styleable.JKLineChart_showHintBlock, true);
                //坐标轴之间的间隔 默觉得5
                mGap = typedArray.getInt(R.styleable.JKLineChart_coordGap, mGap);
                //点是用圆圈还是用方块来画
                int isRound = typedArray.getInt(R.styleable.JKLineChart_indicatorType, 1);
                Log.d(TAG, "ROUND :" + isRound);
                if (isRound == 0) {
                    mIndicatorType = IndicatorType.CIRCLE;
                } else {
                    mIndicatorType = IndicatorType.RECTANGLE;
                }
    
            } finally {
                typedArray.recycle();
            }
    
            init(context);
        }

    上面的操作主要是定义一些变量。并从构造函数中获取我们的自己定义属性

     private void init(Context context) {
            this.mContext = context;
            mItemList = new ArrayList<LineChartItem>();
            //mPadding = dip2px(context,mPadding);
            mIndicWidth = dip2px(context, mIndicWidth);
            mHintTitleHeight = dip2px(context, 40);
            mGapHeight = dip2px(context,mGapHeight);
            initPaint();
        }
        //初始化画笔
        private void initPaint() {
            mIndicPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mCoordPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mHintTitlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mCoordPaint.setColor(mCoordColor);
            mCoordPaint.setTextSize(40);
        }
        @Override
        protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
            this.mWidth = width;
            this.mHeight = height;
            if (isShowHintBlock) {
                mHeight = mHeight - mHintTitleHeight;
            }
            Log.d(TAG, "onSizeChanged");
    
        }
    

    mHeight 代表的是点阵图的高度(不包含下方的提示部分)。假设要显示下方的提示部分,就要减去提示部分的高度。
    以下就是最重点。最基本的onDraw方法喽

    private void animatedDraw(Canvas canvas) {
            if (mItemList == null || mItemList.size() == 0)
                return;
    
            drawCoordText(canvas);
            drawPoint(canvas);
            if (isShowHintBlock) {
                drawHintTitle(canvas);
            }
    
        }

    分三步走,
    第一 drawCoordText画坐标线
    第二 drawPoint画点
    第三 drawHintTitle画下方提示部分

    /**
         * 画坐标线
         * @param canvas
         */
        private void drawCoordText(Canvas canvas) {
            //获取坐标上下限的值
            float[] array = getMinAndMaxCoord();
            //坐标轴总数量
            float totalCount = (array[1] -array[0])/ mGap +1;
            for (float i = array[1], count = 0; i >= array[0]; i = i - mGap, count++) {
                String text = (int) i + "";
                Rect textRect = new Rect();
                mCoordPaint.getTextBounds(text, 0, text.length(), textRect);
                float y = mPadding*3 + mGapHeight*count;
                if (count == 0) {
                    mMinAndMaxPointY[1] = y; //最高点的坐标
                } else if (count == totalCount - 1) {
                    mMinAndMaxPointY[0] = y; //最低点的坐标
                }
                canvas.drawText(text, mPadding, y, mCoordPaint);
                //canvas.drawLine(mPadding,y,mWidth,y,mCoordPaint);
                //画虚线坐标线
                PathEffect effects = new DashPathEffect(new float[]{5, 10}, 1);
                Path path = new Path();
                path.moveTo(mPadding * 2f + textRect.width(), y - textRect.height() / 2);
                if (mTextX >= -EP && mTextX <= EP) {
                    mTextX = mPadding * 2f + textRect.width();
                }
                path.lineTo(mWidth - mPadding, y - textRect.height() / 2);
                mCoordPaint.setStyle(Paint.Style.STROKE);
                mCoordPaint.setPathEffect(effects);
                canvas.drawPath(path, mCoordPaint);
    
            }
        }

    这里有两个辅助函数非常重要!

    。!堪称画坐标线的核心!

    前面提到过,我们的坐标轴是依据传入的数值动态计算的,而不是直接从0開始画的。

     private float[] getMaxAndMin(float[] array) {
            float min, max;
            min = max = array[0];
            float[] result = new float[2];
            for (int i = 0; i < array.length; i++) {
                System.out.print(array[i] + " ");
                if (Float.compare(array[i], max) > 0)   // 推断最大值
                    max = array[i];
                if (Float.compare(array[i], min) < 0)   // 推断最小值
                    min = array[i];
            }
            result[0] = min;
            result[1] = max;
            return result;
        }

    上面这个函数用来获取一个数组中的最大最小值,不管数组是否排序,这个比較简单。

    /**
         * 动态计算最小坐标值和最大坐标值x
         * ps:{4.9f,6f,7f,8f,9.1f}  坐标最小值为  = 0; 坐标最大值 = 10;
         *
         * @return
         */
        private float[] getMinAndMaxCoord() {
            float[] array = getMaxAndMin(values);
            float[] result = new float[2];
            float min = array[0];
            float max = array[1];
            result[0] = (Math.round((min - 0.5f)) / mGap * mGap); //坐标值最小值
            result[1] = ((int) (floor((max + mGap - 0.5f)) / mGap) * mGap); //坐标值最大值
            return result;
        }

    上面这个函数用来计算坐标轴的最小值和最大值,
    比方数组是{4.9f,6f,7f,8f,9.1f} 坐标最小值为 = 0; 坐标最大值 = 10;
    数组是{5.1f,6f,7f,8f,10.1f} 坐标最小值为 = 5; 坐标最大值 = 15;
    floor是我们重写了地板除法

     /**
         * 重写floor除法
         *
         * @param value
         * @return
         */
        private float floor(float value) {
            float result = 0f;
            float tail = value % 1;
            int integer = (int) (value - tail);
            if (Float.compare(tail, 0.5f) > 0) {
    
                return integer + 1;
            } else if (Float.compare(tail, 0.5f) <= 0) {
    
                return integer;
            }
            return result;
        }
    
    
    第二步: 画点
    
     /**
         * 画小方块
         *
         * @param canvas
         */
        private void drawPoint(Canvas canvas) {
            float totalWidth = mWidth - 2 * mPadding - mTextX;  //全部方块所能占领的总面积
            int size = values.length;
            for (int i = 0; i <size; i++) {
                float value = values[i];
                int color = mItemList.get(i).color;
                mIndicPaint.setColor(color);
                mIndicPaint.setTextSize(40);
                String text = value + "";
                Rect textRect = new Rect();
                mIndicPaint.getTextBounds(text, 0, text.length(), textRect);
                if (mIndicatorType == IndicatorType.RECTANGLE) {
                    float top = getPointYByValue(value);
                    float left = mPadding + mTextX + totalWidth / size * i + (totalWidth / size - mInitWidth) / 2;
                    float right = left + mInitWidth;
                    float bottom = top + mInitWidth;
                    RectF indicRect = new RectF(left, top, right, bottom);
                    canvas.drawText(text, left, top - textRect.height(), mIndicPaint);
                    canvas.drawRect(indicRect, mIndicPaint);
                }
            }
            if (mInitWidth <= mIndicWidth) {
                mInitWidth += mSpeed;
                invalidate();
            }
        }

    这里写图片描写叙述
    mTextX是右側方块区域的起始坐标。用来与坐标数字分离,totalWidth是全部点所能占领的宽度,依据点的数量将totalWidth等分,然后居中显示在各自的区域里。
    这里我们仅仅实现了点为方块的样式。点为圆圈的样式各位看官能够自己尝试去实现以下。

    getPointYByValue函数比較重要。是依据点的值来计算坐标的。

    
    
    
     /**
         * 依据值来计算方块的y坐标
         *
         * @param value
         * @return
         */
        private float getPointYByValue(float value) {
            float[] array = getMinAndMaxCoord();
            float diffY = mMinAndMaxPointY[0] - mMinAndMaxPointY[1]; //坐标值之间的差值
            float diffValue = array[1] - array[0];
            float y = mMinAndMaxPointY[1] + diffY/diffValue* (array[1]-value)-mIndicWidth*3/2;
            return y;
        }

    最后 就是画底部的提示区域了

     public void getHintTitleList() {
            mTypeSet = new ArrayList<LineChartItem>(mItemList);
            for (int i = 0; i < mTypeSet.size() - 1; i++) {
                for (int j = mTypeSet.size() - 1; j > i; j--) {
                    if (mTypeSet.get(j).title.equals(mTypeSet.get(i).title)) {
                        mTypeSet.remove(j);
                    }
                }
            }
            Collections.sort(mTypeSet, new Comparator<LineChartItem>() {
                @Override
                public int compare(LineChartItem lhs, LineChartItem rhs) {
                    return -(Float.compare(lhs.value, rhs.value));
                }
            });
        }
    
        private void drawHintTitle(Canvas canvas) {
            getHintTitleList();
            int totalWidth = mWidth - mPadding * 2;
            int width = totalWidth / mTypeSet.size();
            float startY = mMinAndMaxPointY[0] + mPadding * 3;
            for (int i = 0; i < mTypeSet.size(); i++) {
                //draw
                LineChartItem type = mTypeSet.get(i);
                String text = type.title;
                int color = type.color;
                mHintTitlePaint.setColor(color);
                mHintTitlePaint.setTextSize(40);
                Rect textRect = new Rect();
                mHintTitlePaint.getTextBounds(text, 0, text.length(), textRect);
                float x = (width - (mIndicWidth*2 + mPadding + textRect.width())) / 2 + i * width;
                RectF indicRect = new RectF(x + mPadding, startY, x + mIndicWidth*2 + mPadding, startY + mIndicWidth*2);
                canvas.drawRect(indicRect, mHintTitlePaint);
                mHintTitlePaint.setColor(mCoordColor);
                canvas.drawText(text, mPadding * 3 + x, startY + mIndicWidth*2, mHintTitlePaint);
            }
    
        }

    getHintTitleList将我们传入的数组筛选出提示标题,并依照数值排序。

    最最后~!

    !。,因为我们的坐标可能有非常多。0-——无穷都有可能,所以我们自己定义View的高度不是固定的,须要动态计算出来。

    public void setItemList(List<LineChartItem> list) {
            if(list.size()==0 || list == null)
                return;
            mItemList.clear();
            mItemList.addAll(list);
            int size = mItemList.size();
            values = new float[mItemList.size()];
            for (int i = 0; i < size; i++) {
                values[i] = mItemList.get(i).value;
            }
            computerHeight();
            invalidate();
        }
    
        private void computerHeight() {
            float[] array = getMinAndMaxCoord();
            //坐标轴总数量
            float totalCount = (array[1] -array[0])/ mGap  ;
            //上面折线图的最大Y坐标
            float y = mPadding*3 + mGapHeight*totalCount;
            //以下提示部分的起始Y坐标
            float startY = y + mPadding * 3;
            int height = (int) (startY+mIndicWidth*2+mPadding*2);
            ViewGroup.LayoutParams lp = getLayoutParams();
            lp.height = height;
            this.setLayoutParams(lp);
            Log.d(TAG,"HEIGHT:"+height);
    
        }

    每次我们填充时间的时候,都会先调用computerHeight来动态计算视图高度

    使用方式 SO EASY~!

     LineChartItem item1 = new LineChartItem(Color.parseColor("#00ff00"), 12.5f, "正常");
            LineChartItem item2 = new LineChartItem(Color.parseColor("#0000ff"), 10.1f, "偏低");
            LineChartItem item3 = new LineChartItem(Color.parseColor("#FF0000"), 20.2f, "偏高");
            LineChartItem item4 = new LineChartItem(Color.parseColor("#FF0000"), 15.1f, "偏高");
            List<LineChartItem> list = new ArrayList<LineChartItem>();
            list.add(item1);
            list.add(item2);
            list.add(item3);
            list.add(item4);
            mLineChart.setItemList(list);

    你仅仅用将数据传递给LineChart 。剩下的工作就会自己主动完毕了,就是这么简单,全然没有网上那些开源控件用起来那么复杂吧~~

    源代码 我的github:https://github.com/devilthrone/JKChart
    欢迎fork and starO(∩_∩)O
    OVER!

  • 相关阅读:
    hdu 1527威佐夫博弈
    hdu 1506
    hdu 1878 欧拉回路
    欧拉回路知识
    hdu 2545 并查集 树上战争
    hdu 2594 kmp
    hdu 1867 kmp匹配
    hdu 2844 多重背包二进制优化
    hdu 4006
    1047
  • 原文地址:https://www.cnblogs.com/cynchanpin/p/7019294.html
Copyright © 2011-2022 走看看