效果图
开发流程
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 */ @Override protected 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>() { @Override public void onSuccess(Call<HourlyResponse> call, Response<HourlyResponse> response) { if(getView() != null){ getView().getWeatherHourlyResult(response); } } @Override public void onFailed() { if(getView() != null){ getView().getDataFailed(); } } }); }
然后再
然后先去把自定义View做好。这里面会用到一些图标
先写好工具类,在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; } }
在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; //画虚线的点的index private 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开始添加虚线位置的索引值idx lastText = hourlyBase.getIcon(); } } idx++; } dashLineList.add(size - 1);//添加最后一条虚线位置的索引值idx invalidate(); } 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); //defPadding paddingT = 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);//防抖动 } @Override protected 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的heightMode int 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); } @Override protected 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 statement drawPoint = (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从上到下 } @Override public 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 */ @Override protected 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小时预报--> <TextView android: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小时预报布局--> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/dp_12"> <!--当天最高温--> <TextView android: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" /> <!--当天最低温--> <TextView android: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" /> <LinearLayout android: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.IndexHorizontalScrollView android:id="@+id/hsv" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.llw.goodweather.view.horizonview.HourlyForecastView android: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 */ @Override public 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); } @Override protected 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); } @Override protected 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") @Override protected 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() { @Override public 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
<!--太阳和月亮--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal"> <TextView android: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" /> <!--月相描述--> <TextView android: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--> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/dp_160" android:layout_marginTop="@dimen/dp_24" android:orientation="horizontal"> <!--太阳视图--> <com.llw.goodweather.view.skyview.SunView android: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.SunView android: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>() { @Override public void onSuccess(Call<SunMoonResponse> call, Response<SunMoonResponse> response) { if(getView() != null){ getView().getSunMoonResult(response); } } @Override public 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) @Override public 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,运行一下
看起来还是可以的,这篇文章就到这里结束了。