仿墨迹天气的折线图控件,效果杠杠滴

2024-06-03 12:32

本文主要是介绍仿墨迹天气的折线图控件,效果杠杠滴,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

2016-10-28 ComputerBlue 鸿洋 鸿洋
鸿洋

hongyangAndroid

你好,欢迎关注鸿洋的公众号,每天为您推送高质量文章,让你每天都能涨知识。点击历史消息,查看所有已推送的文章,喜欢可以置顶本公众号。此外,本公众号支持投稿,如果你有原创的文章,希望通过本公众号发布,欢迎投稿。


本文由 ComputerBlue投稿。

ComputerBlue的博客地址:

http://blog.csdn.net/acmnickzhang




1 概述   

这个控件难点在于绘图时候的一些坐标计算,大小计算。

自定义一个View来绘制折线图,外面套一层自定义的HorizontalScrollView来实现横向的滚动...


效果图:




2 代码讲解   

初始化部分代码,初始化一些参数,画笔对象,因为只是个demo所以把高度之类的参数都写死了,你们可以自己改改。


  1. public Today24HourView(Context context) {  
  2.         this(context, null);  
  3.     }  
  4.   
  5.     public Today24HourView(Context context, AttributeSet attrs) {  
  6.         this(context, attrs, 0);  
  7.     }  
  8.   
  9.     public Today24HourView(Context context, AttributeSet attrs, int defStyleAttr) {  
  10.         super(context, attrs, defStyleAttr);  
  11.         init();  
  12.     }  
  13.   
  14.     private void init() {  
  15.         mWidth = MARGIN_LEFT_ITEM + MARGIN_RIGHT_ITEM + ITEM_SIZE * ITEM_WIDTH;  
  16.         mHeight = 500//暂时先写死  
  17.         tempBaseTop = (500 - bottomTextHeight)/4;  
  18.         tempBaseBottom = (500 - bottomTextHeight)*2/3;  
  19.   
  20.         initHourItems();  
  21.         initPaint();  
  22.     }  
  23.   
  24.     private void initPaint() {  
  25.         pointPaint = new Paint();  
  26.         pointPaint.setColor(new Color().WHITE);  
  27.         pointPaint.setAntiAlias(true);  
  28.         pointPaint.setTextSize(8);  
  29.   
  30.         linePaint = new Paint();  
  31.         linePaint.setColor(new Color().WHITE);  
  32.         linePaint.setAntiAlias(true);  
  33.         linePaint.setStyle(Paint.Style.STROKE);  
  34.         linePaint.setStrokeWidth(5);  
  35.   
  36.         dashLinePaint = new Paint();  
  37.         dashLinePaint.setColor(new Color().WHITE);  
  38.         PathEffect effect = new DashPathEffect(new float[]{5555}, 1);  
  39.         dashLinePaint.setPathEffect(effect);  
  40.         dashLinePaint.setStrokeWidth(3);  
  41.         dashLinePaint.setAntiAlias(true);  
  42.         dashLinePaint.setStyle(Paint.Style.STROKE);  
  43.   
  44.         windyBoxPaint = new Paint();  
  45.         windyBoxPaint.setTextSize(1);  
  46.         windyBoxPaint.setColor(new Color().WHITE);  
  47.         windyBoxPaint.setAlpha(windyBoxAlpha);  
  48.         windyBoxPaint.setAntiAlias(true);  
  49.   
  50.         textPaint = new TextPaint();  
  51.         textPaint.setTextSize(DisplayUtil.sp2px(getContext(), 12));  
  52.         textPaint.setColor(new Color().WHITE);  
  53.         textPaint.setAntiAlias(true);  
  54.   
  55.         bitmapPaint = new Paint();  
  56.         bitmapPaint.setAntiAlias(true);  
  57.     }  
  58.   
  59.     //简单初始化下,后续改为由外部传入  
  60.     private void initHourItems(){  
  61.         listItems = new ArrayList<>();  
  62.         for(int i=0; i<ITEM_SIZE; i++){  
  63.             String time;  
  64.             if(i<10){  
  65.                 time = "0" + i + ":00";  
  66.             } else {  
  67.                 time = i + ":00";  
  68.             }  
  69.             int left =MARGIN_LEFT_ITEM  +  i * ITEM_WIDTH;  
  70.             int right = left + ITEM_WIDTH - 1;  
  71.             int top = (int)(mHeight -bottomTextHeight +  
  72.                     (maxWindy - WINDY[i])*1.0/(maxWindy - minWindy)*windyBoxSubHight  
  73.                     - windyBoxMaxHeight);  
  74.             int bottom =  mHeight - bottomTextHeight;  
  75.             Rect rect = new Rect(left, top, right, bottom);  
  76.             Point point = calculateTempPoint(left, right, TEMP[i]);  
  77.   
  78.             HourItem hourItem = new HourItem();  
  79.             hourItem.windyBoxRect = rect;  
  80.             hourItem.time = time;  
  81.             hourItem.windy = WINDY[i];  
  82.             hourItem.temperature = TEMP[i];  
  83.             hourItem.tempPoint = point;  
  84.             hourItem.res = WEATHER_RES[i];  
  85.             listItems.add(hourItem);  
  86.         }  
  87.     }  


