Android 天气APP(九)细节优化、必应每日一图

简介: Android 天气APP(九)细节优化、必应每日一图

细节优化、必应每日一图


在上一篇博客中已经实现了基本的功能,但是还有些美中不足,有一些细节问题要处理一下:


比如一进入页面的时候天气数据是通过网络加载的,这个时候网络慢的时候页面迟迟没有刷新,所以不太友好,常规的处理方式是给一个加载提示,告诉用户数据正在加载中,稍安勿躁。这就需要用到一个加载框了。


加载弹窗


加载框显示的图片:


20200407143450422.png


加载框的背景图:


20200407144742401.png


接下来自定义控件,在模块的view包创建两个自定义View


2020040714433495.png


LoadingTextView.java


package com.llw.mvplibrary.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatTextView;
/**
 * 颜色波浪TextView
 */
public class LoadingTextView extends AppCompatTextView {
    private LinearGradient mLinearGradient;
    private Matrix mGradientMatrix;
    private Paint mPaint;
    private int mViewWidth = 0;
    private int mTranslate = 0;
    private boolean mAnimating = true;
    public LoadingTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mViewWidth == 0) {
            mViewWidth = getMeasuredWidth();
            if (mViewWidth > 0) {
                mPaint = getPaint();
                mLinearGradient = new LinearGradient(-mViewWidth, 0, 0, 0,
                        new int[]{0x33ffffff, 0xff3286ED, 0x33ffffff},
                        new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
                mPaint.setShader(mLinearGradient);
                mGradientMatrix = new Matrix();
            }
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mAnimating && mGradientMatrix != null) {
            mTranslate += mViewWidth / 10;
            if (mTranslate > 2 * mViewWidth) {
                mTranslate = -mViewWidth;
            }
            mGradientMatrix.setTranslate(mTranslate, 0);
            mLinearGradient.setLocalMatrix(mGradientMatrix);
            postInvalidateDelayed(50);
        }
    }
}


LoadingView.java


package com.llw.mvplibrary.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import com.llw.mvplibrary.R;
import java.lang.ref.SoftReference;
/**
 * 加载框
 */
