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

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

相关文章

以canvas方式绘制粒子背景效果,感觉还可以

这个是看到项目中别人写好的,感觉这种写法效果还可以,就存留记录下 就是这种的背景效果。如果想改背景颜色可以通过canvas.js文件中的fillStyle值改。 附上demo下载地址。 https://download.csdn.net/download/u012138137/11249872

echarts省份标注加散点效果

这个是安徽的效果图,鼠标移到红色标注或者对应的市区位置都会显示对应的数值。 先直接上代码: import anhuiMapJson from './anhui.json'getCoords: function(city) {var res = [];if (city != null) {for (var c in this.cityMap.features) {if (this.cityMa

AI赋能天气:微软研究院发布首个大规模大气基础模型Aurora

编者按:气候变化日益加剧,高温、洪水、干旱,频率和强度不断增加的全球极端天气给整个人类社会都带来了难以估计的影响。这给现有的天气预测模型提出了更高的要求——这些模型要更准确地预测极端天气变化,为政府、企业和公众提供更可靠的信息,以便做出及时的准备和响应。为了应对这一挑战,微软研究院开发了首个大规模大气基础模型 Aurora,其超高的预测准确率、效率及计算速度,实现了目前最先进天气预测系统性能的显著

XMG 抽屉效果

1.比如说我创建了3个View -(void)viewDidLoad{  [ super viewDidLoad]; [self setUpChild] ;         UIPanGestureRecognizer *pan=[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];

XMG xib中不属于一个类的控件,拖线到指定的类中

1.比如我现在有一个view绑定为GreenView,我们按住control向类里面拖线的方式想要达到目的,显然拖不进去。例图如下 那么我们此时还想要达到目的,就需要自己去GreenView的类内部去写IBo 然后这面连接起来 2.第二,大哥郝良建给做的扩展 可以在.h或者.m中写一个NSObject的属性 然后在xib中对应的位置创建一个NSObject的属性

Avalonia 常用控件二 Menu相关

1、Menu 添加代码如下 <Button HorizontalAlignment="Center" Content="Menu/菜单"><Button.Flyout><MenuFlyout><MenuItem Header="打开"/><MenuItem Header="-"/><MenuItem Header="关闭"/></MenuFlyout></Button.Flyout></B

3_创建Tab控件

1,新建MFC 对话框项目,为对话框添加Tab控件,选中Tab控件,新建控件变量m_tab_ctrl 2,为Tab控件添加tab项 m_tab_ctrl.InsertItem(0, L”000”),参数1,哪个位置;参数2,item的名称 3,为Tab控件添加监听事件, void C测试Dlg::OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESUL

示例:推荐一个基于第三方开源控件库DataGridFilter封装的FilterColumnDataGrid,可以像Excel拥有列头筛选器

一、目的:基于第三方开源控件库DataGridFilter封装的FilterColumnDataGrid,可以像Excel拥有列头筛选器,感兴趣的可以去下方链接地址查看开源控件库地址。本控件封装的目的在于将第三方库的皮肤和样式封装到皮肤库中可统一设置样式,同时生成nuget方便调用 二、效果如下 三、环境 VS2022 Net7 四、使用方式 1、安装nuget包:H.Con

TextGroupView (TextView组合控件)

TextGroupView ImageView + TextView + TextView +TextView+ EditText +ImageView + ImageView 实现的组合控件 JitPack依赖 A.项目/build.grade allprojects {repositories {...maven { url 'https://jitpack.io' }}} B.

Android 扇形网络控件 - 无网络视图(动画)

前言 一般在APP没有网络的情况下,我们都会用一个无网络的提示图标,在提示方面为了统一app的情况,我们一般使用简单的提示图标,偶尔只需要改变一下图标的颜色就一举两得,而不需要让PS来换一次颜色。当然app有图标特殊要求的就另当别论了。 效果图 当你第一眼看到这样的图,二话不说直接让UI给你切一张图标来的快对吧,我其实开始也是这么想的,但是到了做的app越来越多的时候,你就会发现就算是用