绘制部分的代码:


里面的循环是为了画出24个时刻的温度,风力和天气的图片。


  1. @Override  
  2.     protected void onDraw(Canvas canvas) {  
  3.         super.onDraw(canvas);  
  4.         for(int i=0; i<listItems.size(); i++){  
  5.             Rect rect = listItems.get(i).windyBoxRect;  
  6.             Point point = listItems.get(i).tempPoint;  
  7.             //画风力的box和提示文字  
  8.             onDrawBox(canvas, rect, i);  
  9.             //画温度的点  
  10.             onDrawTemp(canvas, i);  
  11.             //画表示天气图片  
  12.             if(listItems.get(i).res != -1 && i != currentItemIndex){  
  13.                 Drawable drawable = ContextCompat.getDrawable(getContext(), listItems.get(i).res);  
  14.                 drawable.setBounds(point.x - DisplayUtil.dip2px(getContext(), 10),  
  15.                         point.y - DisplayUtil.dip2px(getContext(), 25),  
  16.                         point.x + DisplayUtil.dip2px(getContext(), 10),  
  17.                         point.y - DisplayUtil.dip2px(getContext(), 5));  
  18.                 drawable.draw(canvas);  
  19.             }  
  20.             onDrawLine(canvas, i);  
  21.             onDrawText(canvas, i);  
  22.         }  
  23.         //底部水平的白线  
  24.         linePaint.setColor(new Color().WHITE);  
  25.         canvas.drawLine(0, mHeight - bottomTextHeight, mWidth, mHeight - bottomTextHeight, linePaint);  
  26.    
  27.     }  


onDrawBox代码片段:


1.通过drawRoundRect画下面的矩形,如果是选中的那个时刻,那么将透明度设置成255

2.画文字为了让文字在box上面并居中对齐,需要将画笔改为居中模式,然后算出一块矩形,表示在该矩形水平居中。其次baseLine是为了高度的居中。

3.里面的getScrollBarX()方法是计算偏移量,因为文字会随着滑动而移动,移动的水平位置就是由它决定。


  1. //画底部风力的BOX  
  2.     private void onDrawBox(Canvas canvas, Rect rect, int i) {  
  3.         // 新建一个矩形  
  4.         RectF boxRect = new RectF(rect);  
  5.         HourItem item = listItems.get(i);  
  6.         if(i == currentItemIndex) {  
  7.             windyBoxPaint.setAlpha(255);  
  8.             canvas.drawRoundRect(boxRect, 44, windyBoxPaint);  
  9.             //画出box上面的风力提示文字  
  10.             Rect targetRect = new Rect(getScrollBarX(), rect.top - DisplayUtil.dip2px(getContext(), 20)  
  11.                     , getScrollBarX() + ITEM_WIDTH, rect.top - DisplayUtil.dip2px(getContext(), 0));  
  12.             Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();  
  13.             int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;  
  14.             textPaint.setTextAlign(Paint.Align.CENTER);  
  15.             canvas.drawText("风力" + item.windy + "级", targetRect.centerX(), baseline, textPaint);  
  16.         } else {  
  17.             windyBoxPaint.setAlpha(windyBoxAlpha);  
  18.             canvas.drawRoundRect(boxRect, 44, windyBoxPaint);  
  19.         }  
  20.     }  