public class LoadingView extends androidx.appcompat.widget.AppCompatImageView {
    private int mCenterRotateX;//图片旋转点x
    private int mCenterRotateY;//图片旋转点y
    private LoadingRunnable mRunnable;
    public LoadingView(Context context) {
        this(context, null);
    }
    public LoadingView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        setScaleType(ScaleType.MATRIX);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.loading);
        setImageBitmap(bitmap);
        mCenterRotateX = bitmap.getWidth() / 2;
        mCenterRotateY = bitmap.getHeight() / 2;
    }
    /**
     * onDraw()之前调用
     */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mRunnable==null){
             mRunnable=new LoadingRunnable(this);
        }
        if (!mRunnable.isLoading){
            mRunnable.start();
        }
    }
    /**
     * view销毁时调用
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mRunnable!=null){
            mRunnable.stop();
        }
        mRunnable=null;
    }
     class LoadingRunnable implements Runnable {
        private boolean isLoading;
        private Matrix mMatrix;
        private SoftReference<LoadingView> mLoadingViewSoftReference;
        private float mDegrees = 0f;
        public LoadingRunnable(LoadingView loadingView) {
            mLoadingViewSoftReference = new SoftReference<LoadingView>(loadingView);
            mMatrix = new Matrix();
        }
        @Override
        public void run() {
            if (mLoadingViewSoftReference.get().mRunnable != null && mMatrix != null) {
                mDegrees += 30f;
                mMatrix.setRotate(mDegrees, mCenterRotateX, mCenterRotateY);
                mLoadingViewSoftReference.get().setImageMatrix(mMatrix);
                if (mDegrees==360){
                    mDegrees=0f;
                }
                if (isLoading) {
                    mLoadingViewSoftReference.get().postDelayed(mLoadingViewSoftReference.get().mRunnable, 100);
                }
            }
        }
        public void stop() {
            isLoading = false;
        }
        public void start() {
            isLoading = true;
            if (mLoadingViewSoftReference.get().mRunnable != null && mMatrix != null) {
                mLoadingViewSoftReference.get().postDelayed(mLoadingViewSoftReference.get().mRunnable, 100);
            }
        }
    }
}


在模块的res文件夹下创建一个新的layout文件夹用处存放布局文件,然后创


建一个弹窗的布局文件dialog_loading.xml

20200407145010204.png


代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_loading"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:background="@drawable/ic_loading_bg"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="16dp">
    <!--旋转的图-->
    <com.llw.mvplibrary.view.LoadingView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <!--变色的字-->
    <com.llw.mvplibrary.view.LoadingTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="加载中......"
        android:textColor="#fff"
        android:textSize="16sp" />
</LinearLayout>


当然弹窗的出现和消失也是要给动画的。


在模块的styles.xml文件中增加。

20200407145638495.png


  <!--加载弹窗的样式-->
    <style name="loading_dialog" parent="@android:style/Theme.Dialog">
        <item name="android:windowFrame">@null</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:background">@null</item>
        <item name="android:windowBackground">@null</item>
        <item name="android:backgroundDimEnabled">false</item>
    </style>


接下来就是使用了,考虑到可能有多个地方要使用这个,所以将使用方法封装到底层的BaseActivity中,


  private Dialog mDialog;//加载弹窗


  //弹窗出现
    public void showLoadingDialog(){
        if (mDialog == null) {
            mDialog = new Dialog(context, R.style.loading_dialog);
        }
        mDialog.setContentView(R.layout.dialog_loading);
        mDialog.setCancelable(false);
        mDialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        mDialog.show();
    }
    //弹窗消失
    public void dismissLoadingDialog(){
        if (mDialog != null) {
            mDialog.dismiss();
        }
        mDialog = null;
    }


同理,在BaseFragment中也放入,使用过程中只要你的Activity继承了

BaseActivity或者MvpActivity都可以调用弹窗的出现和消失方法。


接下来在MainActivity中使用。

20200407150901616.png

20200407151014829.png

20200407151040476.png


接下来进行必应每日一图的接口访问。


访问地址:

https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1

访问之后:

20200407151448661.png

用这串地址返回的数据生成一个实体Bean。


在项目的bean包下创建一个BiYingImgResponse.java

20200407151754833.png


代码如下:


package com.llw.goodweather.bean;
import java.util.List;
public class BiYingImgResponse {
    /**
     * images : [{"startdate":"20200406","fullstartdate":"202004061600","enddate":"20200407","url":"/th?id=OHR.PinkMoon_ZH-CN9026483067_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","urlbase":"/th?id=OHR.PinkMoon_ZH-CN9026483067","copyright":"四月的满月从圣迈克尔山上升起,英国康沃尔 (© Simon Maycock/Alamy Live News)","copyrightlink":"https://www.bing.com/search?q=%E5%9C%A3%E7%B1%B3%E6%AD%87%E5%B0%94%E5%B1%B1&form=hpcapt&mkt=zh-cn","title":"","quiz":"/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20200406_PinkMoon%22&FORM=HPQUIZ","wp":true,"hsh":"571d8c115ed49dad56d5f1e678ddeeb1","drk":1,"top":1,"bot":1,"hs":[]}]
     * tooltips : {"loading":"正在加载...","previous":"上一个图像","next":"下一个图像","walle":"此图片不能下载用作壁纸。","walls":"下载今日美图。仅限用作桌面壁纸。"}
     */
    private TooltipsBean tooltips;
    private List<ImagesBean> images;
    public TooltipsBean getTooltips() {
        return tooltips;
    }
    public void setTooltips(TooltipsBean tooltips) {
        this.tooltips = tooltips;
    }
    public List<ImagesBean> getImages() {
        return images;
    }
    public void setImages(List<ImagesBean> images) {
        this.images = images;
    }
    public static class TooltipsBean {
        /**
         * loading : 正在加载...
         * previous : 上一个图像
         * next : 下一个图像
         * walle : 此图片不能下载用作壁纸。
         * walls : 下载今日美图。仅限用作桌面壁纸。
         */
        private String loading;
        private String previous;
        private String next;
        private String walle;
        private String walls;
        public String getLoading() {
            return loading;
        }
        public void setLoading(String loading) {
            this.loading = loading;
        }
        public String getPrevious() {
            return previous;
        }
        public void setPrevious(String previous) {
            this.previous = previous;
        }
        public String getNext() {
            return next;
        }
        public void setNext(String next) {
            this.next = next;
        }
        public String getWalle() {
            return walle;
        }
        public void setWalle(String walle) {
            this.walle = walle;
        }
        public String getWalls() {
            return walls;
        }
        public void setWalls(String walls) {
            this.walls = walls;
        }
    }
    public static class ImagesBean {
        /**
         * startdate : 20200406
         * fullstartdate : 202004061600
         * enddate : 20200407
         * url : /th?id=OHR.PinkMoon_ZH-CN9026483067_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp
         * urlbase : /th?id=OHR.PinkMoon_ZH-CN9026483067
         * copyright : 四月的满月从圣迈克尔山上升起,英国康沃尔 (© Simon Maycock/Alamy Live News)
         * copyrightlink : https://www.bing.com/search?q=%E5%9C%A3%E7%B1%B3%E6%AD%87%E5%B0%94%E5%B1%B1&form=hpcapt&mkt=zh-cn
         * title : 
         * quiz : /search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20200406_PinkMoon%22&FORM=HPQUIZ
         * wp : true
         * hsh : 571d8c115ed49dad56d5f1e678ddeeb1
         * drk : 1
         * top : 1
         * bot : 1
         * hs : []
         */
        private String startdate;
        private String fullstartdate;
        private String enddate;
        private String url;
        private String urlbase;
        private String copyright;
        private String copyrightlink;
        private String title;
        private String quiz;
        private boolean wp;
        private String hsh;
        private int drk;
        private int top;
        private int bot;
        private List<?> hs;
        public String getStartdate() {
            return startdate;
        }
        public void setStartdate(String startdate) {
            this.startdate = startdate;
        }
        public String getFullstartdate() {
            return fullstartdate;
        }
        public void setFullstartdate(String fullstartdate) {
            this.fullstartdate = fullstartdate;
        }
        public String getEnddate() {
            return enddate;
        }
        public void setEnddate(String enddate) {
            this.enddate = enddate;
        }
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
        public String getUrlbase() {
            return urlbase;
        }
        public void setUrlbase(String urlbase) {
            this.urlbase = urlbase;
        }
        public String getCopyright() {
            return copyright;
        }
        public void setCopyright(String copyright) {
            this.copyright = copyright;
        }
        public String getCopyrightlink() {
            return copyrightlink;
        }
        public void setCopyrightlink(String copyrightlink) {
            this.copyrightlink = copyrightlink;
        }
        public String getTitle() {
            return title;
        }
        public void setTitle(String title) {
            this.title = title;
        }
        public String getQuiz() {
            return quiz;
        }
        public void setQuiz(String quiz) {
            this.quiz = quiz;
        }
        public boolean isWp() {
            return wp;
        }
        public void setWp(boolean wp) {
            this.wp = wp;
        }
        public String getHsh() {
            return hsh;
        }
        public void setHsh(String hsh) {
            this.hsh = hsh;
        }
        public int getDrk() {
            return drk;
        }
        public void setDrk(int drk) {
            this.drk = drk;
        }
        public int getTop() {
            return top;
        }
        public void setTop(int top) {
            this.top = top;
        }
        public int getBot() {
            return bot;
        }
        public void setBot(int bot) {
            this.bot = bot;
        }
        public List<?> getHs() {
            return hs;
        }
        public void setHs(List<?> hs) {
            this.hs = hs;
        }
    }
}


