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

简介: Android 天气APP(二十七)增加地图天气的逐小时天气、太阳和月亮数据

效果图


20200904105930372.gif

开发流程


1.功能优化

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

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


1.功能优化


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


20200904090610738.png


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

我其实还想再说两句的


20200904090840719.png


好好好,开始写。


打开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("打开定位");
        }

20200904093524182.png



代码的意思也很明显开了定位就去定位,没有开定位就提示你一下顺便把文本改成打开定位。


然后设置一个全局的标识

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


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


20200904092931934.png


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




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,我在定位的监听回调中也做了一下处理


20210401143728542.png


这样就行成了一个闭环。


OK我们运行一下吧


20200904095251621.gif


这样就可以了。


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();
                    }
                }
            });
        }


然后再


20200904095742229.png


然后先去把自定义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


20200904101452515.png


下面一个一个来说明


先从接口来看


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中


20200904105103357.png


这一部分布局代码如下:


            <!--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小时天气预报请求


20200904105532416.png


然后在请求返回中做处理


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


  /**
     * 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,然后运行一下:


20200904105930372.gif


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包下新增一个


20200904111347196.png

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


20200904112012299.png

            <!--太阳和月亮-->
                        <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,你最好也自己新建一个


2020090411274970.png


打开Constant,新增如下:


20200904113054265.png


再打开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();
                    }
                }
            });
        }


再增加


20200904113440432.png


在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


20200904113846548.png


再来看返回


  /**
     * 太阳和月亮
     *
     * @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,运行一下


20200904141907529.png


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

相关文章
|
1月前
|
XML Java 数据库
安卓项目:app注册/登录界面设计
本文介绍了如何设计一个Android应用的注册/登录界面,包括布局文件的创建、登录和注册逻辑的实现,以及运行效果的展示。
139 0
安卓项目:app注册/登录界面设计
|
1月前
|
JSON API 网络安全
App数据的爬取
App数据的爬取
|
2月前
|
存储 开发工具 Android开发
使用.NET MAUI开发第一个安卓APP
【9月更文挑战第24天】使用.NET MAUI开发首个安卓APP需完成以下步骤:首先,安装Visual Studio 2022并勾选“.NET Multi-platform App UI development”工作负载;接着,安装Android SDK。然后,创建新项目时选择“.NET Multi-platform App (MAUI)”模板,并仅针对Android平台进行配置。了解项目结构,包括`.csproj`配置文件、`Properties`配置文件夹、平台特定代码及共享代码等。
158 2
|
1月前
|
存储 大数据 数据库
Android经典面试题之Intent传递数据大小为什么限制是1M?
在 Android 中,使用 Intent 传递数据时存在约 1MB 的大小限制,这是由于 Binder 机制的事务缓冲区限制、Intent 的设计初衷以及内存消耗和性能问题所致。推荐使用文件存储、SharedPreferences、数据库存储或 ContentProvider 等方式传递大数据。
59 0
|
2月前
|
XML Android开发 数据格式
🌐Android国际化与本地化全攻略!让你的App走遍全球无障碍!🌍
在全球化背景下,实现Android应用的国际化与本地化至关重要。本文以一款旅游指南App为例,详细介绍如何通过资源文件拆分与命名、适配布局与方向、处理日期时间及货币格式、考虑文化习俗等步骤,完成多语言支持和本地化调整。通过邀请用户测试并收集反馈,确保应用能无缝融入不同市场,提升用户体验与满意度。
102 3
|
1月前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
216 0
|
2月前
|
XML 数据库 Android开发
10分钟手把手教你用Android手撸一个简易的个人记账App
该文章提供了使用Android Studio从零开始创建一个简单的个人记账应用的详细步骤,包括项目搭建、界面设计、数据库处理及各功能模块的实现方法。
|
缓存 开发工具 Android开发
Android App性能评测分析-启动时间篇
1、前言 随着项目版本的迭代,App的性能问题会逐渐暴露出来,而好的用户体验与性能表现紧密相关,性能问题从应用的启动优化开始,下面会根据实际app性能测试案例,进行app性能评测之启动时间的分析和总结。
4263 0
|
Shell Linux 测试技术
Android App性能评测分析-cpu占用篇
1、前言 很多时候在使用APP的时候,手机可能会发热发烫。这是因为CPU使用率过高,CPU过于繁忙,会使整个手机无法响应用户,整体性能降低,用户体验就会很差,也容易引起ANR等等一系列问题。
5180 0
|
Java 测试技术 程序员
Android App性能评测分析-内存篇
1、内存了解 在Android App的性能优化的各个部分里,内存方面的知识较多且不易理解,内存的问题绝对是最令人头疼的一部分,需要对内存基础知识、内存分配、内存管理机制等非常熟悉,才能排查问题。
2237 0

热门文章

最新文章