onDrawTemp代码片段:


主要负责画出随着滑动而移动的温度提示的滚动条

这里和上面的绘制类似,但是多了运动轨迹的计算(因为温度的滚动条的移动多了竖直方向的,而风力文字提示的移动只有水平的)。


  1. private void onDrawTemp(Canvas canvas, int i) {  
  2.         HourItem item = listItems.get(i);  
  3.         Point point = item.tempPoint;  
  4.         canvas.drawCircle(point.x, point.y, 10, pointPaint);  
  5.   
  6.         if(currentItemIndex == i) {  
  7.             //计算提示文字的运动轨迹  
  8.             int Y = getTempBarY();  
  9.             //画出背景图片  
  10.             Drawable drawable = ContextCompat.getDrawable(getContext(), R.mipmap.hour_24_float);  
  11.             drawable.setBounds(getScrollBarX(),  
  12.                     Y - DisplayUtil.dip2px(getContext(), 24),  
  13.                     getScrollBarX() + ITEM_WIDTH,  
  14.                     Y - DisplayUtil.dip2px(getContext(), 4));  
  15.             drawable.draw(canvas);  
  16.             //画天气  
  17.             int res = findCurrentRes(i);  
  18.             if(res != -1) {  
  19.                 Drawable drawTemp = ContextCompat.getDrawable(getContext(), res);  
  20.                 drawTemp.setBounds(getScrollBarX()+ITEM_WIDTH/2 + (ITEM_WIDTH/2 - DisplayUtil.dip2px(getContext(), 18))/2,  
  21.                         Y - DisplayUtil.dip2px(getContext(), 23),  
  22.                         getScrollBarX()+ITEM_WIDTH - (ITEM_WIDTH/2 - DisplayUtil.dip2px(getContext(), 18))/2,  
  23.                         Y - DisplayUtil.dip2px(getContext(), 5));  
  24.                 drawTemp.draw(canvas);  
  25.   
  26.             }  
  27.             //画出温度提示  
  28.             int offset = ITEM_WIDTH/2;  
  29.             if(res == -1)  
  30.                 offset = ITEM_WIDTH;  
  31.             Rect targetRect = new Rect(getScrollBarX(), Y - DisplayUtil.dip2px(getContext(), 24)  
  32.                     , getScrollBarX() + offset, Y - DisplayUtil.dip2px(getContext(), 4));  
  33.             Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();  
  34.             int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;  
  35.             textPaint.setTextAlign(Paint.Align.CENTER);  
  36.             canvas.drawText(item.temperature + "°", targetRect.centerX(), baseline, textPaint);  
  37.         }  
  38.     }  


onDrawLine代码片段:


折线如果是直线那么显得很生硬,为了平滑一些,做了贝塞尔曲线,根据奇偶性做方向不同的贝塞尔曲线。


  1. //温度的折线,为了折线比较平滑,做了贝塞尔曲线  
  2.     private void onDrawLine(Canvas canvas, int i) {  
  3.         linePaint.setColor(new Color().YELLOW);  
  4.         linePaint.setStrokeWidth(3);  
  5.         Point point = listItems.get(i).tempPoint;  
  6.         if(i != 0){  
  7.             Point pointPre = listItems.get(i-1).tempPoint;  
  8.             Path path = new Path();  
  9.             path.moveTo(pointPre.x, pointPre.y);  
  10.             if(i % 2 == 0)  
  11.                 path.cubicTo(pointPre.x, pointPre.y, (pointPre.x+point.x)/2, (pointPre.y+point.y)/2+14, point.x, point.y);  
  12.             else  
  13.                 path.cubicTo(pointPre.x, pointPre.y, (pointPre.x+point.x)/2, (pointPre.y+point.y)/2-14, point.x, point.y);  
  14.             canvas.drawPath(path, linePaint);  
  15.         }  
  16.     }  


