Android自定义控件(实现状态提示图表)

前面分析那么多系统源码了,也该暂停下来休息一下,趁昨晚闲着看见一个有意思的需求就操练一下分析源码后的实例演练—-自定义控件。

这个实例很适合新手入门自定义控件。先看下效果图:

横屏模式如下:
竖屏模式如下:

看见没有,这个控件完全自定义的,连文字等都是自定义的,没有任何图片等资源,就仅仅是一个小的Java文件,这个界面只有一个控件。如下咱们看下实现代码。

实例代码

如下就是整个工程的源码了。

自定义上面展示的控件AreaChartsView源码:

/**
 * Author    : yanbo
 * Date     : 2015-06-03
 * Time     : 09:22
 * Description : 自定义区域描述图表View
 */
public class AreaChartsView extends View {
  private Paint mPaint;

  private int[] mZeroPos = new int[2];
  private int[] mMaxYPos = new int[2];
  private int[] mMaxXPos = new int[2];

  private int mWidth, mHight;
  private int mRealWidth, mRealHight;
  private String mTitleY, mTitleX;

  private ArrayList<Integer> mXLevel = new ArrayList<>();
  private ArrayList<Integer> mYLevel = new ArrayList<>();
  private ArrayList<String> mGridLevelText = new ArrayList<>();
  private ArrayList<Integer> mGridColorLevel = new ArrayList<>();
  private ArrayList<Integer> mGridTxtColorLevel = new ArrayList<>();

  private int mGridLevel = mXLevel.size() - 1;

  //title字符大小
  private int mXYTitleTextSize = 40;

  private int mMeasureXpos, mMeasureYpos;

