首页 > 学院 > 开发设计 > 正文

仿小米天气预报未来几天趋势预报折线图

2019-11-09 18:35:56
字体:
来源:转载
供稿:网友

废话不多说,直接贴实现效果图和代码。。。。。。

效果图如下: 这里写图片描述 这里写图片描述

首先说一下我的实现思路,外部一个HorizontalScrollView,为scrollView添加一个LinearLayout子控件容器,遍历循环往LinearLayout中添加每天天气的layout,并为view设置每一天的数据。数据来源于https://www.nowapi.com/api/weather.future

下边贴代码: 首先是每天天气的layout布局

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal"> <TextView android:id="@+id/tv_horizontal_item_date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="星期一/n2016-01-11" android:gravity="center_horizontal"/> <ImageView android:id="@+id/iv_horizontal_item_day_pic" android:layout_width="30dp" android:layout_height="30dp" android:src="@mipmap/ic_launcher" /> <TextView android:id="@+id/tv_horizontal_item_day_weather" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="晴" android:layout_marginBottom="6dp"/> <com.example.cyy.weather.widget.FutureWeatherTrendView android:id="@+id/view_horizontal_item_trend" android:layout_width="120dp" android:layout_height="120dp" /> <ImageView android:id="@+id/iv_horizontal_item_night_pic" android:layout_width="30dp" android:layout_height="30dp" android:src="@mipmap/ic_launcher" android:layout_marginTop="6dp"/> <TextView android:id="@+id/tv_horizontal_item_night_weather" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="晴"/> <TextView android:id="@+id/tv_horizontal_item_wind" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="西南风/n3级" android:layout_marginTop="9dp" android:gravity="center_horizontal"/></LinearLayout>

预览图是这样的 这里写图片描述 中间那块儿是一个自定义的view,下边看看这个自定义view是如何实现的,为了让这个自定义view适合任何数据。大致思路是: 1.算出每一度所占的高度,就是这几天中有一个最高的温度和一个最低的温度,让这两个温度相减得到最大温度差,然后用这个view的高度(除去上下文本的高度)除以这个温度差即得到这个值; 2.为了与前一天后一天的温度相连起来,有必要知道与前一天温度的中间值和与后一天温度的中间值,这个值根据相似三角形原理算出分别是取当天与前一天温度中间值以及当天与后一天温度中间值 如图: 这里写图片描述 如果是第一天如下图(不必知道与上一天的中间值) 这里写图片描述

如果是最后一天如下图(不必知道与下一天的中间值) 这里写图片描述

3.画点,写文本,连线,根据与最低温度的差值算出来温度点和两边的Y坐标,根据坐标值画点,写文本,最后连线