onDrawText代码片段:


  1. //绘制底部时间  
  2.     private void onDrawText(Canvas canvas, int i) {  
  3.         //此处的计算是为了文字能够居中  
  4.         Rect rect = listItems.get(i).windyBoxRect;  
  5.         Rect targetRect = new Rect(rect.left, rect.bottom, rect.right, rect.bottom + bottomTextHeight);  
  6.         Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();  
  7.         int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;  
  8.         textPaint.setTextAlign(Paint.Align.CENTER);  
  9.   
  10.         String text = listItems.get(i).time;  
  11.         canvas.drawText(text, targetRect.centerX(), baseline, textPaint);  
  12.     }  


计算部分的代码:


该方法由外部的HorizontalScrollView调用。两个参数分别是

int offset = computeHorizontalScrollOffset();
int maxOffset = computeHorizontalScrollRange() - DisplayUtil.getScreenWidth(getContext());


这里有一问:为什么需要减去屏幕的宽度?


答:    比如HorizontalScrollView的滚动条移动范围在0-----1000像素之间的话,computeHorizontalScrollRange()计算出的值就会是1000+屏幕宽度


  1. //设置scrollerView的滚动条的位置,通过位置计算当前的时段  
  2.     public void setScrollOffset(int offset, int maxScrollOffset){  
  3.         this.maxScrollOffset = maxScrollOffset;  
  4.         scrollOffset = offset;  
  5.         int index = calculateItemIndex(offset);  
  6.         currentItemIndex = index;  
  7.         invalidate();  
  8.     }  


然后需要计算滑动到某位置时,当前的时刻是几。

先说说getScrollBarX()方法|:(结合下面的图片看)

已知条件是HorizontalScrollView的滚动条位置和滚动条最大滚动距离,我们需要计算的是温度提示滚动条(矩形)的left的横坐标。

所以得到温度滚动条的最大移动距离,就能计算出当前温度滚动条的位置left。

最后x = 当前的left+左侧的margin。

计算当前的时刻采取不断累加ITEM_WIDTH,一旦sum大于x,则i就是当前的item的下标。


  1. //通过滚动条偏移量计算当前选择的时刻  
  2.     private int calculateItemIndex(int offset){  
  3. //        Log.d(TAG, "maxScrollOffset = " + maxScrollOffset + "  scrollOffset = " + scrollOffset);  
  4.         int x = getScrollBarX();  
  5.         int sum = MARGIN_LEFT_ITEM  - ITEM_WIDTH/2;  
  6.         for(int i=0; i<ITEM_SIZE; i++){  
  7.             sum += ITEM_WIDTH;  
  8.             if(x < sum)  
  9.                 return i;  
  10.         }  
  11.         return ITEM_SIZE - 1;  
  12.     }  


  1. private int getScrollBarX(){  
  2.         int x = (ITEM_SIZE - 1) * ITEM_WIDTH * scrollOffset / maxScrollOffset;  
  3.         x = x + MARGIN_LEFT_ITEM;  
  4.         return x;  
  5.     }  




计算运动轨迹代码(实质是计算Y轴的变化):


通过x的变化得到Y的变化。

先要计算当前的x处于哪两个时刻之间,因为y的变化范围必须在这两个时刻的温度的点的Y之间。

得到这两个点之后通过等比关系获得Y

