上一篇:Android 天气APP(二十六)增加自动更新(检查版本、通知栏下载、自动安装)

效果图

开发流程

  • 1.功能优化
  • 2.地图天气中增加逐小时天气
  • 3.地图天气中增加太阳和月亮数据

1.功能优化

  首先说明一下,写这个APP是个人行为,所以很多的地方我写的代码并不是很完善,会存在这样或那样的问题,如果有遇到过的就及时告诉我,说到这个我之前一个朋友就遇到过,她说之前安装的时候可以正常打开,这一次安装之后就会直接闪退,问我是怎么回事,我也很纳闷啊?我说你多让几个朋友安装试试,看是不是会闪退,因为我自己也是经常测试这个APP,如果是一进去就闪退这么明显的BUG我怎么可能不知道呢?后面的结果就是她的朋友安装都可以正常打开,这就让我感到有点开心,可能不是我的问题,内心还有点喜悦,后面又想是不是内存不够了?但是我这个APP不耗多少内存啊,而且你既然可以安装的话,那么运行也不成问题,最后才知道问题所在,结果还是我的代码功能不够完善,因为她没有开定位。没有开定位,就无法获得定位的数据,没有数据的话我的全局变量就是null,然后我用这个null去请求接口,然后崩溃了,我太难了。

  这不是坑爹吗?当时我就想说你打开不就完了吗?但是我转念一想,我是不是应该提醒用户去打开定位呢?于是就有了下面的相关优化,说了这么多废话,是不是应该写代码了呢?
我其实还想再说两句的

好好好,开始写。
打开MainActivity,先来写一个判断是否打开了定位功能的方法,这个方法和onCreate是平级的

 /*** 手机是否开启位置服务,如果没有开启那么App将不能使用定位功能*/private boolean isOpenLocationServiceEnable() {LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);if (gps || network) {return true;}return false;}

  上面这段代码获取系统的定位服务,然后进一步获取网络定位和GPS定位的状态,然后返回用于判断。首先是initData中增加如下代码:

     if (isOpenLocationServiceEnable()) {tvCity.setEnabled(false);//不可点击startLocation();//开始定位} else {tvCity.setEnabled(true);//可以点击ToastUtils.showShortToast(context, "(((φ(◎ロ◎;)φ))),你好像忘记打开定位功能了");tvCity.setText("打开定位");}

  代码的意思也很明显开了定位就去定位,没有开定位就提示你一下顺便把文本改成打开定位。
然后设置一个全局的标识

private int OPEN_LOCATION = 9527;//进入手机定位设置页面标识

  然后为这个tvCity添加点击事件,在点击的时候还是要判断是否有打开定位权限


如果没有权限也不让进入地图天气页面

startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), OPEN_LOCATION);

  通过startActivityForResult跳转到系统定位设置页面时传递了一个请求码过去,然后当返回时用这个请求做验证就知道是不是当前的页面,然后再返回里面做判断,重写onActivityResult方法,代码如下。

 /*** 返回Activity的结果** @param requestCode* @param resultCode* @param data*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {if (requestCode == OPEN_LOCATION) {//则是从手机定位页面返回if (isOpenLocationServiceEnable()) {//已打开tvCity.setText("重新定位");tvCity.setEnabled(true);//可以点击}else {ToastUtils.showShortToast(context,"有意思吗?你跳过去又不打开定位,玩呢?嗯?我也是有脾气的好伐!");tvCity.setText("打开定位");tvCity.setEnabled(false);//不可点击}}super.onActivityResult(requestCode, resultCode, data);}

  在返回里面再做一次判断,因为你跳过去并不代表你就打开了定位,对于这种跳过去不打开定位的用户,我表示强烈谴责和抗议,同时为了不出现这种功能性上的BUG,我在定位的监听回调中也做了一下处理

这样就行成了一个闭环。

OK我们运行一下吧

这样就可以了。

2.地图天气中增加逐小时天气

下面就是在地图天气中增加逐小时天气了,这里我用了和风的自定义View,感觉还是不错的,下面来一步一步实现吧。首先在我既然要显示这个数据就要先获取,打开MapWeatherContract,在里面新增如下代码:

     /*** 24小时天气预报* @param location   城市名*/public void weatherHourly(String location){//这个3 表示使用新的V7API访问地址ApiService service = ServiceGenerator.createService(ApiService.class,3);service.hourlyWeather(location).enqueue(new NetCallBack<HourlyResponse>() {@Overridepublic void onSuccess(Call<HourlyResponse> call, Response<HourlyResponse> response) {if(getView() != null){getView().getWeatherHourlyResult(response);}}@Overridepublic void onFailed() {if(getView() != null){getView().getDataFailed();}}});}

然后再

然后先去把自定义View做好。这里面会用到一些图标
链接:图标提取地址
提取码:57sb
先写好工具类,在app下的utils包新建

package com.llw.goodweather.utils;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.util.DisplayMetrics;
import android.util.TypedValue;/*** 测量工具类*/
public class DisplayUtil {public static int dip2px(Context context, int dp){return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,context.getResources().getDisplayMetrics());}protected static int sp2px(Context context, int sp){return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,context.getResources().getDisplayMetrics());}public static int px2dp(Context context, float pxValue) {final float scale =  context.getResources().getDisplayMetrics().density;return (int) (pxValue / scale + 0.5f);}public static int dp2px(Context context, float dipValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dipValue * scale + 0.5f);}public static int px2sp(Context context, float pxValue) {final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;return (int) (pxValue / fontScale + 0.5f);}public static int sp2px(Context context, float spValue) {final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;return (int) (spValue * fontScale + 0.5f);}public static Bitmap bitmapResize(Bitmap src, float pxX, float pxY){//压缩图片Matrix matrix = new Matrix();matrix.postScale(pxX / src.getWidth(), pxY / src.getHeight());Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);return ret;}public static int getScreenWidth(Context context){DisplayMetrics dm = context.getResources().getDisplayMetrics();return dm.widthPixels;}public static int getScreenHeight(Context context){DisplayMetrics dm = context.getResources().getDisplayMetrics();return dm.heightPixels;}
}

IconUtils.java,里面的图标就是从上面的链接里面下载下来的