package com.example.cyy.weather.widget;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;import com.example.cyy.weather.utils.Tools;/** * Created by cyy on 2017/1/11. * * //未来天气趋势的一个自定义view */public class FutureWeatherTrendView extends View { //为当前view指定一个默认最小宽度和默认最小高度 PRivate int mDefLeastWidth = 80, mDefLeastHeight = 150; //上下文对象 private Context mContext; //分别代表画点的画笔,画文本的画笔和画线的画笔 private Paint mCirclePaint, mTextPaint, mLinePaint; //字体大小 private int mPaintTextSize = 16; //画的点的半径 private int mCircleRadius = 3; //包含有字体度量信息的对象 private Paint.FontMetrics mFontMetrics; //低温的左,中,右和高温的左,中,右,中间的温度为当天的温度,左边的温度是当天和上一天的中间值, // 右边的温度是当天和下一天的中间值 private float mLowTemArray[], mHighTemArray[]; //未来几天中温度的最低值和最高值 private static float mLowestTem, mHighestTem; //未来几天中最低温度点的Y坐标 private float mLowestTemY; //文本到点之间的间距 private int mTextCircleDis = 3; //标记第一天和最后一天,如果type=0代表第一天,type=1代表最后一天,type=2代表第一天和最后一天之间的天 private int mType; public FutureWeatherTrendView(Context context) { super(context); } public FutureWeatherTrendView(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; initPaint(); } /** * 初始化各种画笔 */ private void initPaint(){ mCirclePaint = new Paint(); mCirclePaint.setColor(Color.YELLOW); mTextPaint = new Paint(); mTextPaint.setTextSize(Tools.sp2px(mContext, mPaintTextSize)); mTextPaint.setColor(Color.BLACK); mFontMetrics = mTextPaint.getFontMetrics(); mLinePaint = new Paint(); mLinePaint.setStrokeWidth(3); mLinePaint.setColor(Color.BLUE); //抗锯齿 mLinePaint.setAntiAlias(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //view的宽度和高度 int width, height; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); width = measureWidthHeight(widthMode, widthSize, 0); height = measureWidthHeight(heightMode, heightSize, 0); setMeasuredDimension(width, height); } /** * 测量view的宽度和高度 * @param mode * @param size * @param tag 0表示宽度,1表示高度 * @return */ private int measureWidthHeight(int mode, int size, int tag) { int resultSize = 0; if(mode == MeasureSpec.EXACTLY){ resultSize = size; }else{ if(tag == 0){ //view的宽度不能小于最小宽度 resultSize = Tools.dip2px(mContext, mDefLeastWidth) + getPaddingLeft() + getPaddingRight(); }else{ //view的高度不能小于最小高度 resultSize = Tools.dip2px(mContext, mDefLeastHeight) + getPaddingTop() + getPaddingBottom(); } if(mode == MeasureSpec.AT_MOST){ resultSize = Math.min(size, resultSize); } } return resultSize; } @Override protected void onDraw(Canvas canvas) { //低温Y坐标 float lowTemTextY = getTemY(mLowTemArray[1]) + getExtraHeight(); drawLowOrHigh(canvas, mLowTemArray, lowTemTextY, mType); drawLowOrHigh(canvas, mHighTemArray, getTextHeight(), mType); } /** * 画低温和高温的点,温度文本值以及趋势 * @param canvas 画布 * @param temArray 低温或者高温左中右温度集合 * @param textY 低温或者高温文本的Y坐标 */ private void drawLowOrHigh(Canvas canvas, float temArray[], float textY, int type) { //第一步,画低温点,即中间的那个温度点(需要找到圆圈中心的坐标) //X坐标 中心 int temX = getWidth() / 2; //Y坐标 中心 float temY = getTemY(temArray[1]); //画点 canvas.drawCircle(temX, temY, Tools.dip2px(mContext, mCircleRadius), mCirclePaint); //第二步,写低温下边的温度值(从文字左下角开始画,需要找到左下角的坐标) //X坐标 float temTextX = getWidth() / 2 - mTextPaint.measureText(temArray[1] + "°") / 2; //Y坐标 float temTextY = textY; //写文本 canvas.drawText(temArray[1] + "°", temTextX, temTextY, mTextPaint); //第三步,得到低温左边和右边点的坐标 int temLeftX = 0; float temLeftY = getTemY(temArray[0]); int temRightX = getWidth(); float temRightY = getTemY(temArray[2]); //第四步,中间的温度点和左边以及右边的连线 if(type == 0){ canvas.drawLine(temX + Tools.dip2px(mContext, mCircleRadius), temY, temRightX, temRightY, mLinePaint); } else if(type == 1){ canvas.drawLine(temLeftX, temLeftY, temX - Tools.dip2px(mContext, mCircleRadius), temY, mLinePaint); } else{ canvas.drawLine(temLeftX, temLeftY, temX - Tools.dip2px(mContext, mCircleRadius), temY, mLinePaint); canvas.drawLine(temX + Tools.dip2px(mContext, mCircleRadius), temY, temRightX, temRightY, mLinePaint); } } /** * 获得文本所占的高度 * @return */ private float getTextHeight(){ //文本的高度 float textHeight = mFontMetrics.bottom - mFontMetrics.top; return textHeight; } /** * 文本高度加上文本到温度点的间距 */ private float getExtraHeight(){ return getTextHeight() + Tools.dip2px(mContext, mTextCircleDis); } /** * 获得未来几天中最低温度所对应的点中心的Y坐标值 */ private float getLowestTemY(){ //最低点的Y坐标值=整个view的高度减去底下的文本的高度以及文本与温度点之间的间距 mLowestTemY = getHeight() - getExtraHeight(); return mLowestTemY; } /** * 平均一温度所占据的高度值 * @return */ private float getPerTemHeight(){ //最高温度和最低温度的高度差值 float lowestHighestHeightDis = getHeight() - 2 * getExtraHeight(); //最高温度和最低温度的差值 float lowestHighestTemDis = mHighestTem - mLowestTem; return lowestHighestHeightDis / lowestHighestTemDis; } /** * 获得tem温度所在点的纵坐标 * @param tem */ private float getTemY(float tem){ float temY = getLowestTemY() - (tem - mLowestTem) * getPerTemHeight(); return temY; } /** * 设置低温和低温左右的温度值 * @param lowTemArray */ public FutureWeatherTrendView setLowTemArray(float lowTemArray[]){ this.mLowTemArray = lowTemArray; return this; } /** * 设置高温和高温左右的温度值 * @param highTemArray */ public FutureWeatherTrendView setHighTemArray(float highTemArray[]){ this.mHighTemArray = highTemArray; return this; } public FutureWeatherTrendView setType(int type){ this.mType = type; return this; } /** * 设置未来几天中最低的温度值 * @param lowestTem */ public static void setLowestTem(int lowestTem){ mLowestTem = lowestTem; } /** * 设置未来几天中最高的温度值 * @param highestTem */ public static void setHighestTem(int highestTem){ mHighestTem = highestTem; }}

然后是给view设置数据,向容器中添加view的代码,如下:

mTvPeriod.setText(weatherList.get(0).days + "---" + weatherList.get(weatherList.size() - 1).days); View itemView = null; FutureWeatherObj lastWeatherObj = null; FutureWeatherObj nextWeatherObj = null; int lowestTem = 0, highestTem = 0; LinearLayout ll = new LinearLayout(this); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); ll.setLayoutParams(params); ll.setOrientation(LinearLayout.HORIZONTAL); ll.removeAllViews(); ArrayList<Integer> lowTemList = new ArrayList<Integer>();//存放低温温度值的集合 ArrayList<Integer> highTemList = new ArrayList<Integer>();//存放高温温度值的集合 for (int i = 0; i < weatherList.size(); i++){ FutureWeatherObj weatherObj = weatherList.get(i); lowTemList.add(Integer.parseInt(weatherObj.temp_low)); highTemList.add(Integer.parseInt(weatherObj.temp_high)); Collections.sort(lowTemList);//按照升序排列所有低温温度值 Collections.sort(highTemList);//按照升序排列所有高温温度值 } FutureWeatherTrendView.setLowestTem(lowTemList.get(0)); FutureWeatherTrendView.setHighestTem(highTemList.get(weatherList.size() - 1)); for(int i = 0; i < weatherList.size(); i++){ float lowTemArray[] = new float[3]; float highTemArray[] = new float[3]; float lastNextTem[] = new float[4]; FutureWeatherObj weatherObj = weatherList.get(i); if(i == 0){ //没有前一天只有后一天 lastWeatherObj = null; nextWeatherObj = weatherList.get(i + 1); } else if(i > 0 && i < weatherList.size() - 1){ //有前一天和后一天 lastWeatherObj = weatherList.get(i - 1); nextWeatherObj = weatherList.get(i + 1); } else if(i == weatherList.size() - 1){ //只有前一天没有后一天 lastWeatherObj = weatherList.get(i - 1); nextWeatherObj = null; } if(lastWeatherObj != null) { lastNextTem[0] = Integer.parseInt(lastWeatherObj.temp_low); lastNextTem[1] = Integer.parseInt(lastWeatherObj.temp_high); } if(weatherObj != null){ lowTemArray[1] = Integer.parseInt(weatherObj.temp_low); highTemArray[1] = Integer.parseInt(weatherObj.temp_high); } if(nextWeatherObj != null) { lastNextTem[2] = Integer.parseInt(nextWeatherObj.temp_low); lastNextTem[3] = Integer.parseInt(nextWeatherObj.temp_high); } if(i == 0){ lowTemArray[2] = (lowTemArray[1] + lastNextTem[2]) / 2; highTemArray[2] = (highTemArray[1] + lastNextTem[3]) / 2; }else if(i > 0 && i < weatherList.size() - 1){ lowTemArray[0] = (lastNextTem[0] + lowTemArray[1]) / 2; highTemArray[0] = (lastNextTem[1] + highTemArray[1]) / 2; lowTemArray[2] = (lowTemArray[1] + lastNextTem[2]) / 2; highTemArray[2] = (highTemArray[1] + lastNextTem[3]) / 2; }else if(i == weatherList.size() - 1){ lowTemArray[0] = (lastNextTem[0] + lowTemArray[1]) / 2; highTemArray[0] = (lastNextTem[1] + highTemArray[1]) / 2; } itemView = LayoutInflater.from(this).inflate(R.layout.activity_future_weather_horizontal_item, null); TextView mTvDate = (TextView) itemView.findViewById(R.id.tv_horizontal_item_date); ImageView mIvDayPic = (ImageView) itemView.findViewById(R.id.iv_horizontal_item_day_pic); TextView mTvDayWeather = (TextView) itemView.findViewById(R.id.tv_horizontal_item_day_weather); FutureWeatherTrendView mViewTrend = (FutureWeatherTrendView) itemView.findViewById(R.id.view_horizontal_item_trend); ImageView mIvNightPic = (ImageView) itemView.findViewById(R.id.iv_horizontal_item_night_pic); TextView mTvNightWeather = (TextView) itemView.findViewById(R.id.tv_horizontal_item_night_weather); TextView mTvWind = (TextView) itemView.findViewById(R.id.tv_horizontal_item_wind); mTvDate.setText(weatherObj.week + "/n" + weatherObj.days); Picasso.with(this).load(weatherObj.weather_icon).into(mIvDayPic); Picasso.with(this).load(weatherObj.weather_icon1).into(mIvNightPic); if(!weatherObj.weather.contains("转")){ mTvDayWeather.setText(weatherObj.weather); mTvNightWeather.setText(weatherObj.weather); }else{ String weather[] = weatherObj.weather.split("转"); mTvDayWeather.setText(weather[0]); mTvNightWeather.setText(weather[1]); } mTvWind.setText(weatherObj.wind + "/n" + weatherObj.winp); mViewTrend.setLowTemArray(lowTemArray) .setHighTemArray(highTemArray) .setType(i == 0 ? 0 : i == weatherList.size() - 1 ? 1 : 2); ll.addView(itemView); } mHsTrend.addView(ll);

weatherList是未来天气数据集合


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表