廢話不多說,直接貼實現效果圖和代碼。。。。。。
效果圖如下:  
 
首先說一下我的實現思路,外部一個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="@m預覽圖是這樣的 中間那塊兒是一個自定義的view,下邊看看這個自定義view是如何實現的,為了讓這個自定義view適合任何數據。大致思路是: 1.算出每一度所占的高度,就是這幾天中有一個最高的溫度和一個最低的溫度,讓這兩個溫度相減得到最大溫度差,然后用這個view的高度(除去上下文本的高度)除以這個溫度差即得到這個值; 2.為了與前一天后一天的溫度相連起來,有必要知道與前一天溫度的中間值和與后一天溫度的中間值,這個值根據相似三角形原理算出分別是取當天與前一天溫度中間值以及當天與后一天溫度中間值 如圖:
 中間那塊兒是一個自定義的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是未來天氣數據集合
新聞熱點
疑難解答