package com.llw.goodweather.utils;import com.llw.goodweather.R;/*** 图标工具类*/
public class IconUtils {/*** 获取白天深色天气图标*/public static int getDayIconDark(String weather) {int imageId;switch (weather) {case "100":imageId = R.mipmap.icon_100d;break;case "101":imageId = R.mipmap.icon_101d;break;case "102":imageId = R.mipmap.icon_102d;break;case "103":imageId = R.mipmap.icon_103d;break;case "104":imageId = R.mipmap.icon_104d;break;case "200":imageId = R.mipmap.icon_200d;break;case "201":imageId = R.mipmap.icon_210d;break;case "202":imageId = R.mipmap.icon_202d;break;case "203":imageId = R.mipmap.icon_203d;break;case "204":imageId = R.mipmap.icon_204d;break;case "205":imageId = R.mipmap.icon_205d;break;case "206":imageId = R.mipmap.icon_206d;break;case "207":imageId = R.mipmap.icon_207d;break;case "208":imageId = R.mipmap.icon_208d;break;case "209":imageId = R.mipmap.icon_209d;break;case "210":imageId = R.mipmap.icon_210d;break;case "211":imageId = R.mipmap.icon_211d;break;case "212":imageId = R.mipmap.icon_212d;break;case "213":imageId = R.mipmap.icon_213d;break;case "300":imageId = R.mipmap.icon_300d;break;case "301":imageId = R.mipmap.icon_301d;break;case "302":imageId = R.mipmap.icon_302d;break;case "303":imageId = R.mipmap.icon_303d;break;case "304":imageId = R.mipmap.icon_304d;break;case "305":imageId = R.mipmap.icon_305d;break;case "306":imageId = R.mipmap.icon_306d;break;case "307":imageId = R.mipmap.icon_307d;break;case "308":imageId = R.mipmap.icon_308d;break;case "309":imageId = R.mipmap.icon_309d;break;case "310":imageId = R.mipmap.icon_310d;break;case "311":imageId = R.mipmap.icon_311d;break;case "312":imageId = R.mipmap.icon_312d;break;case "313":imageId = R.mipmap.icon_313d;break;case "314":imageId = R.mipmap.icon_314d;break;case "315":imageId = R.mipmap.icon_315d;break;case "316":imageId = R.mipmap.icon_316d;break;case "317":imageId = R.mipmap.icon_317d;break;case "318":imageId = R.mipmap.icon_318d;break;case "399":imageId = R.mipmap.icon_399d;break;case "400":imageId = R.mipmap.icon_400d;break;case "401":imageId = R.mipmap.icon_401d;break;case "402":imageId = R.mipmap.icon_402d;break;case "403":imageId = R.mipmap.icon_403d;break;case "404":imageId = R.mipmap.icon_404d;break;case "405":imageId = R.mipmap.icon_405d;break;case "406":imageId = R.mipmap.icon_406d;break;case "407":imageId = R.mipmap.icon_407d;break;case "408":imageId = R.mipmap.icon_408d;break;case "409":imageId = R.mipmap.icon_409d;break;case "410":imageId = R.mipmap.icon_410d;break;case "499":imageId = R.mipmap.icon_499d;break;case "500":imageId = R.mipmap.icon_500d;break;case "501":imageId = R.mipmap.icon_501d;break;case "502":imageId = R.mipmap.icon_502d;break;case "503":imageId = R.mipmap.icon_503d;break;case "504":imageId = R.mipmap.icon_504d;break;case "507":imageId = R.mipmap.icon_507d;break;case "508":imageId = R.mipmap.icon_508d;break;case "509":imageId = R.mipmap.icon_509d;break;case "510":imageId = R.mipmap.icon_510d;break;case "511":imageId = R.mipmap.icon_511d;break;case "512":imageId = R.mipmap.icon_512d;break;case "513":imageId = R.mipmap.icon_513d;break;case "514":imageId = R.mipmap.icon_514d;break;case "515":imageId = R.mipmap.icon_515d;break;case "900":imageId = R.mipmap.icon_900d;break;case "901":imageId = R.mipmap.icon_901d;break;case "999":imageId = R.mipmap.icon_999d;break;default:imageId = R.mipmap.icon_100d;break;}return imageId;}/*** 获取晚上深色天气图标*/public static int getNightIconDark(String weather) {int imageId;switch (weather) {case "100":imageId = R.mipmap.icon_100n;break;case "101":imageId = R.mipmap.icon_101n;break;case "102":imageId = R.mipmap.icon_102n;break;case "103":imageId = R.mipmap.icon_103n;break;case "104":imageId = R.mipmap.icon_104n;break;case "200":imageId = R.mipmap.icon_200n;break;case "201":imageId = R.mipmap.icon_210n;break;case "202":imageId = R.mipmap.icon_202n;break;case "203":imageId = R.mipmap.icon_203n;break;case "204":imageId = R.mipmap.icon_204n;break;case "205":imageId = R.mipmap.icon_205n;break;case "206":imageId = R.mipmap.icon_206n;break;case "207":imageId = R.mipmap.icon_207n;break;case "208":imageId = R.mipmap.icon_208n;break;case "209":imageId = R.mipmap.icon_209n;break;case "210":imageId = R.mipmap.icon_210n;break;case "211":imageId = R.mipmap.icon_211n;break;case "212":imageId = R.mipmap.icon_212n;break;case "213":imageId = R.mipmap.icon_213n;break;case "300":imageId = R.mipmap.icon_300n;break;case "301":imageId = R.mipmap.icon_301n;break;case "302":imageId = R.mipmap.icon_302n;break;case "303":imageId = R.mipmap.icon_303n;break;case "304":imageId = R.mipmap.icon_304n;break;case "305":imageId = R.mipmap.icon_305n;break;case "306":imageId = R.mipmap.icon_306n;break;case "307":imageId = R.mipmap.icon_307n;break;case "308":imageId = R.mipmap.icon_308n;break;case "309":imageId = R.mipmap.icon_309n;break;case "310":imageId = R.mipmap.icon_310n;break;case "311":imageId = R.mipmap.icon_311n;break;case "312":imageId = R.mipmap.icon_312n;break;case "313":imageId = R.mipmap.icon_313n;break;case "314":imageId = R.mipmap.icon_314n;break;case "315":imageId = R.mipmap.icon_315n;break;case "316":imageId = R.mipmap.icon_316n;break;case "317":imageId = R.mipmap.icon_317n;break;case "318":imageId = R.mipmap.icon_318n;break;case "399":imageId = R.mipmap.icon_399n;break;case "400":imageId = R.mipmap.icon_400n;break;case "401":imageId = R.mipmap.icon_401n;break;case "402":imageId = R.mipmap.icon_402n;break;case "403":imageId = R.mipmap.icon_403n;break;case "404":imageId = R.mipmap.icon_404n;break;case "405":imageId = R.mipmap.icon_405n;break;case "406":imageId = R.mipmap.icon_406n;break;case "407":imageId = R.mipmap.icon_407n;break;case "408":imageId = R.mipmap.icon_408n;break;case "409":imageId = R.mipmap.icon_409n;break;case "410":imageId = R.mipmap.icon_410n;break;case "499":imageId = R.mipmap.icon_499n;break;case "500":imageId = R.mipmap.icon_500n;break;case "501":imageId = R.mipmap.icon_501n;break;case "502":imageId = R.mipmap.icon_502n;break;case "503":imageId = R.mipmap.icon_503n;break;case "504":imageId = R.mipmap.icon_504n;break;case "507":imageId = R.mipmap.icon_507n;break;case "508":imageId = R.mipmap.icon_508n;break;case "509":imageId = R.mipmap.icon_509n;break;case "510":imageId = R.mipmap.icon_510n;break;case "511":imageId = R.mipmap.icon_511n;break;case "512":imageId = R.mipmap.icon_512n;break;case "513":imageId = R.mipmap.icon_513n;break;case "514":imageId = R.mipmap.icon_514n;break;case "515":imageId = R.mipmap.icon_515n;break;case "900":imageId = R.mipmap.icon_900n;break;case "901":imageId = R.mipmap.icon_901n;break;case "999":imageId = R.mipmap.icon_999n;break;default:imageId = R.mipmap.icon_100n;break;}return imageId;}}

在com.llw.goodweather中新建view包,然后再建一个horizonview

下面一个一个来说明
先从接口来看
ScrollWatched.java

package com.llw.goodweather.view.horizonview;/*** 定义滑动监听接口*/
public interface ScrollWatched {void addWatcher(ScrollWatcher watcher);void removeWatcher(ScrollWatcher watcher);void notifyWatcher(int x);
}

ScrollWatcher.java

package com.llw.goodweather.view.horizonview;/*** 更新滑动*/
public interface ScrollWatcher {void update(int scrollX);
}

HourlyForecastView.java