  public AreaChartsView(Context context, AttributeSet attrs) {
    super(context, attrs);

    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setAntiAlias(true);
    mPaint.setFilterBitmap(true);
  }

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    mWidth = getWidth();
    mHight = getHeight();
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    initPosition();
    drawXYTitle(canvas);
    drawXYLine(canvas);
    drawContent(canvas);
  }

  private void initPosition() {
    //初始化坐标图的xy交点原点坐标
    mZeroPos[0] = mXYTitleTextSize * 2;
    mZeroPos[1] = mHight - mXYTitleTextSize * 4;
    //初始化坐标图的X轴最大值坐标
    mMaxXPos[0] = mWidth;
    mMaxXPos[1] = mHight - mXYTitleTextSize * 4;
    //初始化坐标图的Y轴最大值坐标
    mMaxYPos[0] = mXYTitleTextSize * 2;
    mMaxYPos[1] = mXYTitleTextSize * 2;
  }

  private void drawXYTitle(Canvas canvas) {
    mPaint.setColor(Color.parseColor("#1FB0E7"));
    mPaint.setTextSize(mXYTitleTextSize);
    mPaint.setTextAlign(Paint.Align.LEFT);
    //画Y轴顶的title
    canvas.drawText(mTitleY, mMaxYPos[0] - mXYTitleTextSize * 2, mMaxYPos[1] - mXYTitleTextSize, mPaint);
    mPaint.setTextAlign(Paint.Align.RIGHT);
    //画X轴顶的title
    canvas.drawText(mTitleX, mMaxXPos[0], mMaxXPos[1] + mXYTitleTextSize * 2, mPaint);
  }

  private void drawXYLine(Canvas canvas) {
    mPaint.setColor(Color.DKGRAY);
    mPaint.setTextAlign(Paint.Align.RIGHT);
    //画XY轴
    canvas.drawLine(mMaxYPos[0], mMaxYPos[1], mZeroPos[0], mZeroPos[1], mPaint);
    canvas.drawLine(mZeroPos[0], mZeroPos[1], mMaxXPos[0], mMaxXPos[1], mPaint);
  }

  private void drawContent(Canvas canvas) {
    mGridLevel = mXLevel.size() - 1;
    //计算出偏移title等显示尺标后的真实XY轴长度,便于接下来等分
    mRealWidth = (mWidth - mXYTitleTextSize * 2);
    mRealHight = (mHight - mXYTitleTextSize * 4);
    //算出等分间距
    int offsetX = mRealWidth/(mGridLevel);
    int offsetY = mRealHight/(mGridLevel+1);
    //循环绘制content
    for (int index=0; index<mGridLevel+1; index++) {
      mPaint.setColor(Color.DKGRAY);
      mPaint.setTextAlign(Paint.Align.RIGHT);
      mPaint.setTextSize(mXYTitleTextSize-5);
      //绘制X轴的那些坐标区间点,包含0点坐标
      canvas.drawText(String.valueOf(mXLevel.get(index)), mZeroPos[0]+(index*offsetX), mZeroPos[1] + mXYTitleTextSize, mPaint);

      if (index != 0) {
        //绘制Y轴坐标区间点,不包含0点坐标,X轴已经画过了
        canvas.drawText(String.valueOf(mYLevel.get(index)), mZeroPos[0], mZeroPos[1]-(index*offsetY), mPaint);
      }

      if (index == mGridLevel) {
        //坐标区间 = 真实区间 + 1
        break;
      }

      mPaint.setColor(mGridColorLevel.get(mGridLevel - 1 - index));
      mPaint.setStyle(Paint.Style.FILL);
      //绘制区间叠加图谱方块,从远到0坐标,因为小的图会覆盖大的图
      canvas.drawRect(mMaxYPos[0], mMaxYPos[1] + index*offsetY, mMaxXPos[0]-index*offsetX, mMaxXPos[1], mPaint);

      mPaint.setColor(mGridTxtColorLevel.get(index));
      mPaint.setTextAlign(Paint.Align.RIGHT);
      mPaint.setTextSize(mXYTitleTextSize);
      //绘制每个方块状态区间的提示文字
      canvas.drawText(mGridLevelText.get(index), mMaxXPos[0] - index * offsetX - mXYTitleTextSize,
          mMaxYPos[1] + index * offsetY + mXYTitleTextSize, mPaint);
    }
    //绘制当前坐标
    drawNotice(canvas, offsetX, offsetY);
  }

  private void drawNotice(Canvas canvas, int offsetX, int offsetY) {
    int realPosX = 0;
    int realPosY = 0;
    //计算传入的x值与真实屏幕坐标的像素值的百分比差值转换
    for (int index=0; index<mGridLevel; index++) {
      if (mMeasureXpos >= mXLevel.get(index) && mMeasureXpos < mXLevel.get(index+1)) {
        int subValue = mMeasureXpos - mXLevel.get(index);
        int offset = mXLevel.get(index+1) - mXLevel.get(index);
        realPosX = mZeroPos[0] + index*offsetX + (subValue / offset);
        break;
      }
    }
    //计算传入的y值与真实屏幕坐标的像素值的百分比差值转换
    for (int index=0; index<mGridLevel; index++) {
      if (mMeasureYpos >= mYLevel.get(index) && mMeasureYpos < mYLevel.get(index+1)) {
        int subValue = mMeasureYpos - mYLevel.get(index);
        int offset = mYLevel.get(index+1) - mYLevel.get(index);
        realPosY = mZeroPos[1] - index*offsetY - (offsetY - (subValue / offset));
        break;
      }
    }
    //画我们传入的坐标点的标记小红点
    mPaint.setColor(Color.RED);
    mPaint.setStyle(Paint.Style.FILL);
    canvas.drawCircle(realPosX, realPosY, 8, mPaint);

    int[] centerPos = {mZeroPos[0] + mRealWidth/2, mZeroPos[1] - mRealHight/2};

    mPaint.setColor(Color.WHITE);
    mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    RectF rectF = null;
    Path path = new Path();
    //画红点旁边的提示框和文字,有四个区域,然后提示框的小三角指标方位不同
    if (realPosX <= centerPos[0] && realPosY >= centerPos[1]) {
      //left-bottom
      //画三角形
      path.moveTo(realPosX+5, realPosY+5);
      path.lineTo(realPosX+15, realPosY+15);
      path.lineTo(realPosX+15, realPosY-15);
      //画矩形背景
      rectF = new RectF(realPosX+15, realPosY-40, realPosX+200, realPosY + 30);
      canvas.drawRoundRect(rectF, 15, 15, mPaint);
      //画提示框的文字
      mPaint.reset();
      mPaint.setColor(Color.RED);
      mPaint.setTextSize(mXYTitleTextSize - 5);
      canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX+30, realPosY, mPaint);
    }
    else if (realPosX <= centerPos[0] && realPosY < centerPos[1]) {
      //left-top
      path.moveTo(realPosX+5, realPosY+5);
      path.lineTo(realPosX+15, realPosY+15);
      path.lineTo(realPosX + 15, realPosY - 15);

      rectF = new RectF(realPosX+15, realPosY - 20, realPosX+200, realPosY + 50);
      canvas.drawRoundRect(rectF, 15, 15, mPaint);

      mPaint.reset();
      mPaint.setColor(Color.RED);
      mPaint.setTextSize(mXYTitleTextSize - 5);
      canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX+30, realPosY+20, mPaint);
    }
    else if (realPosX > centerPos[0] && realPosY >= centerPos[1]) {
      //right-bottom
      path.moveTo(realPosX-5, realPosY+5);
      path.lineTo(realPosX-15, realPosY+15);
      path.lineTo(realPosX - 15, realPosY - 15);

      rectF = new RectF(realPosX-200, realPosY-40, realPosX-15, realPosY + 30);
      canvas.drawRoundRect(rectF, 15, 15, mPaint);

      mPaint.reset();
      mPaint.setColor(Color.RED);
      mPaint.setTextSize(mXYTitleTextSize - 5);
      canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX-180, realPosY, mPaint);
    }
    else if (realPosX > centerPos[0] && realPosY < centerPos[1]) {
      //right-top
      path.moveTo(realPosX-5, realPosY+5);
      path.lineTo(realPosX-15, realPosY+15);
      path.lineTo(realPosX - 15, realPosY - 15);

      rectF = new RectF(realPosX-200, realPosY - 20, realPosX-15, realPosY + 50);
      canvas.drawRoundRect(rectF, 15, 15, mPaint);

      mPaint.reset();
      mPaint.setColor(Color.RED);
      mPaint.setTextSize(mXYTitleTextSize - 5);
      canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX-180, realPosY+30, mPaint);
    }

    path.close();
    mPaint.setColor(Color.WHITE);
    mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    canvas.drawPath(path, mPaint);
  }

  //设置当前比值
  public void updateValues(int x, int y) {
    mMeasureXpos = x;
    mMeasureYpos = y;

    postInvalidate();
  }

  //设置XY轴顶角的title字体大小
  public void setTitleTextSize(int size) {
    mXYTitleTextSize = size;
  }

  //初始化X轴的坐标区间点值,可以不均等分
  public void initXLevelOffset(ArrayList<Integer> list) {
    mXLevel.clear();
    mXLevel.addAll(list);
  }

  //初始化Y轴的坐标区间点值,可以不均等分
  public void initYLevelOffset(ArrayList<Integer> list) {
    mYLevel.clear();
    mYLevel.addAll(list);
  }

  //初始化每个区间的提示文字,如果不想显示可以设置""
  public void initGridLevelText(ArrayList<String> list) {
    mGridLevelText.clear();
    mGridLevelText.addAll(list);
  }

  //初始化每个区间的颜色
  public void initGridColorLevel(ArrayList<Integer> list) {
    mGridColorLevel.clear();
    mGridColorLevel.addAll(list);
  }

  //初始化每个区间的提示文字颜色
  public void initGridTxtColorLevel(ArrayList<Integer> list) {
    mGridTxtColorLevel.clear();
    mGridTxtColorLevel.addAll(list);
  }

  //初始化XY轴title
  public void initTitleXY(String x, String y) {
    mTitleX = x;
    mTitleY = y;
  }
}

