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无非就是重写前面文章分析的那三个方法而已。

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

时间: 2017-11-29

Android自定义控件(实现状态提示图表)的相关文章

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

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

通知栏状态-请问,谁知道怎么获得android状态栏的状态

问题描述 请问,谁知道怎么获得android状态栏的状态 现在开发中有一个需求就是做和ios的Assistive Touch 一样功能的app,现在通过反射可以让状态栏拉下来和滚上去了.但是有一个问题就是,怎么获得这个状态栏的状态呢?求大神们支招 解决方案 Activity或者Service都能初始化一个状态栏通知.可因为Activity只有在活动状态并获得焦点时才能执行操作,所以还是建议用Service来创建状态栏通知.这样,即使用户正在使用其他程序或者设备已经休眠时,仍然可以从后台创建通知.

Android自定义控件之开关按钮学习笔记分享_Android

今天来讲讲自定义单个控件,就拿开关按钮来讲讲,相信大家见了非常多这样的了,先看看效果: 我们可以看到一个很常见的开关按钮,那就来分析分析. 首先: 这是由两张图片构成: ①一张为有开和关的背景图片 ②一张为控制开和关的滑动按钮 第一步: 写个类继承View,并重写几个方法: 第一个为构造函数,重写一个参数的函数和两个参数的函数就够了,因为两个参数的函数能够使用自定义属性 第二个为控制控件的大小–>protected void onMeasure(int widthMeasureSpec, int

android按钮-关于设置Android Button按钮状态(normal,focused,pressed)图片,有没有简便的方法

问题描述 关于设置Android Button按钮状态(normal,focused,pressed)图片,有没有简便的方法 Android Button按钮状态(normal,focused,pressed)的图片 大都用drawable "selector" 来实现 类似 btn_background.xml <?xml version="1.0" encoding="UTF-8"?> <selector xmlns:and

Android自定义控件——开源组件SlidingMenu的项目集成

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39611355        在实际项目开发中,定制一个菜单,能让用户得到更好的用户体验,诚然菜单的样式各种各样,但是有一种菜单--滑动菜单,是被众多应用广泛使用的.关于这种滑动菜单的实现,我在前面的博文中也介绍了如何自定义去实现,请参考Android自定义控件--侧滑菜单,这篇博文描述的是如何从无到有创建一个侧滑菜单的控件,里面的代码不多,但是处理的逻辑和各种效果比较复杂,如果稍

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

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

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

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

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

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

Android 判断网络状态

 Android 判断网络状态这一应用技巧在实际应中是比较重要的.那么,在Android操作系统中,如何能够正确的判断我们所连接的网络是否断开恩?今天我们就针对这一应用技巧进行一个详细的分析. public class ConnectionChangeReceiver extends  BroadcastReceiver  {  @Override  public void onReceive( Context context, Intent intent )  {  ConnectivityM