package com.llw.goodweather.view.horizonview;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.llw.goodweather.R;
import com.llw.goodweather.bean.HourlyResponse;
import com.llw.goodweather.utils.DisplayUtil;
import com.llw.goodweather.utils.IconUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;/*** 24小时天气预报自定义View*/
public class HourlyForecastView extends View implements ScrollWatcher {private Context mContext;//折线private Paint foldLinePaint;private Paint backPaint;//底线private Paint baseLinePaint;//虚线private Paint dashPaint;//文字private Paint textPaint;//图片private Paint bitmapPaint;//文本的大小private int textSize;//数据private List<HourlyResponse.HourlyBean> hourlyWeatherList;//画虚线的点的indexprivate List<Integer> dashLineList;private int screenWidth;//每个item的宽度private int itemWidth;//温度基准高度private int lowestTempHeight;//温度基准高度private int highestTempHeight;//最低温private int lowestTemp;//最高温private int highestTemp;//默认图片绘制位置float bitmapHeight;//默认图片宽高float bitmapXY;//View宽高private int mWidth;private int mHeight;//默认高private int defHeightPixel = 0;private int defWidthPixel = 0;private int paddingL = 0;private int paddingT = 0;private int paddingR = 0;private int paddingB = 0;private int mScrollX = 0;private float baseLineHeight;private Paint paint1;private boolean isDark = false;public HourlyForecastView(Context context) {this(context, null);}public HourlyForecastView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, -1);}public HourlyForecastView(final Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public HourlyForecastView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(context);}private void init(Context context) {mContext = context;
//        if (ContentUtil.APP_SETTING_THEME.equals("深色")) {//            isDark = true;
//        } else {//            isDark = false;
//        }isDark = false;initDefValue();initPaint();}private static int ITEM_SIZE = 24;public void initData(List<HourlyResponse.HourlyBean> weatherData) {hourlyWeatherList = weatherData;int size = weatherData.size();ITEM_SIZE = size;dashLineList = new ArrayList<>();Iterator iterator = weatherData.iterator();HourlyResponse.HourlyBean hourlyBase;String lastText = "";int idx = 0;while (iterator.hasNext()) {hourlyBase = (HourlyResponse.HourlyBean) iterator.next();if (!hourlyBase.getIcon().equals(lastText)) {if (idx != size - 1) {dashLineList.add(idx);//从0开始添加虚线位置的索引值idxlastText = hourlyBase.getIcon();}}idx++;}dashLineList.add(size - 1);//添加最后一条虚线位置的索引值idxinvalidate();}private void initDefValue() {DisplayMetrics dm = getResources().getDisplayMetrics();screenWidth = dm.widthPixels;itemWidth = DisplayUtil.dp2px(mContext, 30);defWidthPixel = itemWidth * (ITEM_SIZE - 1);defHeightPixel = DisplayUtil.dp2px(mContext, 80);lowestTempHeight = DisplayUtil.dp2px(mContext, 40);//长度  非y轴值highestTempHeight = DisplayUtil.dp2px(mContext, 70);//defPaddingpaddingT = DisplayUtil.dp2px(mContext, 20);paddingL = DisplayUtil.dp2px(mContext, 10);paddingR = DisplayUtil.dp2px(mContext, 15);textSize = DisplayUtil.sp2px(mContext, 12);bitmapHeight = 1 / 2f * (2 * defHeightPixel - lowestTempHeight) + DisplayUtil.dp2px(mContext, 2);//- 给文字留地方bitmapXY = 18;}private TextPaint textLinePaint;private void initPaint() {//        setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速paint1 = new Paint(Paint.ANTI_ALIAS_FLAG);paint1.setColor(mContext.getResources().getColor(R.color.line_back_dark));paint1.setStyle(Paint.Style.FILL);foldLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);foldLinePaint.setStyle(Paint.Style.STROKE);foldLinePaint.setStrokeWidth(5);//折线颜色foldLinePaint.setColor(mContext.getResources().getColor(R.color.line_color_2));backPaint = new Paint(Paint.ANTI_ALIAS_FLAG);backPaint.setStrokeWidth(2);backPaint.setAntiAlias(true);dashPaint = new Paint(Paint.ANTI_ALIAS_FLAG);dashPaint.setColor(mContext.getResources().getColor(R.color.back_white));DashPathEffect pathEffect = new DashPathEffect(new float[]{8, 8, 8, 8}, 1);dashPaint.setPathEffect(pathEffect);dashPaint.setStrokeWidth(3);dashPaint.setAntiAlias(true);dashPaint.setStyle(Paint.Style.STROKE);textPaint = new Paint();textPaint.setTextAlign(Paint.Align.CENTER);textPaint.setTextSize(textSize);textLinePaint = new TextPaint();textLinePaint.setTextSize(DisplayUtil.sp2px(getContext(), 12));textLinePaint.setAntiAlias(true);textLinePaint.setColor(mContext.getResources().getColor(R.color.black));//底部时间文字颜色textPaint.setColor(mContext.getResources().getColor(R.color.search_light_un_color));baseLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);baseLinePaint.setStrokeWidth(3);baseLinePaint.setStyle(Paint.Style.STROKE);baseLinePaint.setColor(mContext.getResources().getColor(R.color.slategray));bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);bitmapPaint.setFilterBitmap(true);//图像滤波处理bitmapPaint.setDither(true);//防抖动}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//当设置的padding值小于默认值是设置为默认值paddingT = DisplayUtil.dp2px(mContext, 20);paddingL = DisplayUtil.dp2px(mContext, 10);paddingR = DisplayUtil.dp2px(mContext, 15);paddingB = Math.max(paddingB, getPaddingBottom());//获取测量模式//注意 HorizontalScrollView的子View 在没有明确指定dp值的情况下 widthMode总是MeasureSpec.UNSPECIFIED//同理 ScrollView的子View的heightModeint widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获取测量大小int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {mWidth = widthSize + paddingL + paddingR;mHeight = heightSize;}//如果为wrap_content 那么View大小为默认值if (widthMode == MeasureSpec.UNSPECIFIED && heightMode == MeasureSpec.AT_MOST) {mWidth = defWidthPixel + paddingL + paddingR;mHeight = defHeightPixel + paddingT + paddingB;}//设置视图的大小setMeasuredDimension(mWidth, mHeight);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);initDefValue();initPaint();if (hourlyWeatherList != null && hourlyWeatherList.size() != 0) {drawLines(canvas);drawBitmaps(canvas);drawTemp(canvas);}}private void drawTemp(Canvas canvas) {for (int i = 0; i < hourlyWeatherList.size(); i++) {if (currentItemIndex == i) {//计算提示文字的运动轨迹
//                int Y = getTempBarY(i);String tmp = hourlyWeatherList.get(i).getTemp();float temp = Integer.parseInt(tmp);int Y = (int) (tempHeightPixel(temp) + paddingT);//画出温度提示int offset = itemWidth / 4;Rect targetRect = new Rect(getScrollBarX(), Y - DisplayUtil.dip2px(getContext(), 24), getScrollBarX() + offset, Y - DisplayUtil.dip2px(getContext(), 4));Paint.FontMetricsInt fontMetrics = textLinePaint.getFontMetricsInt();int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;textLinePaint.setTextAlign(Paint.Align.LEFT);
//                if (ContentUtil.APP_SETTING_UNIT.equals("hua")) {//                    tmp = String.valueOf(TransUnitUtil.getF(tmp));
//                }canvas.drawText(tmp + "°", targetRect.centerX(), baseline, textLinePaint);}}}private void drawBitmaps(Canvas canvas) {int scrollX = mScrollX;boolean leftHide;boolean rightHide;for (int i = 0; i < dashLineList.size() - 1; i++) {leftHide = true;rightHide = true;int left = itemWidth * dashLineList.get(i) + paddingL;int right = itemWidth * dashLineList.get(i + 1) + paddingL;//图的中间位置  drawBitmap是左边开始画float drawPoint = 0;if (left > scrollX && left < scrollX + screenWidth) {leftHide = false;//左边缘显示}if (right > scrollX && right < scrollX + screenWidth) {rightHide = false;}if (!leftHide && !rightHide) {//左右边缘都显示drawPoint = (left + right) / 2f;} else if (leftHide && !rightHide) {//右边缘与屏幕左边drawPoint = (scrollX + right) / 2f;} else if (!leftHide) {//左边缘与屏幕右边//rightHide is True when reach this statementdrawPoint = (left + screenWidth + scrollX) / 2f;} else {//左右边缘都不显示if (right < scrollX + screenWidth) { //左右边缘都在屏幕左边continue;} else if (left > scrollX + screenWidth) {//左右边缘都在屏幕右边continue;} else {drawPoint = (screenWidth) / 2f + scrollX;}}String code = hourlyWeatherList.get(dashLineList.get(i)).getIcon();BitmapDrawable bd;if (code.contains("d")) {bd = (BitmapDrawable) mContext.getResources().getDrawable(IconUtils.getDayIconDark(code.replace("d", "")));} else {bd = (BitmapDrawable) mContext.getResources().getDrawable(IconUtils.getNightIconDark(code.replace("n", "")));}assert bd != null;Bitmap bitmap = DisplayUtil.bitmapResize(bd.getBitmap(),DisplayUtil.dp2px(mContext, bitmapXY), DisplayUtil.dp2px(mContext, bitmapXY));//越界判断if (drawPoint >= right - bitmap.getWidth() / 2f) {drawPoint = right - bitmap.getWidth() / 2f;}if (drawPoint <= left + bitmap.getWidth() / 2f) {drawPoint = left + bitmap.getWidth() / 2f;}drawBitmap(canvas, bitmap, drawPoint, bitmapHeight);
//            String text = hourlyWeatherList.get(dashLineList.get(i)).getCond_txt();
//            textPaint.setTextSize(DisplayUtil.sp2px(mContext, 8));
//            canvas.drawText(text, drawPoint, bitmapHeight + bitmap.getHeight() + 100 / 3f, textPaint);}}private void drawBitmap(Canvas canvas, Bitmap bitmap, float left, float top) {canvas.save();canvas.drawBitmap(bitmap, left - bitmap.getWidth() / 2, top, bitmapPaint);canvas.restore();}private void drawLines(Canvas canvas) {//底部的线的高度 高度为控件高度减去text高度的1.5倍baseLineHeight = mHeight - 1.5f * textSize;Path path = new Path();List<Float> dashWidth = new ArrayList<>();List<Float> dashHeight = new ArrayList<>();List<Point> mPointList = new ArrayList<>();for (int i = 0; i < hourlyWeatherList.size(); i++) {float temp = Integer.parseInt(hourlyWeatherList.get(i).getTemp());float w = itemWidth * i + paddingL;float h = tempHeightPixel(temp) + paddingT;Point point = new Point((int) w, (int) h);mPointList.add(point);//画虚线if (dashLineList.contains(i)) {dashWidth.add(w);dashHeight.add(h);}}float prePreviousPointX = Float.NaN;float prePreviousPointY = Float.NaN;float previousPointX = Float.NaN;float previousPointY = Float.NaN;float currentPointX = Float.NaN;float currentPointY = Float.NaN;float nextPointX;float nextPointY;for (int valueIndex = 0; valueIndex < hourlyWeatherList.size(); ++valueIndex) {if (Float.isNaN(currentPointX)) {Point point = mPointList.get(valueIndex);currentPointX = point.x;currentPointY = point.y;}if (Float.isNaN(previousPointX)) {//是否是第一个点if (valueIndex > 0) {Point point = mPointList.get(valueIndex - 1);previousPointX = point.x;previousPointY = point.y;} else {//是的话就用当前点表示上一个点previousPointX = currentPointX;previousPointY = currentPointY;}}if (Float.isNaN(prePreviousPointX)) {//是否是前两个点if (valueIndex > 1) {Point point = mPointList.get(valueIndex - 2);prePreviousPointX = point.x;prePreviousPointY = point.y;} else {//是的话就用当前点表示上上个点prePreviousPointX = previousPointX;prePreviousPointY = previousPointY;}}// 判断是不是最后一个点了if (valueIndex < hourlyWeatherList.size() - 1) {Point point = mPointList.get(valueIndex + 1);nextPointX = point.x;nextPointY = point.y;} else {//是的话就用当前点表示下一个点nextPointX = currentPointX;nextPointY = currentPointY;}if (valueIndex == 0) {// 将Path移动到开始点path.moveTo(currentPointX, currentPointY);} else {// 求出控制点坐标final float firstDiffX = (currentPointX - prePreviousPointX);final float firstDiffY = (currentPointY - prePreviousPointY);final float secondDiffX = (nextPointX - previousPointX);final float secondDiffY = (nextPointY - previousPointY);final float firstControlPointX = previousPointX + (0.2F * firstDiffX);final float firstControlPointY = previousPointY + (0.2F * firstDiffY);final float secondControlPointX = currentPointX - (0.2F * secondDiffX);final float secondControlPointY = currentPointY - (0.2F * secondDiffY);//画出曲线path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY,currentPointX, currentPointY);}// 更新值,prePreviousPointX = previousPointX;prePreviousPointY = previousPointY;previousPointX = currentPointX;previousPointY = currentPointY;currentPointX = nextPointX;currentPointY = nextPointY;}//画折线canvas.drawPath(path, foldLinePaint);path.lineTo(mWidth - paddingR, baseLineHeight);path.lineTo(paddingL, baseLineHeight);//画阴影int[] shadeColors = new int[]{Color.argb(100, 60, 174, 242),Color.argb(30, 60, 174, 242),Color.argb(18, 237, 238, 240)};Shader mShader = new LinearGradient(0, 0, 0, getHeight(), shadeColors, null, Shader.TileMode.CLAMP);backPaint.setShader(mShader);canvas.drawPath(path, backPaint);//画虚线drawDashLine(dashWidth, dashHeight, canvas);for (int i = 0; i < hourlyWeatherList.size(); i++) {float temp = Integer.parseInt(hourlyWeatherList.get(i).getTemp());float w = itemWidth * i + paddingL;float h = tempHeightPixel(temp) + paddingT;//画时间String time = hourlyWeatherList.get(i).getFxTime();//画时间if (ITEM_SIZE > 8) {if (i % 2 == 0) {if (i == 0) {textPaint.setTextAlign(Paint.Align.LEFT);} else {textPaint.setTextAlign(Paint.Align.CENTER);}canvas.drawText(time.substring(time.length() - 11, time.length() - 6), w, baseLineHeight + textSize + DisplayUtil.dip2px(mContext, 3), textPaint);}} else {textPaint.setTextAlign(Paint.Align.CENTER);if (i == 0) {canvas.drawText(".  现在", w, baseLineHeight + textSize + DisplayUtil.dip2px(mContext, 3), textPaint);} else {canvas.drawText(time.substring(time.length() - 11, time.length() - 6), w, baseLineHeight + textSize + DisplayUtil.dip2px(mContext, 3), textPaint);}}}}//画虚线private void drawDashLine(List<Float> dashWidth, List<Float> dashHeight, Canvas canvas) {if (dashHeight != null && dashHeight.size() > 1) {for (int i = 1; i < dashHeight.size() - 1; i++) {canvas.drawLine(dashWidth.get(i), dashHeight.get(i) + 3, dashWidth.get(i), baseLineHeight, dashPaint);}}}public float tempHeightPixel(float tmp) {float res = ((tmp - lowestTemp) / (highestTemp - lowestTemp)) * (highestTempHeight - lowestTempHeight) + lowestTempHeight;return defHeightPixel - res;//y从上到下}@Overridepublic void update(int scrollX) {mScrollX = scrollX;}public void setLowestTemp(int lowestTemp) {this.lowestTemp = lowestTemp;}public void setHighestTemp(int highestTemp) {this.highestTemp = highestTemp;}private int maxScrollOffset = 0;//滚动条最长滚动距离private int scrollOffset = 0; //滚动条偏移量private int currentItemIndex = 0; //当前滚动的位置所对应的item下标//设置scrollerView的滚动条的位置,通过位置计算当前的时段public void setScrollOffset(int offset, int maxScrollOffset) {this.maxScrollOffset = maxScrollOffset + DisplayUtil.dp2px(mContext, 50);scrollOffset = offset;currentItemIndex = calculateItemIndex();invalidate();}//通过滚动条偏移量计算当前选择的时刻private int calculateItemIndex() {int x = getScrollBarX();int sum = paddingL - itemWidth / 2;for (int i = 0; i < ITEM_SIZE - 1; i++) {sum += itemWidth;if (x < sum)return i;}return ITEM_SIZE - 1;}private int getScrollBarX() {int x = (ITEM_SIZE - 1) * itemWidth * scrollOffset / maxScrollOffset;x = x - DisplayUtil.dp2px(mContext, 3);return x;}}

IndexHorizontalScrollView.java

package com.llw.goodweather.view.horizonview;import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.HorizontalScrollView;import com.llw.goodweather.utils.DisplayUtil;/*** 横向滑动条*/
public class IndexHorizontalScrollView extends HorizontalScrollView {private HourlyForecastView hourlyForecastView;public IndexHorizontalScrollView(Context context) {this(context, null);}public IndexHorizontalScrollView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public IndexHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 绘制* @param canvas*/@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);int offset = computeHorizontalScrollOffset();int maxOffset = computeHorizontalScrollRange() - DisplayUtil.getScreenWidth(getContext());if(hourlyForecastView != null){hourlyForecastView.setScrollOffset(offset, maxOffset);}}/*** 设置24小时的View* @param today24HourView*/public void setToday24HourView(HourlyForecastView today24HourView){this.hourlyForecastView = today24HourView;}
}