再来看下布局文件:

<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">

  <com.yanbober.customerviewdemo.areachartsview.AreaChartsView
    android:id="@+id/area_charts_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"/>

</RelativeLayout>

再看看主界面:

public class MainActivity extends AppCompatActivity {
  private AreaChartsView mAreaChartsView;
  private Timer timer;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mAreaChartsView = (AreaChartsView) this.findViewById(R.id.area_charts_view);

    //初始化自定义图表的规格和属性
    ArrayList<Integer> mXLevel = new ArrayList<>();
    ArrayList<Integer> mYLevel = new ArrayList<>();
    ArrayList<String> mGridLevelText = new ArrayList<>();
    ArrayList<Integer> mGridColorLevel = new ArrayList<>();
    ArrayList<Integer> mGridTxtColorLevel = new ArrayList<>();
    //初始化x轴坐标区间
    mXLevel.add(0);
    mXLevel.add(60);
    mXLevel.add(90);
    mXLevel.add(100);
    mXLevel.add(110);
    mXLevel.add(120);
    //初始化y轴坐标区间
    mYLevel.add(0);
    mYLevel.add(90);
    mYLevel.add(140);
    mYLevel.add(160);
    mYLevel.add(180);
    mYLevel.add(200);
    //初始化区间颜色
    mGridColorLevel.add(Color.parseColor("#1FB0E7"));
    mGridColorLevel.add(Color.parseColor("#4FC7F4"));
    mGridColorLevel.add(Color.parseColor("#4FDDF2"));
    mGridColorLevel.add(Color.parseColor("#90E9F4"));
    mGridColorLevel.add(Color.parseColor("#B2F6F1"));
    //初始化区间文字提示颜色
    mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
    mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
    mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
    mGridTxtColorLevel.add(Color.WHITE);
    mGridTxtColorLevel.add(Color.BLACK);
    //初始化区间文字
    mGridLevelText.add("异常");
    mGridLevelText.add("过高");
    mGridLevelText.add("偏高");
    mGridLevelText.add("正常");
    mGridLevelText.add("偏低");

