细节优化、必应每日一图
在上一篇博客中已经实现了基本的功能,但是还有些美中不足,有一些细节问题要处理一下:
比如一进入页面的时候天气数据是通过网络加载的,这个时候网络慢的时候页面迟迟没有刷新,所以不太友好,常规的处理方式是给一个加载提示,告诉用户数据正在加载中,稍安勿躁。这就需要用到一个加载框了。
加载弹窗
加载框显示的图片:
加载框的背景图:
接下来自定义控件,在模块的view包创建两个自定义View
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
代码如下:
<?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文件中增加。
<!--加载弹窗的样式--> <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中使用。
接下来进行必应每日一图的接口访问。
访问地址:
https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1
访问之后:
用这串地址返回的数据生成一个实体Bean。
在项目的bean包下创建一个BiYingImgResponse.java类
代码如下:
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中增加
/** * 必应每日一图 */ @GET("/HPImageArchive.aspx?format=js&idx=0&n=1") Call<BiYingImgResponse> biying();
然后修改WeatherContract订阅器
其他的方法报错的话,后面加一个值,0就可以了,因为0是和风,1是必应。
接下来在MainActvity中,修改代码:
现在已经有图片地址了,但是得把图片显示出来才行。先修改activity_main.xml布局文件。
@BindView(R.id.bg) LinearLayout bg;//背景图
根布局指定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, "数据为空"); } }
运行一下:
背景图片就已经变了。累了吗?累了就歇会,不累的话就继续往下看