里面还有一些颜色值,在mvplibrary下的colors.xml,如下

<?xml version="1.0" encoding="utf-8"?>
<resources><color name="arc_bg_color">#C6D7F4</color><color name="arc_progress_color">#FBFEF7</color><color name="white">#ffffff</color><!--白色--><color name="black">#000000</color><!--黑色--><color name="black_2">#1E1E1A</color><!--黑色2--><color name="green">#77d034</color><!--绿色--><color name="blue_one">#9FC8E9</color><!--浅蓝色--><color name="transparent">#00000000</color><!--透明--><color name="transparent_bg">#22000000</color><!--黑色半透明--><color name="transparent_bg_white">#22FFFFFF</color><!--白色半透明--><color name="orange">#F38A50</color><!--橘色--><color name="shallow_orange">#FFEFD5</color><!--浅橘色--><color name="black_3">#454545</color><!--黑色3--><color name="gray">#BABABA</color><!--灰色--><color name="pink">#FFBCB3</color><!--粉色--><color name="pink_one">#FDEBE8</color><!--浅粉色--><color name="shallow_blue">#E7F3FC</color><!--浅蓝色 偏白--><color name="shallow_gray">#F2F2F2</color><!--浅灰色--><color name="dark_gray">#707070</color><!--深灰色--><color name="shallow_black">#6D6D6D</color><!--褐黑色--><color name="red">#FF0A00</color><!--红色--><color name="line_gray">#E3E5E8</color><!--灰色分割线--><color name="shallow_yellow">#E7C373</color><!--浅黄色--><color name="world_city_color">#243440</color><color name="blue_more">#C8DCFF</color><!--浅蓝色--><color name="gray_white">#F8F8F8</color><!--灰白--><color name="transparent_bg_2">#44000000</color><!--黑色半透明 二--><color name="transparent_bg_3">#66000000</color><!--黑色半透明 三--><color name="transparent_bg_4">#70000000</color><!--黑色半透明 四--><color name="transparent_bg_5">#88000000</color><!--黑色半透明 五--><color name="temp_max_tx">#FF7E45</color><!--高温文字颜色--><color name="temp_min_tx">#B3BCCA</color><!--低温文字颜色--><color name="white_2">#22FFFFFF</color><!--白色透明度22%--><color name="white_4">#44FFFFFF</color><!--白色透明度44%--><color name="white_6">#66FFFFFF</color><!--白色透明度66%--><color name="white_8">#88FFFFFF</color><!--白色透明度88%--><color name="purple">#56004f</color><!--紫色--><color name="gray_white_2">#F6F6F6</color><!--灰白2--><color name="gray_2">#626262</color><!--灰色2--><color name="black_4">#141414</color><!--黑色4--><color name="line">#DEDEE1</color><!--分割线--><color name="point_color">#D9D9D9</color><!--点分割颜色--><color name="gray_3">#818181</color><!--灰色3--><color name="back_white">#f7f8fa</color><color name="attention_text_light">#E9EAEF</color><color name="dark_text_color">#B9C0CA</color><color name="line_back_dark">#3f8DA0BA</color><color name="line_color">#919191</color><color name="search_light_un_color">#666666</color><color name="slategray">#708090</color><!--灰石色 --><color name="about_bg_color">#50A0FF</color><color name="about_bg_color_2">#002C46</color><color name="blue">#597EF7</color><color name="sun_light_circle">#33F2BE55</color><color name="moon_light_circle">#33446FF2</color><color name="sun_dark_circle">#33AD9442</color><color name="moon_dark_circle">#334A5CC4</color><color name="color_a4a4a4">#A4A4A4</color><color name="line_color_2">#3C91F2</color><color name="moon_tv_color">#595959</color></resources>

