zoukankan      html  css  js  c++  java
  • Android 自定义View -- 简约的折线图

    转载请注明出处:http://write.blog.csdn.net/postedit/50434634

    接上篇 Android 圆形百分比(进度条) 自定义view

    昨天分手了,不开心,来练练自定义view麻痹自己,毕竟菜鸟只能靠不断练习提高。#程序员不应该有女朋友#

    我们要实现的是一种只有来看趋势,不需要看具体数值,比较简约的折线图。比如下图这样的:


    这个时候,一些比较优秀的第三方图表库如:MPChart 就显得比较臃肿了。所以我们需要自定义一个折线图。

    老规矩,先来看最终的实现效果:


    其实这种做的很简约,大概分三个步骤:

    一、画坐标轴

    二、画点

    三、画线

    那么我们开始吧Let's go (Let it go)。

    设计一下大概需要的东西。首先把X轴和Y轴的数据存放在两个String[]里。

    具体的点的位置用一个Map<Integer,Integer>来存放.

    步骤:

    一、新建一个类,取名为SimpleLineChart继承View 重写他的构造方法。这里为了简便,就不添加自定义属性了attr.xml。

    <span style="font-size:18px;">   </span><span style="font-size:12px;"> public SimpleLineChart(Context context) {
            this(context, null);
        }
    
        public SimpleLineChart(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SimpleLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }</span>

    二、测量大小。(继续偷懒,只支持EXACTLY,AT_MOST直接丢异常)

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            if (widthMode == MeasureSpec.EXACTLY) {
                mWidth = widthSize;
            }else if(widthMode == MeasureSpec.AT_MOST){
                throw new IllegalArgumentException("width must be EXACTLY,you should set like android:width="200dp"");
            }
    
            if (heightMode == MeasureSpec.EXACTLY) {
                mHeight = heightSize;
            }else if(widthMeasureSpec == MeasureSpec.AT_MOST){
    
                throw new IllegalArgumentException("height must be EXACTLY,you should set like android:height="200dp"");
            }
    
            setMeasuredDimension(mWidth, mHeight);
        }

    三、重点来了,开始draw。首先处理几种异常情况。当X轴或者Y轴为没有文字(也就是没有刻度的时候),抛出异常。

    if(mXAxis.length==0||mYAxis.length==0){
                throw new IllegalArgumentException("X or Y items is null");
            }
    当没有任何点的数据的时候,显示字符串提醒用户没有数据(实际上是往中心drawText)。 

     //画坐标线的轴
            Paint axisPaint = new Paint();
            axisPaint.setTextSize(mYAxisFontSize);
            axisPaint.setColor(Color.parseColor("#3F51B5"));
    
    if (mPointMap == null || mPointMap.size() == 0) {
                int textLength = (int) axisPaint.measureText(mNoDataMsg);
                canvas.drawText(mNoDataMsg, mWidth/2 - textLength/2, mHeight/2, axisPaint);
            } 

    异常情况处理完了,开始上面三个步骤挨个画,首先来画个Y轴,计算每个刻度的间隔。他的值应该是mWidth / Y轴文字个数,然后用循环把每个刻度都画出来。再申请一个数组yPoints,存放每个Y刻度的具体坐标。

      //画 Y 轴
    
    
                //存放每个Y轴的坐标
                int[] yPoints = new int[mYAxis.length];
    
    
                //计算Y轴 每个刻度的间距
                int yInterval = (int) ((mHeight - mYAxisFontSize - 2) / (mYAxis.length));
    
                //测量Y轴文字的高度 用来画第一个数
                Paint.FontMetrics fm = axisPaint.getFontMetrics();
                int yItemHeight = (int) Math.ceil(fm.descent - fm.ascent);
    
                Log.e("wing", mHeight + "");
                for (int i = 0; i < mYAxis.length; i++) {
                    canvas.drawText(mYAxis[i], 0, mYAxisFontSize + i * yInterval, axisPaint);
                    yPoints[i] = (int) (mYAxisFontSize + i * yInterval);
    
    
                }
    
    我们运行一下,看到了如下效果:



    需要注意的是,这里的坐标需要微调,大家多试一下。同理开始画X轴:

          //画 X 轴
    
                //x轴的刻度集合
                int[] xPoints = new int[mXAxis.length];
    
                Log.e("wing", xPoints.length + "");
                //计算Y轴开始的原点坐标
                int xItemX = (int) axisPaint.measureText(mYAxis[1]);
    
                //X轴偏移量
                int xOffset = 50;
                //计算x轴 刻度间距
                int xInterval = (int) ((mWidth - xOffset) / (mXAxis.length));
                //获取X轴刻度Y坐标
                int xItemY = (int) (mYAxisFontSize + mYAxis.length * yInterval);
    
                for (int i = 0; i < mXAxis.length; i++) {
                    canvas.drawText(mXAxis[i], i * xInterval + xItemX + xOffset, xItemY, axisPaint);
                    xPoints[i] = (int) (i * xInterval + xItemX + axisPaint.measureText(mXAxis[i]) / 2 + xOffset + 10);
    //            Log.e("wing", xPoints[i] + "");
                }
    

    注意这里X轴的y坐标就是画Y轴时候的最下面的文字(最后一个)的坐标,存成了xItemY。



    之后我们来画点,这里我采用的方法是画圆。直接drawCircle。从map中取出所有点的对应i,j然后再从两个数组 xPoints[] yPoints[]取出真实的X,Y坐标,最后画出来

            //画点
                Paint pointPaint = new Paint();
    
                pointPaint.setColor(mLineColor);
    
                Paint linePaint = new Paint();
    
                linePaint.setColor(mLineColor);
                linePaint.setAntiAlias(true);
                //设置线条宽度
                linePaint.setStrokeWidth(mStrokeWidth);
                pointPaint.setStyle(Paint.Style.FILL);
    
    
                for (int i = 0; i < mXAxis.length; i++) {
                    if (mPointMap.get(i) == null) {
                        throw new IllegalArgumentException("PointMap has incomplete data!");
                    }
    
                    //画点
                    canvas.drawCircle(xPoints[i], yPoints[mPointMap.get(i)], mPointRadius, pointPaint);
                    if (i > 0) {//画线
                        canvas.drawLine(xPoints[i - 1], yPoints[mPointMap.get(i - 1)], xPoints[i], yPoints[mPointMap.get(i)], linePaint);
                    }
                }
    

    上面画完点之后开始画线drawLine,参数是前一个点的坐标,和后一个点的坐标。挨个画出来。



       这时候我们的最复杂的绘制就完成了。接下来来加入一点功能:参数的设置。

     /**
         * 设置map数据
         * @param data
         */
        public void setData(HashMap<Integer,Integer> data){
            mPointMap = data;
            invalidate();
        }
    
        /**
         * 设置Y轴文字
         * @param yItem
         */
        public void setYItem(String[] yItem){
            mYAxis = yItem;
        }
    
        /**
         * 设置X轴文字
         * @param xItem
         */
        public void setXItem(String[] xItem){
            mXAxis = xItem;
        }
    
        public void setLineColor(int color){
            mLineColor = color;
            invalidate();
        }

    以上代码很简单 我就不多说了。整个View完工,接下来介绍如何使用。


    使用:

    和普通的View一样,我们直接在XML布局文件中加入SimpleLineChart,注意不要忘记包名。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context="com.wingsofts.simplelinechart.MainActivity">
    
    <com.wingsofts.simplelinechart.SimpleLineChart
        android:id="@+id/simpleLineChart"
        android:layout_width="400dp"
        android:layout_height="200dp" />
    </RelativeLayout>
    
    然后在Activity中findviewbyid,给他设置X轴的文字 Y轴的文字 还有数据源

    public class MainActivity extends AppCompatActivity {
        private SimpleLineChart mSimpleLineChart;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mSimpleLineChart = (SimpleLineChart) findViewById(R.id.simpleLineChart);
            String[] xItem = {"1","2","3","4","5","6","7"};
            String[] yItem = {"10k","20k","30k","40k","50k"};
            if(mSimpleLineChart == null)
                Log.e("wing","null!!!!");
            mSimpleLineChart.setXItem(xItem);
            mSimpleLineChart.setYItem(yItem);
            HashMap<Integer,Integer> pointMap = new HashMap();
            for(int i = 0;i<xItem.length;i++){
                pointMap.put(i, (int) (Math.random()*5));
            }
            mSimpleLineChart.setData(pointMap);
        }
    }
    

    简单的几步,就可以得到预览图的效果了!是不是很好玩!觉得好的话评论一下,star一下。祭奠我死去的爱情。

    项目下载地址(求关注 求星星 ):点击打开链接


    下一篇来一个比较炫 比较复杂的view 自定义仪表盘 :时尚自定义仪表盘


  • 相关阅读:
    VS一个奇怪的发布问题
    VS删除空白行
    IDEA Rider 准备试用一段时间(1)
    IDEA Rider使用64位IISExpress(3)
    IDEA rider 管道模式 经典模式(2)
    VSCode批量替换使用注意问题
    IScroll Unable to preventDefault inside passive event listener due to target being treated as passive
    数据库索引也不是多多益善
    css设置手型光标
    javascript参数化拼接字符串两种方法
  • 原文地址:https://www.cnblogs.com/muyuge/p/6333560.html
Copyright © 2011-2022 走看看