看下图 ,红色字是已知的。


  1. //计算温度提示文字的运动轨迹  
  2.     private int getTempBarY(){  
  3.         int x = getScrollBarX();  
  4.         int sum = MARGIN_LEFT_ITEM ;  
  5.         Point startPoint = null, endPoint;  
  6.         int i;  
  7.         for(i=0; i<ITEM_SIZE; i++){  
  8.             sum += ITEM_WIDTH;  
  9.             if(x < sum) {  
  10.                 startPoint = listItems.get(i).tempPoint;  
  11.                 break;  
  12.             }  
  13.         }  
  14.         if(i+1 >= ITEM_SIZE || startPoint == null)  
  15.             return listItems.get(ITEM_SIZE-1).tempPoint.y;  
  16.         endPoint = listItems.get(i+1).tempPoint;  
  17.   
  18.         Rect rect = listItems.get(i).windyBoxRect;  
  19.         int y = (int)(startPoint.y + (x - rect.left)*1.0/ITEM_WIDTH * (endPoint.y - startPoint.y));  
  20.         return y;  
  21.     }  




项目源码地址:

  • https://github.com/zx391324751/MoJiDemo 



阅读原文

这篇关于仿墨迹天气的折线图控件,效果杠杠滴的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1026992

相关文章

C#实现WinForm控件焦点的获取与失去

《C#实现WinForm控件焦点的获取与失去》在一个数据输入表单中,当用户从一个文本框切换到另一个文本框时,需要准确地判断焦点的转移,以便进行数据验证、提示信息显示等操作,本文将探讨Winform控件... 目录前言获取焦点改变TabIndex属性值调用Focus方法失去焦点总结最后前言在一个数据输入表单

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用

使用Python实现生命之轮Wheel of life效果

《使用Python实现生命之轮Wheeloflife效果》生命之轮Wheeloflife这一概念最初由SuccessMotivation®Institute,Inc.的创始人PaulJ.Meyer... 最近看一个生命之轮的视频,让我们珍惜时间,因为一生是有限的。使用python创建生命倒计时图表,珍惜时间

防近视护眼台灯什么牌子好?五款防近视效果好的护眼台灯推荐

在家里,灯具是属于离不开的家具,每个大大小小的地方都需要的照亮,所以一盏好灯是必不可少的,每个发挥着作用。而护眼台灯就起了一个保护眼睛,预防近视的作用。可以保护我们在学习,阅读的时候提供一个合适的光线环境,保护我们的眼睛。防近视护眼台灯什么牌子好?那我们怎么选择一个优秀的护眼台灯也是很重要,才能起到最大的护眼效果。下面五款防近视效果好的护眼台灯推荐: 一:六个推荐防近视效果好的护眼台灯的

lvgl8.3.6 控件垂直布局 label控件在image控件的下方显示

在使用 LVGL 8.3.6 创建一个垂直布局,其中 label 控件位于 image 控件下方,你可以使用 lv_obj_set_flex_flow 来设置布局为垂直,并确保 label 控件在 image 控件后添加。这里是如何步骤性地实现它的一个基本示例: 创建父容器:首先创建一个容器对象,该对象将作为布局的基础。设置容器为垂直布局:使用 lv_obj_set_flex_flow 设置容器

小程序button控件上下边框的显示和隐藏

问题 想使用button自带的loading图标功能,但又不需要button显示边框线 button控件有一条淡灰色的边框,在控件上了样式 border:none; 无法让button边框隐藏 代码如下: <button class="btn">.btn{border:none; /*一般使用这个就是可以去掉边框了*/} 解决方案 发现button控件有一个伪元素(::after

MFC中Spin Control控件使用,同时数据在Edit Control中显示

实现mfc spin control 上下滚动,只需捕捉spin control 的 UDN_DELTAPOD 消息,如下:  OnDeltaposSpin1(NMHDR *pNMHDR, LRESULT *pResult) {  LPNMUPDOWN pNMUpDown = reinterpret_cast(pNMHDR);  // TODO: 在此添加控件通知处理程序代码    if

MFC 控件重绘(2) NM_CUSTOMDRAW, WM_DRAWITEM, 虚函数DrawItem

控件重绘有三种方法: 1 设定界面属性 2 利用Windows的消息机制,通过Windows消息映射(Message Mapping)和反映射(Message Reflecting),在合适的时机修改控件的状态和行为。此方式涉及NM_CUSTOMDRAW和WM_DRAWITEM 3 利用虚函数机制,重载虚函数。即DrawItem虚函数。 对于NM_CUSTOMDRAW,某些支持此消息的控件