然后在activity_map_weather.xml中

这一部分布局代码如下:

                     <!--24小时预报--><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="@dimen/dp_12"android:text="24小时预报"android:textColor="@color/black_4"android:textSize="@dimen/sp_16" /><!--24小时预报布局--><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="@dimen/dp_12"><!--当天最高温--><TextViewandroid:id="@+id/tv_line_max_tmp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="@dimen/dp_12"android:layout_marginTop="@dimen/dp_16"android:text="21°"android:textColor="@color/black_4"android:textSize="@dimen/sp_16" /><!--当天最低温--><TextViewandroid:id="@+id/tv_line_min_tmp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="@dimen/dp_12"android:layout_marginTop="@dimen/dp_66"android:text="11°"android:textColor="@color/black_4"android:textSize="@dimen/dp_16" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="@dimen/dp_98"android:layout_marginLeft="@dimen/dp_40"android:orientation="horizontal"android:paddingRight="@dimen/dp_12"><com.llw.goodweather.view.horizonview.IndexHorizontalScrollViewandroid:id="@+id/hsv"android:layout_width="match_parent"android:layout_height="wrap_content"><com.llw.goodweather.view.horizonview.HourlyForecastViewandroid:id="@+id/hourly"android:layout_width="wrap_content"android:layout_height="wrap_content" /></com.llw.goodweather.view.horizonview.IndexHorizontalScrollView></LinearLayout></RelativeLayout>

然后在MapWeatherActivity中

 @BindView(R.id.tv_line_max_tmp)TextView tvLineMaxTmp;//今日最高温@BindView(R.id.tv_line_min_tmp)TextView tvLineMinTmp;//今日最低温@BindView(R.id.hourly)HourlyForecastView hourly;//和风自定义逐小时天气渲染控件@BindView(R.id.hsv)IndexHorizontalScrollView hsv;//和风自定义滚动条

在搜索城市的返回中,新增一个24小时天气预报请求

然后在请求返回中做处理