这个地方写API的时候就要注意了,因为必应的访问地址和和风的访问地址不一样,所以这里要用分支来做,首先修改ServiceGenerator


修改后的代码:


package com.llw.mvplibrary.net;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class ServiceGenerator {
    //https://free-api.heweather.net/s6/weather/now?key=3086e91d66c04ce588a7f538f917c7f4&location=深圳
    //将上方的API接口地址进行拆分得到不变的一部分,实际开发中可以将这一部分作为服务器的ip访问地址
    public static String BASE_URL = null;//地址
    private static String urlType(int type){
        switch (type){
            case 0://和风天气
                BASE_URL = "https://free-api.heweather.net";
                break;
            case 1://必应每日一图
                BASE_URL = "https://cn.bing.com";
                break;
        }
        return BASE_URL;
    }
    //创建服务  参数就是API服务
    public static <T> T createService(Class<T> serviceClass,int type) {
        //创建OkHttpClient构建器对象
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
        //设置请求超时的时间,这里是10秒
        okHttpClientBuilder.connectTimeout(10000, TimeUnit.MILLISECONDS);
        //消息拦截器  因为有时候接口不同在排错的时候 需要先从接口的响应中做分析。利用了消息拦截器可以清楚的看到接口返回的所有内容
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        //setlevel用来设置日志打印的级别,共包括了四个级别:NONE,BASIC,HEADER,BODY
        //BASEIC:请求/响应行
        //HEADER:请求/响应行 + 头
        //BODY:请求/响应航 + 头 + 体
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        //为OkHttp添加消息拦截器
        okHttpClientBuilder.addInterceptor(httpLoggingInterceptor);
        //在Retrofit中设置httpclient
        Retrofit retrofit = new Retrofit.Builder().baseUrl(urlType(type))//设置地址  就是上面的固定地址,如果你是本地访问的话,可以拼接上端口号  例如 +":8080"
                .addConverterFactory(GsonConverterFactory.create())//用Gson把服务端返回的json数据解析成实体
                .client(okHttpClientBuilder.build())//放入OKHttp,之前说过retrofit是对OkHttp的进一步封装
                .build();
        return retrofit.create(serviceClass);//返回这个创建好的API服务
    }
}