    mAreaChartsView.initGridColorLevel(mGridColorLevel);
    mAreaChartsView.initGridLevelText(mGridLevelText);
    mAreaChartsView.initGridTxtColorLevel(mGridTxtColorLevel);
    mAreaChartsView.initXLevelOffset(mXLevel);
    mAreaChartsView.initYLevelOffset(mYLevel);
    mAreaChartsView.initTitleXY("投入量(H)", "产出量(H)");
  }

  @Override
  protected void onStart() {
    super.onStart();
    timer = new Timer();
    timer.schedule(new TimerTask() {
      @Override
      public void run() {
        Random random = new Random();
        int x = random.nextInt(120) % (120 + 1) + 0;
        Random randomy = new Random();
        int y = randomy.nextInt(200) % (200 + 1) + 0;
        //随机模拟赋值
        mAreaChartsView.updateValues(x, y);
      }
    }, 0, 1000);
  }

  @Override
  protected void onPause() {
    super.onPause();
    timer.cancel();
  }
}

总结

上面代码很简单,核心的都已经注释了,不需要过多解释。核心思路就是一些坐标点的计算。该控件支持设置mergin及width与hight等属性,支持自定义所有颜色及显示及坐标区分等,唯一缺陷就是没来得及写attr属性xml设置这些值,有兴趣的自己实现吧,我是没时间了。