然后实现里面的一个构造方法

 /*** 24小时天气预报数据返回* @param response*/@Overridepublic void getWeatherHourlyResult(Response<HourlyResponse> response) {if(response.body().getCode().equals(Constant.SUCCESS_CODE)){List<HourlyResponse.HourlyBean> hourlyWeatherList = response.body().getHourly();List<HourlyResponse.HourlyBean> data = new ArrayList<>();if (hourlyWeatherList.size() > 23) {for (int i = 0; i < 24; i++) {data.add(hourlyWeatherList.get(i));String condCode = data.get(i).getIcon();String time = data.get(i).getFxTime();time = time.substring(time.length() - 11, time.length() - 9);int hourNow = Integer.parseInt(time);if (hourNow >= 6 && hourNow <= 19) {data.get(i).setIcon(condCode + "d");} else {data.get(i).setIcon(condCode + "n");}}} else {for (int i = 0; i < hourlyWeatherList.size(); i++) {data.add(hourlyWeatherList.get(i));String condCode = data.get(i).getIcon();String time = data.get(i).getFxTime();time = time.substring(time.length() - 11, time.length() - 9);int hourNow = Integer.parseInt(time);if (hourNow >= 6 && hourNow <= 19) {data.get(i).setIcon(condCode + "d");} else {data.get(i).setIcon(condCode + "n");}}}int minTmp = Integer.parseInt(data.get(0).getTemp());int maxTmp = minTmp;for (int i = 0; i < data.size(); i++) {int tmp = Integer.parseInt(data.get(i).getTemp());minTmp = Math.min(tmp, minTmp);maxTmp = Math.max(tmp, maxTmp);}//设置当天的最高最低温度hourly.setHighestTemp(maxTmp);hourly.setLowestTemp(minTmp);if (maxTmp == minTmp) {hourly.setLowestTemp(minTmp - 1);}hourly.initData(data);tvLineMaxTmp.setText(maxTmp + "°");tvLineMinTmp.setText(minTmp + "°");}else {ToastUtils.showShortToast(context, CodeToStringUtils.WeatherCode(response.body().getCode()));}}

OK,然后运行一下:

3.地图天气中增加太阳和月亮数据

首先定义样式,在styles.xml中增加

 <declare-styleable name="SunAnimationView"><attr name="sun_circle_color" format="color"/><attr name="sun_font_color" format="color"/><attr name="sun_font_size" format="dimension"/><attr name="sun_circle_radius" format="integer"/><attr name="type" format="boolean"/></declare-styleable>

然后在view包下新增一个

SunView.java

package com.llw.goodweather.view.skyview;import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;import androidx.annotation.Nullable;import com.llw.goodweather.R;
import com.llw.goodweather.utils.DisplayUtil;import java.text.DecimalFormat;/*** 太阳、月亮自定义View*/
public class SunView extends View {private int mWidth; //屏幕宽度private int marginTop = 50;//离顶部的高度private int mCircleColor;  //圆弧颜色private int mFontColor;  //字体颜色private int mRadius;  //圆的半径private float mCurrentAngle; //当前旋转的角度private float mTotalMinute; //总时间(日落时间减去日出时间的总分钟数)private float mNeedMinute; //当前时间减去日出时间后的总分钟数private float mPercentage; //根据所给的时间算出来的百分占比private float positionX, positionY; //太阳图片的x、y坐标private float mFontSize;  //字体大小private String mStartTime; //开始时间(日出时间)private String mEndTime; //结束时间(日落时间)private String mCurrentTime; //当前时间private Paint mTextPaint; //画笔private Paint mLinePaint; //画笔private Paint mTimePaint; //画笔private RectF mRectF; //半圆弧所在的矩形private Bitmap mSunIcon; //太阳图片private WindowManager wm;private Paint mShadePaint;private Paint mPathPaint;private Context mContext;private boolean isSun = true;private float endHour;private Paint shadePaint;private Paint pathPaint;public SunView(Context context) {this(context, null);}public SunView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public SunView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView(context, attrs);}private void initView(Context context, @Nullable AttributeSet attrs) {mContext = context;marginTop = DisplayUtil.dip2px(context, 30);@SuppressLint("CustomViewStyleable") TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.SunAnimationView);mCircleColor = type.getColor(R.styleable.SunAnimationView_sun_circle_color, getContext().getResources().getColor(R.color.dark_text_color));mFontColor = type.getColor(R.styleable.SunAnimationView_sun_font_color, getContext().getResources().getColor(R.color.colorAccent));mRadius = type.getInteger(R.styleable.SunAnimationView_sun_circle_radius, DisplayUtil.dp2px(getContext(), 130));mRadius = DisplayUtil.dp2px(getContext(), mRadius);mFontSize = type.getDimension(R.styleable.SunAnimationView_sun_font_size, DisplayUtil.dp2px(getContext(), 10));mFontSize = DisplayUtil.dp2px(getContext(), mFontSize);isSun = type.getBoolean(R.styleable.SunAnimationView_type, true);type.recycle();mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mTimePaint = new Paint(Paint.ANTI_ALIAS_FLAG);shadePaint = new Paint(Paint.ANTI_ALIAS_FLAG);pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);}public void setType(boolean isSun, int circleColor, int fontColor) {this.isSun = isSun;mCircleColor = circleColor;mFontColor = fontColor;}public void setTimes(String startTime, String endTime, String currentTime) {mStartTime = startTime;mEndTime = endTime;mCurrentTime = currentTime;String currentTimes[] = currentTime.split(":");String startTimes[] = startTime.split(":");String endTimes[] = endTime.split(":");float currentHour = Float.parseFloat(currentTimes[0]);float currentMinute = Float.parseFloat(currentTimes[1]);float startHour = Float.parseFloat(startTimes[0]);endHour = Float.parseFloat(endTimes[0]);if (!isSun && endHour < startHour) {endHour += 24;}float endMinute = Float.parseFloat(endTimes[1]);if (isSun) {if (currentHour > endHour) {mCurrentTime = endTime;} else if (currentHour == endHour && currentMinute >= endMinute) {mCurrentTime = endTime;}}mTotalMinute = calculateTime(mStartTime, mEndTime, false);//计算总时间,单位:分钟mNeedMinute = calculateTime(mStartTime, mCurrentTime, true);//计算当前所给的时间 单位:分钟mPercentage = Float.parseFloat(formatTime(mTotalMinute, mNeedMinute));//当前时间的总分钟数占日出日落总分钟数的百分比mCurrentAngle = 180 * mPercentage;setAnimation(0, mCurrentAngle, 2000);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);mWidth = wm.getDefaultDisplay().getWidth() / 2;positionX = mWidth / 2 - mRadius - DisplayUtil.dip2px(mContext, 9); // 太阳图片的初始x坐标positionY = mRadius; // 太阳图片的初始y坐标super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, mWidth / 2 - mRadius, marginTop, mWidth / 2 + mRadius, mRadius * 2 + marginTop);}@SuppressLint("DrawAllocation")@Overrideprotected void onDraw(Canvas canvas) {// 渐变遮罩的画笔shadePaint.setColor(mContext.getResources().getColor(R.color.back_white));shadePaint.setStyle(Paint.Style.FILL);mShadePaint = shadePaint;pathPaint.setColor(mContext.getResources().getColor(R.color.attention_text_light));if (isSun) {mSunIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_sun);} else {mSunIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_moon);}mSunIcon = DisplayUtil.bitmapResize(mSunIcon, DisplayUtil.dp2px(mContext, 18), DisplayUtil.dp2px(mContext, 18));pathPaint.setStyle(Paint.Style.STROKE);pathPaint.setStrokeWidth(2);mPathPaint = pathPaint;mLinePaint.setStyle(Paint.Style.STROKE);mLinePaint.setDither(true);//防止抖动mLinePaint.setStrokeWidth(2);//第一步:画半圆drawSemicircle(canvas);canvas.save();mLinePaint.setColor(mContext.getResources().getColor(R.color.attention_text_light));canvas.drawLine(mWidth / 2 - mRadius - DisplayUtil.dip2px(mContext, 10), mRadius + marginTop, mWidth / 2 + mRadius + DisplayUtil.dip2px(mContext, 10), mRadius + marginTop, mLinePaint);//第二步:绘制太阳的初始位置 以及 后面在动画中不断的更新太阳的X,Y坐标来改变太阳图片在视图中的显示//第三部:绘制图上的文字drawSunPosition(canvas);drawFont(canvas);super.onDraw(canvas);}/*** 绘制半圆*/private void drawSemicircle(Canvas canvas) {mRectF = new RectF(mWidth / 2 - mRadius, marginTop, mWidth / 2 + mRadius, mRadius * 2 + marginTop);mTextPaint.setStyle(Paint.Style.STROKE);mTextPaint.setDither(true);//防止抖动mTextPaint.setColor(mCircleColor);canvas.drawArc(mRectF, 180, 180, true, mTextPaint);}/*** 绘制太阳的位置*/private void drawSunPosition(Canvas canvas) {//        canvas.drawRect(positionX + DisplayUtil.dp2px(mContext, 10), marginTop, mWidth / 2 + mRadius, mRadius * 2 + marginTop, mShadePaint);canvas.drawArc(mRectF, 180, 180, true, mPathPaint);canvas.drawBitmap(mSunIcon, positionX, positionY, mLinePaint);}/*** 绘制底部左右边的日出时间和日落时间** @param canvas*/private void drawFont(Canvas canvas) {mFontSize = DisplayUtil.dp2px(getContext(), 12);mTextPaint.setColor(mFontColor);mTextPaint.setTextSize(mFontSize);mTimePaint.setColor(getResources().getColor(R.color.black_4));mTimePaint.setTextSize(mFontSize);String startTime = TextUtils.isEmpty(mStartTime) ? "" : mStartTime;String endTime = TextUtils.isEmpty(mEndTime) ? "" : mEndTime;String sunrise = "日出";String sunset = "日落";if (!isSun) {sunrise = "月出";sunset = "月落";}mTimePaint.setTextAlign(Paint.Align.CENTER);mTextPaint.setTextAlign(Paint.Align.CENTER);canvas.drawText(sunrise, mWidth / 2 - mRadius + DisplayUtil.dip2px(mContext, 8), mRadius + DisplayUtil.dip2px(mContext, 16) + marginTop, mTextPaint);canvas.drawText(startTime, mWidth / 2 - mRadius + DisplayUtil.dip2px(mContext, 8), mRadius + DisplayUtil.dip2px(mContext, 32) + marginTop, mTimePaint);canvas.drawText(sunset, mWidth / 2 + mRadius - DisplayUtil.dip2px(mContext, 8), mRadius + DisplayUtil.dip2px(mContext, 16) + marginTop, mTextPaint);canvas.drawText(endTime, mWidth / 2 + mRadius - DisplayUtil.dip2px(mContext, 8), mRadius + DisplayUtil.dip2px(mContext, 32) + marginTop, mTimePaint);}/*** 精确计算文字宽度** @param paint 画笔* @param str   字符串文本*/public static int getTextWidth(Paint paint, String str) {int iRet = 0;if (str != null && str.length() > 0) {int len = str.length();float[] widths = new float[len];paint.getTextWidths(str, widths);for (int j = 0; j < len; j++) {iRet += (int) Math.ceil(widths[j]);}}return iRet;}/*** 根据日出和日落时间计算出一天总共的时间:单位为分钟** @param startTime 日出时间* @param endTime   日落时间* @return*/private float calculateTime(String startTime, String endTime, boolean isCurrent) {String startTimes[] = startTime.split(":");String endTimes[] = endTime.split(":");float startHour = Float.parseFloat(startTimes[0]);float startMinute = Float.parseFloat(startTimes[1]);float endHour = Float.parseFloat(endTimes[0]);float endMinute = Float.parseFloat(endTimes[1]);if (!isCurrent && !isSun && endHour < startHour) {endHour += 24;}if (isSun) {if (startHour > endHour) {return 0;} else if (startHour == endHour && startMinute >= endMinute) {return 0;}} else {if (isCurrent) {if (startHour > endHour) {return 0;} else if (startHour == endHour && startMinute >= endMinute) {return 0;}} else {if (startHour >= endHour + 24) {return 0;}}}if (checkTime(startTime, endTime)) {return (endHour - startHour - 1) * 60 + (60 - startMinute) + endMinute;}return 0;}/*** 对所给的时间做一下简单的数据校验** @param startTime* @param endTime* @return*/private boolean checkTime(String startTime, String endTime) {if (TextUtils.isEmpty(startTime) || TextUtils.isEmpty(endTime)|| !startTime.contains(":") || !endTime.contains(":")) {return false;}String startTimes[] = startTime.split(":");String endTimes[] = endTime.split(":");float startHour = Float.parseFloat(startTimes[0]);float startMinute = Float.parseFloat(startTimes[1]);float endHour = Float.parseFloat(endTimes[0]);float endMinute = Float.parseFloat(endTimes[1]);//如果所给的时间(hour)小于日出时间(hour)或者大于日落时间(hour)if ((startHour < Float.parseFloat(mStartTime.split(":")[0]))|| (endHour > this.endHour)) {return false;}//如果所给时间与日出时间:hour相等,minute小于日出时间if ((startHour == Float.parseFloat(mStartTime.split(":")[0]))&& (startMinute < Float.parseFloat(mStartTime.split(":")[1]))) {return false;}//如果所给时间与日落时间:hour相等,minute大于日落时间if ((startHour == this.endHour)&& (endMinute > Float.parseFloat(mEndTime.split(":")[1]))) {return false;}if (startHour < 0 || endHour < 0|| startHour > 23 || endHour > 23|| startMinute < 0 || endMinute < 0|| startMinute > 60 || endMinute > 60) {return false;}return true;}/*** 根据具体的时间、日出日落的时间差值 计算出所给时间的百分占比** @param totalTime 日出日落的总时间差* @param needTime  当前时间与日出时间差* @return*/private String formatTime(float totalTime, float needTime) {if (totalTime == 0)return "0.00";DecimalFormat decimalFormat = new DecimalFormat("0.00");//保留2位小数,构造方法的字符格式这里如果小数不足2位,会以0补足.return decimalFormat.format(needTime / totalTime);//format 返回的是字符串}private void setAnimation(float startAngle, float currentAngle, int duration) {ValueAnimator sunAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);sunAnimator.setDuration(duration);sunAnimator.setTarget(currentAngle);sunAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {//每次要绘制的圆弧角度mCurrentAngle = (float) animation.getAnimatedValue();invalidateView();}});sunAnimator.start();}private void invalidateView() {//绘制太阳的x坐标和y坐标positionX = mWidth / 2 - (float) (mRadius * Math.cos((mCurrentAngle) * Math.PI / 180)) - DisplayUtil.dp2px(mContext, 10);positionY = mRadius - (float) (mRadius * Math.sin((mCurrentAngle) * Math.PI / 180)) + DisplayUtil.dip2px(mContext, 18);invalidate();}
}

然后进入到activity_map_weather.xml

                     <!--太阳和月亮--><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center_vertical"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="@dimen/dp_12"android:text="太阳和月亮"android:textColor="@color/black_4"android:textSize="@dimen/sp_16" /><!--月相描述--><TextViewandroid:id="@+id/tv_moon_state"android:layout_width="@dimen/dp_0"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="right"android:paddingRight="@dimen/dp_12"android:textColor="@color/moon_tv_color"android:textSize="@dimen/sp_14" /></LinearLayout><!--太阳、月亮的View--><LinearLayoutandroid:layout_width="match_parent"android:layout_height="@dimen/dp_160"android:layout_marginTop="@dimen/dp_24"android:orientation="horizontal"><!--太阳视图--><com.llw.goodweather.view.skyview.SunViewandroid:id="@+id/sun_view"android:layout_width="0dp"android:layout_height="@dimen/dp_240"android:layout_weight="1"app:sun_circle_color="@color/sun_light_circle"app:sun_circle_radius="70"app:sun_font_color="@color/color_a4a4a4"app:sun_font_size="12px" /><!--月亮视图--><com.llw.goodweather.view.skyview.SunViewandroid:id="@+id/moon_view"android:layout_width="0dp"android:layout_height="@dimen/dp_240"android:layout_weight="1"app:sun_circle_color="@color/moon_light_circle"app:sun_circle_radius="70"app:sun_font_color="@color/color_a4a4a4"app:sun_font_size="12px"app:type="false" /></LinearLayout>

然后进入到MapWeatherActivity

 @BindView(R.id.sun_view)SunView sunView;//太阳@BindView(R.id.moon_view)SunView moonView;//月亮@BindView(R.id.tv_moon_state)TextView tvMoonState;//月亮状态

然后就要新增接口了,在新增接口之前,有一个小插曲就是,如果你是在S6版本下创建的Key,那么你是访问不了V7版本下的太阳和月亮的接口的,会提示403,就是没有权限,所以需要你重新创建一个应用KEY,这是我自己新建的KEY,你最好也自己新建一个

打开Constant,新增如下:

再打开ApiService,新增并且修改之前的接口,这里我就不一一展示了

 /*** 太阳和月亮  日出日落、月升月落*/@GET("/v7/astronomy/sunmoon?key="+ API_KEY)Call<SunMoonResponse> getSunMoon(@Query("location") String location, @Query("date") String date);

SunMoonResponse的代码如下:

package com.llw.goodweather.bean;import java.util.List;/*** 太阳和月亮数据实体*/
public class SunMoonResponse {/*** code : 200* updateTime : 2020-09-02T18:00+08:00* fxLink : http://hfx.link/2ax1* sunrise : 2020-09-02T05:44+08:00* sunset : 2020-09-02T18:41+08:00* moonrise : 2020-09-02T19:10+08:00* moonset : 2020-09-03T05:19+08:00* moonPhase : [{"fxTime":"2020-09-02T00:00+08:00","value":"0.48","name":"盈凸月","illumination":"100"},{"fxTime":"2020-09-02T01:00+08:00","value":"0.48","name":"盈凸月","illumination":"100"},{"fxTime":"2020-09-02T02:00+08:00","value":"0.48","name":"盈凸月","illumination":"100"},{"fxTime":"2020-09-02T03:00+08:00","value":"0.48","name":"盈凸月","illumination":"100"},{"fxTime":"2020-09-02T04:00+08:00","value":"0.48","name":"盈凸月","illumination":"100"},{"fxTime":"2020-09-02T05:00+08:00","value":"0.49","name":"盈凸月","illumination":"100"},{"fxTime":"2020-09-02T06:00+08:00","value":"0.49","name":"盈凸月","illumination":"100"},{"fxTime":"2020-09-02T07:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T08:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T09:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T10:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T11:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T12:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T13:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T14:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T15:00+08:00","value":"0.51","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T16:00+08:00","value":"0.52","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T17:00+08:00","value":"0.52","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T18:00+08:00","value":"0.52","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T19:00+08:00","value":"0.52","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T20:00+08:00","value":"0.52","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T21:00+08:00","value":"0.52","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T22:00+08:00","value":"0.52","name":"亏凸月","illumination":"100"},{"fxTime":"2020-09-02T23:00+08:00","value":"0.52","name":"亏凸月","illumination":"100"}]* refer : {"sources":["heweather.com"],"license":["no commercial use"]}*/private String code;private String updateTime;private String fxLink;private String sunrise;private String sunset;private String moonrise;private String moonset;private ReferBean refer;private List<MoonPhaseBean> moonPhase;public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getUpdateTime() {return updateTime;}public void setUpdateTime(String updateTime) {this.updateTime = updateTime;}public String getFxLink() {return fxLink;}public void setFxLink(String fxLink) {this.fxLink = fxLink;}public String getSunrise() {return sunrise;}public void setSunrise(String sunrise) {this.sunrise = sunrise;}public String getSunset() {return sunset;}public void setSunset(String sunset) {this.sunset = sunset;}public String getMoonrise() {return moonrise;}public void setMoonrise(String moonrise) {this.moonrise = moonrise;}public String getMoonset() {return moonset;}public void setMoonset(String moonset) {this.moonset = moonset;}public ReferBean getRefer() {return refer;}public void setRefer(ReferBean refer) {this.refer = refer;}public List<MoonPhaseBean> getMoonPhase() {return moonPhase;}public void setMoonPhase(List<MoonPhaseBean> moonPhase) {this.moonPhase = moonPhase;}public static class ReferBean {private List<String> sources;private List<String> license;public List<String> getSources() {return sources;}public void setSources(List<String> sources) {this.sources = sources;}public List<String> getLicense() {return license;}public void setLicense(List<String> license) {this.license = license;}}public static class MoonPhaseBean {/*** fxTime : 2020-09-02T00:00+08:00* value : 0.48* name : 盈凸月* illumination : 100*/private String fxTime;private String value;private String name;private String illumination;public String getFxTime() {return fxTime;}public void setFxTime(String fxTime) {this.fxTime = fxTime;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getIllumination() {return illumination;}public void setIllumination(String illumination) {this.illumination = illumination;}}
}

然后进入到MapWeatherContract,新增

     /*** 日出日落、月升月落* @param location   城市名*/public void getSunMoon(String location,String date){ApiService service = ServiceGenerator.createService(ApiService.class,3);service.getSunMoon(location,date).enqueue(new NetCallBack<SunMoonResponse>() {@Overridepublic void onSuccess(Call<SunMoonResponse> call, Response<SunMoonResponse> response) {if(getView() != null){getView().getSunMoonResult(response);}}@Overridepublic void onFailed() {if(getView() != null){getView().getDataFailed();}}});}

再增加

在DateUtil中新增两个个方法

//获取当前日期  没有分隔符public static String getNowDateNoLimiter() {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");return sdf.format(new Date());}/*** 获取当前时间转换成时和分* @param data* @return*/@RequiresApi(api = Build.VERSION_CODES.O)public static String getCurrentTime(String data) {String result = null;SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");if (data == null) {//获取当前时间的时和分result = sdf.format(new Date());} else {LocalDateTime date = LocalDateTime.parse(data, DateTimeFormatter.ISO_OFFSET_DATE_TIME);result = date.format(DateTimeFormatter.ofPattern("HH:mm"));}return result;}

现在进入到MapWeatherActivity.java

再来看返回

 /*** 太阳和月亮** @param response*/@RequiresApi(api = Build.VERSION_CODES.O)@Overridepublic void getSunMoonResult(Response<SunMoonResponse> response) {dismissLoadingDialog();if (response.body().getCode().equals(Constant.SUCCESS_CODE)) {SunMoonResponse data = response.body();if (data != null) {String sunRise = getCurrentTime(data.getSunrise());String moonRise = getCurrentTime(data.getMoonrise());String sunSet = getCurrentTime(data.getSunset());String moonSet = getCurrentTime(data.getMoonset());String currentTime = getCurrentTime(null);sunView.setTimes(sunRise, sunSet, currentTime);moonView.setTimes(moonRise, moonSet, currentTime);if(data.getMoonPhase() != null && data.getMoonPhase().size()>0){tvMoonState.setText(data.getMoonPhase().get(0).getName());}} else {ToastUtils.showShortToast(context, "日出日落数据为空");}} else {ToastUtils.showShortToast(context, CodeToStringUtils.WeatherCode(response.body().getCode()));}}

OK,运行一下

看起来还是可以的,这篇文章就到这里结束了。

源码地址:GoodWeather
欢迎 StarFork

下一篇:Android 天气APP(二十八)地图搜索定位

Android 天气APP(二十七)增加地图天气的逐小时天气、太阳和月亮数据相关推荐

  1. Android 天气APP(十七)热门城市 - 国内城市

    上一篇:Android 天气APP(十六)热门城市 - 海外城市 页面标题优化.添加加载弹窗 新版------------------- 一.主页面标题栏优化 二.加载等待弹窗 三.文章源码 旧版-- ...

  2. 简易的安卓天气app(二)——适配器、每小时数据展示

    ✅简易的安卓天气app(一)--解析Json数据.数据类封装

  3. 【Android游戏开发二十七】讲解游戏开发与项目下的hdpi 、mdpi与ldpi资源文件夹以及游戏高清版本的设置...

    今天一个开发者问到我为什么游戏开发要删除项目下的hdpi.mdpi和ldpi文件夹:下面详细给大家解答一下: 首先童鞋们如果看过我写的<[Android游戏开发二十一]Android os设备谎 ...

  4. html怎么预测未来天几天气,天气预报(含实时/未来5-7天/未来逐小时) - 数据接口 - NowAPI...

    Json返回示例{ success: "1", result: { weaid: "1", cityid: "101010100", are ...

  5. Android 垃圾分类APP(一)申请API、搭建项目、访问接口获取数据

    App演示视频 SVID_20210422_144935_1 前言   随着垃圾分类的呼声越来越高,未来说不定全国都会实行,因此了解分类可以让你少被罚钱,另外就是这也是为了环保,为了

  6. Android 天气APP(二十六)增加自动更新(检查版本、通知栏下载、自动安装)

    上一篇:Android 天气APP(二十五)地图天气(下)嵌套滑动布局渲染天气数据 效果图 开发流程 1.开发前言 2.上传应用到分发平台 3.版本数据请求与存储 4.检查版本更新.自定义更新提示弹窗 ...

  7. Android 天气APP(十四)修复UI显示异常、优化业务代码逻辑、增加详情天气显示

    上一篇:Android 天气APP(十三)仿微信弹窗(右上角加号点击弹窗效果).自定义背景图片.UI优化调整 添加管理城市 新版------------------- 一.添加管理城市页面 二.沉浸式 ...

  8. Android 天气APP(十二)空气质量、UI优化调整

    上一篇:Android 天气APP(十一)未来七天的天气预报.逐小时预报.UI优化 空气质量数据.UI优化 新版------------------- 一.自定义View 二.修改XML布局 三.添加 ...

  9. Android 天气APP(十三)仿微信弹窗(右上角加号点击弹窗效果)、自定义背景图片、UI优化调整

    上一篇:Android 天气APP(十二)空气质量.UI优化调整 天气预报详情,逐小时预报详情 新版------------------- 一.适配器点击监听 二.页面实现 三.天气预报详情弹窗 四. ...

最新文章

  1. CentOS 7如何设置Linux开机自动获取IP地址
  2. arduinows2812灯条程序_Arduino 控制WS2812 LED灯条
  3. python中英文半角还是全角_Python3全角转半角的方法
  4. python为什么是动态语言_python为什么是动态语言
  5. JS使用按位异或方式加密字符串
  6. TZOJ 5640: 数据结构实验:仓库管理
  7. ubuntu下安装配置java8
  8. 典型相关分析相关资料
  9. php调用声卡,利用vb测声卡_visualbasic教程
  10. 致谢zyf2000,仅是你的备份,留念和记录学习C++的足迹
  11. srt文件的时间轴平移处理
  12. 小强机器人用户手册和教程目录
  13. 树莓派4B-安装64位操作系统
  14. 【区块链技术与应用】(三)
  15. Nutch学习——读源码 Crawl.java
  16. 计算机教师格言座右铭,教师励志格言座右铭100句
  17. 基于发布者/订阅者模式的小媛逛超市
  18. sql goup by
  19. 使用Python多线程犯的错误总结
  20. S32K144-hello_word点灯

热门文章

  1. 云效触发Jenkins自动构建
  2. ROS实验笔记之——Intel Realsense l515激光相机的使用
  3. ISO认证体系有哪些
  4. HDU1814 Peaceful Commission 2SAT
  5. 蓝桥杯 基础练习 分解质因数 python语言
  6. (一)EasyExcel的使用(读取数据到实体类即绑定实体类)
  7. 第2节 部署虚拟机及常用系统配置
  8. dsniff 和 Ettercap 和 bettercap 详解
  9. 我的读书笔记 - 《暗时间》
  10. 马士兵Python基础版2020教程P58-P96 PPT笔记+课堂代码