接下来在ApiService中增加

20200407152411792.png


   /**
     * 必应每日一图
     */
    @GET("/HPImageArchive.aspx?format=js&idx=0&n=1")
    Call<BiYingImgResponse> biying();


然后修改WeatherContract订阅器

20200407152708175.png


其他的方法报错的话,后面加一个值,0就可以了,因为0是和风,1是必应。

20200407152814332.png


接下来在MainActvity中,修改代码:

20200407155246341.png

现在已经有图片地址了,但是得把图片显示出来才行。先修改activity_main.xml布局文件。

20200407153348396.png

  @BindView(R.id.bg)
    LinearLayout bg;//背景图

20200407154946347.png


根布局指定ID,引入Glide图片加载框架。


  //获取必应每日一图返回
    @Override
    public void getBiYingResult(Response<BiYingImgResponse> response) {
        dismissLoadingDialog();
        if (response.body().getImages() != null) {
            //得到的图片地址是没有前缀的,所以加上前缀否则显示不出来
            String imgUrl = "http://cn.bing.com" + response.body().getImages().get(0).getUrl();
            Glide.with(context)
                    .asBitmap()
                    .load(imgUrl)
                    .into(new SimpleTarget<Bitmap>() {
                        @Override
                        public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
                            Drawable drawable = new BitmapDrawable(context.getResources(), resource);
                            bg.setBackground(drawable);
                        }
                    });
        } else {
            ToastUtils.showShortToast(context, "数据为空");
        }
    }


运行一下:

20200407155430460.png


背景图片就已经变了。累了吗?累了就歇会,不累的话就继续往下看

相关文章
|
14天前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
37 20
Android经典面试题之图片Bitmap怎么做优化
|
15天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
28 15
一个Android 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`配置文件夹、平台特定代码及共享代码等。
|
6天前
|
XML Android开发 数据格式
🌐Android国际化与本地化全攻略!让你的App走遍全球无障碍!🌍
在全球化背景下,实现Android应用的国际化与本地化至关重要。本文以一款旅游指南App为例,详细介绍如何通过资源文件拆分与命名、适配布局与方向、处理日期时间及货币格式、考虑文化习俗等步骤,完成多语言支持和本地化调整。通过邀请用户测试并收集反馈,确保应用能无缝融入不同市场,提升用户体验与满意度。
24 3
|
17天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
37 10
|
16天前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
26 2
|
17天前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
1天前
|
XML 数据库 Android开发
10分钟手把手教你用Android手撸一个简易的个人记账App
该文章提供了使用Android Studio从零开始创建一个简单的个人记账应用的详细步骤,包括项目搭建、界面设计、数据库处理及各功能模块的实现方法。
|
14天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
31 0
|
27天前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
104 0