可以发现,自定义View无非就是重写前面文章分析的那三个方法而已。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • MPAndroidChart开源图表库的使用介绍之饼状图、折线图和柱状图

    MPAndroidChart开源图表库之饼状图 为大家介绍一款图标开源库MPAndroidChart,它不仅可以在Android设备上绘制各种统计图表,而且可以对图表进行拖动和缩放操作,用起来非常灵活.MPAndroidChart同样拥有常用的图表类型:线型图.饼图.柱状图和散点图. mpandroidchartlibrary.jar包下载地址: https://github.com/PhilJay/MPAndroidChart/releases 下面主要实现以下饼状图: 1.从上面的地址中下载

  • 详解Android图表 MPAndroidChart折线图

    1.介绍 MPAndroidChart GitHub地址 MPAndroidChart的强大之处就不在多说了,目前最新的版本是3.0.1,在新版本中很多方法都被弃用了,这个要注意一下,在网上查到的大多数资料都是关于旧版本的,今天来实现一下折线图,把过程记录下来,分享给大家. 效果图: 2.引入开源库 在项目根目录的build.gradle文件中加入如下代码 allprojects { repositories { maven { url "https://jitpack.io" } }

  • Android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)demo实例详解

    最近接了一个项目其中有功能要实现一个清理内存,要求和微信的效果一样.于是想到用surfaceView而不是继承view.下面小编给大家解析下实现思路. surfaceView是为了解决频繁绘制动画产生了闪烁,而采用了双缓冲机制,即A.B两个缓冲轮流显示在画布上,同时,使用不当,同样容易产生闪烁,这是由于A.B中有一个缓冲没有改变. 在我写这个view的时候就遇到了这个问题,研究了好久终于解决. 首先说一下思路: 微信清理缓存的动画是: 一个圆环不停的转动,同时中间有文字显示-->加载完成后,出现

  • 一个酷炫的Android图表制作框架

    一.概述 最近项目中需要制作柱形图以及折线图,所以便在网上搜索了一下这方面的开源框架,最后找到了这个酷炫的框架,不仅支持各种各样的图形制作,包括折线图.柱形图.饼状图等,而且提供了丰富的API接口,等着你去自定义,只要花点心思便能 DIY 出你心仪的图表类型,使用起来也是相当的简单. 从效果图可以看到,这个框架是相当酷炫的啊,在这里附上该框架的github地址hellocharts-android,有兴趣的不妨去 star 一下 二.炫酷的柱形图 可以看到柱形图也是能玩出花样来的,绚丽的色彩,自

  • Android中使用achartengine生成图表的具体方法

    今天在做项目的时候用到了图表功能,记录下来 achartengine是google的一个开源项目,可以在https://code.google.com/p/achartengine/ 下载技术文档,jar包以及项目源代码 demo下载:https://code.google.com/p/achartengine/downloads/list 一.饼状图 新建工程,添加achartengine  jar包 PieChart.java 复制代码 代码如下: package com.meritit.f

  • Android自定义控件(实现状态提示图表)

    前面分析那么多系统源码了,也该暂停下来休息一下,趁昨晚闲着看见一个有意思的需求就操练一下分析源码后的实例演练--自定义控件. 这个实例很适合新手入门自定义控件.先看下效果图: 横屏模式如下: 竖屏模式如下: 看见没有,这个控件完全自定义的,连文字等都是自定义的,没有任何图片等资源,就仅仅是一个小的Java文件,这个界面只有一个控件.如下咱们看下实现代码. 实例代码 如下就是整个工程的源码了. 自定义上面展示的控件AreaChartsView源码: /** * Author : yanbo * D

  • Android自定义控件实现滑动开关效果

    自定义开关控件 Android自定义控件一般有三种方式 1.继承Android固有的控件,在Android原生控件的基础上,进行添加功能和逻辑. 2.继承ViewGroup,这类自定义控件是可以往自己的布局里面添加其他的子控件的. 3.继承View,这类自定义控件没有跟原生的控件有太多的相似的地方,也不需要在自己的肚子里添加其他的子控件. ToggleView自定义开关控件表征上没有跟Android原生的控件有什么相似的地方,而且在滑动的效果上也没有沿袭Android原生的地方,所以我们的自定义

  • Android自定义控件样式实例详解

    本文实例讲述了Android自定义控件样式的方法.分享给大家供大家参考,具体如下: Android控件样式自定义是用定义在drawable文件夹下的XML文件实现,在布局文件中通过设置控件的background属性达到效果. 一.控件常见状态:在XML文件中用到了selector节点,selector可以理解为状态切换器,不同的状态下切换不同的样式,各种状态用Item节点表示,以下为一些常见的状态(注意:statelist中第一个匹配当前状态的item会被使用.因此,如果第一个item没有任何状

  • Android自定义控件下拉刷新实例代码

    实现效果: 图片素材: --> 首先, 写先下拉刷新时的刷新布局 pull_to_refresh.xml: <resources> <string name="app_name">PullToRefreshTest</string> <string name="pull_to_refresh">下拉可以刷新</string> <string name="release_to_refre

  • Android中仿IOS提示框的实现方法

    前言 在Android开发中,我们有时需要实现类似IOS的对话框.今天我就来总结下,如何通过自定义的开发来实现类似的功能. 自定义Dialog 我们知道Android中最常用的对话框就是Dialog及其派生类.这次我们通过组合的方式来实现一个类似IOS对话框的效果.我们先来看一下布局效果,这个相信大家都能弄出来,在这里我就贴一下最后的效果图(注意:对话框的边缘是圆角的). 效果图如下: 我们看到,这个和IOS的对话框已经非常相似了,后面我们需要做的就是将其作为一个组件封装起来,实现AlertDi

  • Android自定义控件EditText使用详解

    本文实例为大家分享了Android自定义控件EditText的具体代码,供大家参考,具体内容如下 自定义控件分三种: 1. 自绘控件 2. 组合控件 3. 继承控件 代码已上传到 github 以后的自定义控件就都放这个仓库 需求 这里由于项目的需要实现一个自定义EditText,主要实现的为两点,一个是工具图标toolIcon,例如点击清除EditText内容.一个为EditText左边的提示图标hintIcon, 例如输入账号密码时前面的图标. 为了让这个控件的拓展性更高,设置了两个点击事件

  • Android检查网络状态工具类详解

    在Android中开发具有网络交互的应用时候,有时候我们需要检查网络状态才能确定是否去请求网络,就需要用到公共类 代码: package com.example.ldp.com.util; /** * Created by Administrator on 2017/4/7. */ import android.content.Context; import android.app.Activity; import android.app.AlertDialog; import android.

  • Android自定义控件之翻转按钮的示例代码

    本文介绍了Android自定义控件之翻转按钮的示例代码,分享给大家,具体如下: 先看一下效果 一.先定义控件的基本结构 这里我们定义一个容器,所以是在ViewGroup的基础上扩展. 简单起见,直接使用扩展自ViewGroup的LinearLayout,并将我们的控件扩展自LinearLayout. 1.按钮的基本布局如下 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andr

  • Android自定义控件实现简单滑动开关效果

    本文实例为大家分享了Android自定义控件实现简单滑动开关的具体代码,供大家参考,具体内容如下 ToggleButton 滑动开关 项目概述 滑动开关是一个纯粹的自定义控件,上面的按钮会随着我们的左右滑动而滑动,并且在状态改变时通知用户,效果如下图1-9 所示,这也是应用中设置某些状态信息时最常见的控件,因此,我们有必要学习关于如何 自定义一个这样的滑动开关. 滑动开关UI 布局文件为activity_main.xml,代码如下:res/layout/activity_main.xml <Re

  • Redis实现信息已读未读状态提示

    本文为大家分享了Redis实现信息已读未读状态提示的关键代码,希望可以给大家一些启发,具体内容如下 前提: 假如现在有2个模块需要提示消息:只要存在用户在上个时间点之后没有看过的信息就提示用户有新的信息 思路如下: 使用hash存储用户上次看过的时间,使用sortedset存储每个模块的每个信息产生的时间 上代码: Map<String, String> dataMap = new HashMap<>(); Jedis jedis=null; String uid="1&

随机推荐