上一篇:Android 天气APP(八)城市切换 之 自定义弹窗与使用

重新定位、必应每日一图

  • 新版-------------------
    • 一、封装定位
    • 二、重新定位
    • 三、必应每日一图
      • ① 添加必应接口
      • ② 必应壁纸存储库
      • ③ 每日一次请求
    • 四、切换背景图
    • 五、文章源码
  • 旧版-------------------
    • 加载弹窗

新版-------------------

  在上一篇文章中,实现了切换城市,搜索城市天气,但是存在一个问题,那就是我切换城市之后,怎么再回到当前定位的地方,因此我们需要一个重新定位的功能。

一、封装定位

  在写重新定位的功能之前,我觉得需要先将定位功能封装一下,现在定位的大部分在代码在MainActivity中,这样不太合理,下面我们在location包下创建一个GoodLocation类,代码如下:

public class GoodLocation {private static volatile GoodLocation mInstance;@SuppressLint("StaticFieldLeak")private static LocationClient mLocationClient = null;//定位监听private GoodLocationListener goodLocationListener;//定位回调接口private static LocationCallback callback;public GoodLocation(Context context) {initLocation(context);}public static GoodLocation getInstance(Context context) {if (mInstance == null) {synchronized (GoodLocation.class) {if (mInstance == null) {mInstance = new GoodLocation(context);}}}return mInstance;}/*** 初始化定位*/private void initLocation(Context context) {try {goodLocationListener = new GoodLocationListener();mLocationClient = new LocationClient(context);} catch (Exception e) {e.printStackTrace();}if (mLocationClient != null) {//注册定位监听mLocationClient.registerLocationListener(goodLocationListener);LocationClientOption option = new LocationClientOption();//如果开发者需要获得当前点的地址信息,此处必须为trueoption.setIsNeedAddress(true);//可选,设置是否需要最新版本的地址信息。默认不需要,即参数为falseoption.setNeedNewVersionRgc(true);//需将配置好的LocationClientOption对象,通过setLocOption方法传递给LocationClient对象使用mLocationClient.setLocOption(option);}}/*** 需要定位的页面调用此方法进行接口回调处理*/public void setCallback(LocationCallback callback) {GoodLocation.callback = callback;}/*** 开始定位*/public void startLocation() {if (mLocationClient != null) {mLocationClient.start();}}/*** 请求定位*/private static void requestLocation() {if (mLocationClient != null) {mLocationClient.requestLocation();}}/*** 停止定位*/private static void stopLocation() {if (mLocationClient != null) {mLocationClient.stop();}}/*** 内部类实现百度定位结果接收*/public static class GoodLocationListener extends BDAbstractLocationListener {private final String TAG = GoodLocationListener.class.getSimpleName();@Overridepublic void onReceiveLocation(BDLocation bdLocation) {if (bdLocation == null) return;if (bdLocation.getDistrict() == null) {Log.e(TAG, "onReceiveLocation: 未获取区/县数据,您可以重新断开连接网络再尝试定位。");requestLocation();}stopLocation();if (callback == null) {Log.e(TAG, "callback is Null!");return;}callback.onReceiveLocation(bdLocation);}}
}

  在这个类中,首先是一个单例,然后通过类构造方法,对定位进行初始化,初始化的同时调用内部类进行定位结果处理,最后再通过接口回调出去,注意一点我在获取定位结果之后就停止定位了,如果不停止的话会造成下一次定位没有结果返回。这个类的核心方法就三个:初始化、实现定位回调、开始定位。

下面我们到MainActivity中对这个类进行使用,首先是声明变量:

 private GoodLocation goodLocation;

然后我们修改一下initLocation()方法,代码如下:

    private void initLocation() {goodLocation = GoodLocation.getInstance(this);goodLocation.setCallback(this);}

  这里的代码相比之前就简单很多了,这就是封装的优点,假如你还有其他页面需要实现定位的话,也可以这么操作,少写重复的代码。然后就是修改startLocation()方法,代码如下所示:

 private void startLocation() {goodLocation.startLocation();}

那么现在对于类中的那个主要方法都已经使用了,我们可以将MainActivity中没用的代码删掉了,比如:

    public LocationClient mLocationClient = null;private final MyLocationListener myListener = new MyLocationListener();

还有就是将MyLocationListener 这个类删掉,它已经完成了自己的使命。

二、重新定位

  定位封装之后我们就需要实现重新定位这个功能,首先说一下逻辑,进入App时处于定位状态,那么不需要重新定位,而当我切换城市之后,不处于定位状态,就可以重新定位,可以通过一个按钮或者一个菜单项来点击触发重新定位的功能,这里我选择用菜单来进行,在上一篇文章中,我实现了菜单项切换城市的功能,那么在这里我将同样增加一个重新定位的菜单项,这个选项相比之前还是有去别的,首先在drawable下新建一个ic_round_location_24.xml文件,代码如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:tint="#FFFFFF"android:viewportWidth="24"android:viewportHeight="24"><pathandroid:fillColor="@android:color/white"android:pathData="M13.02,20.77L13.02,20.77c0,0.64 0.59,1.13 1.21,0.99c1.12,-0.26 2.18,-0.7 3.12,-1.3c0.53,-0.34 0.61,-1.1 0.16,-1.55l0,0c-0.32,-0.32 -0.83,-0.4 -1.21,-0.16c-0.77,0.49 -1.62,0.85 -2.53,1.05C13.32,19.9 13.02,20.31 13.02,20.77z" /><pathandroid:fillColor="@android:color/white"android:pathData="M4.03,12c0,-3.79 2.65,-6.97 6.2,-7.79c0.44,-0.1 0.75,-0.51 0.75,-0.96v0c0,-0.64 -0.6,-1.13 -1.22,-0.98C5.33,3.29 2.03,7.26 2.03,12c0,4.74 3.3,8.71 7.73,9.74c0.62,0.15 1.22,-0.34 1.22,-0.98v0c0,-0.46 -0.31,-0.86 -0.75,-0.96C6.68,18.97 4.03,15.79 4.03,12z" /><pathandroid:fillColor="@android:color/white"android:pathData="M20.79,11L20.79,11c0.64,0 1.13,-0.59 0.99,-1.21c-0.26,-1.12 -0.7,-2.17 -1.3,-3.12c-0.34,-0.54 -1.1,-0.61 -1.55,-0.16l0,0c-0.32,0.32 -0.4,0.83 -0.15,1.21c0.49,0.76 0.85,1.61 1.05,2.53C19.92,10.7 20.33,11 20.79,11z" /><pathandroid:fillColor="@android:color/white"android:pathData="M17.35,3.55c-0.95,-0.6 -2,-1.04 -3.12,-1.3c-0.62,-0.14 -1.21,0.35 -1.21,0.98v0c0,0.45 0.3,0.87 0.74,0.96c0.91,0.2 1.77,0.57 2.53,1.05c0.39,0.24 0.89,0.17 1.21,-0.16l0,0C17.96,4.64 17.89,3.89 17.35,3.55z" /><pathandroid:fillColor="@android:color/white"android:pathData="M18.92,17.49L18.92,17.49c0.45,0.45 1.21,0.38 1.55,-0.16c0.6,-0.94 1.04,-2 1.3,-3.12c0.14,-0.62 -0.35,-1.21 -0.98,-1.21h0c-0.45,0 -0.87,0.3 -0.96,0.74c-0.2,0.91 -0.57,1.77 -1.05,2.53C18.52,16.66 18.6,17.17 18.92,17.49z" /><pathandroid:fillColor="@android:color/white"android:pathData="M16,11.1C16,8.61 14.1,7 12,7s-4,1.61 -4,4.1c0,1.51 1.1,3.28 3.31,5.3c0.39,0.36 0.98,0.36 1.38,0C14.9,14.37 16,12.61 16,11.1zM12,12c-0.59,0 -1.07,-0.48 -1.07,-1.07c0,-0.59 0.48,-1.07 1.07,-1.07s1.07,0.48 1.07,1.07C13.07,11.52 12.59,12 12,12z" />
</vector>

这是一个菜单图标,然后我们修改一下menu_main.xml,在里面增加如下代码:

    <itemandroid:id="@+id/item_relocation"android:icon="@drawable/ic_round_location_24"android:title="重新定位"app:showAsAction="ifRoom" />

如下图所示:

  你会发现这个菜单是显示在ActionBar上面的,ifRoom表示,如果空间足够就显示出来,那么我们一开始是不需要显示出来的,切换城市后显示出来,点击之后搜索城市返回数据时再隐藏。

要完成这个功能,需要怎么做呢?进入MainActivity,声明变量:

    //菜单private Menu mMenu;//城市信息来源标识  0: 定位, 1: 切换城市private int cityFlag = 0;

  因为需要动态修改菜单项,所以声明这个mMenu,下面我们修改onCreateOptionsMenu()方法代码,如下图所示:

    @Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.menu_main, menu);mMenu = menu;//根据cityFlag设置重新定位菜单项是否显示mMenu.findItem(R.id.item_relocation).setVisible(cityFlag == 1);return true;}

  这里会判断这个cityFlag 的值,那么在什么地方进行赋值呢?首先是在startLocation()方法中,代码如下所示:

    private void startLocation() {cityFlag = 0;goodLocation.startLocation();}

然后就是选中要切换的城市之后,代码如下所示:

    @Overridepublic void selectedCity(String cityName) {cityFlag = 1;//切换城市//搜索城市viewModel.searchCity(cityName);//显示所选城市binding.tvCity.setText(cityName);}

这样的话,还剩下一个地方去再次是否需要重新定位,那就是在搜索城市拿到返回值时,修改代码如下图所示:

最后别忘记了点击item的地方,修改onOptionsItemSelected()方法代码,如下所示:

    @SuppressLint("NonConstantResourceId")@Overridepublic boolean onOptionsItemSelected(@NonNull MenuItem item) {switch (item.getItemId()) {case R.id.item_switching_cities:if (cityDialog != null) cityDialog.show();break;case R.id.item_relocation:startLocation();//点击重新定位item时,再次定位一下。break;}return true;}

下面我们来运行一下:

三、必应每日一图

  页面背景如果一直是不变的,好像也会造成审美疲劳,所以这里我们使用必应的每日一图接口,获取一张壁纸图片,首先要做的是添加接口。

① 添加必应接口

首先我们修改一下ApiType类,增加一个API类型,代码如下所示:

public enum ApiType {SEARCH,     //和风 城市搜索WEATHER,    //和风 天气接口BING,       //必应壁纸
}

然后修改NetworkApi中的getBaseUrl()方法,代码如下所示:

    private static void getBaseUrl(ApiType apiType) {switch (apiType) {case SEARCH:mBaseUrl = "https://geoapi.qweather.com";   //和风天气搜索城市break;case WEATHER:mBaseUrl = "https://devapi.qweather.com";   //和风天气APIbreak;case BING:mBaseUrl = "https://cn.bing.com";           //必应壁纸break;default:break;}}

  你会看到这里多了一个必应的请求地址头,下面根据必应请求返回的数据手写一个实体类,在bean包下新建一个BingResponse类,代码如下所示:

public class BingResponse {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 {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 {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;}}
}

下面添加接口,在ApiService中增加如下所示代码:

 @GET("/HPImageArchive.aspx?format=js&idx=0&n=1")Observable<BingResponse> bing();

② 必应壁纸存储库

在repository包下新建一个BingRepository类,里面的代码如下所示:

@SuppressLint("CheckResult")
public class BingRepository {private static final String TAG = WeatherRepository.class.getSimpleName();private static final class BingRepositoryHolder {private static final BingRepository mInstance = new BingRepository();}public static BingRepository getInstance() {return BingRepositoryHolder.mInstance;}/*** 必应壁纸** @param responseLiveData 成功数据* @param failed           错误信息*/public void bing(MutableLiveData<BingResponse> responseLiveData, MutableLiveData<String> failed) {String type = "必应壁纸-->";NetworkApi.createService(ApiService.class, ApiType.BING).bing().compose(NetworkApi.applySchedulers(new BaseObserver<>() {@Overridepublic void onSuccess(BingResponse bingResponse) {if (bingResponse == null) {failed.postValue("必应壁纸数据为null。");return;}responseLiveData.postValue(bingResponse);}@Overridepublic void onFailure(Throwable e) {Log.e(TAG, "onFailure: " + e.getMessage());failed.postValue(type + e.getMessage());}}));}
}

这里就是接口常规的使用,如果你之前的代码都有认真理解的话,这个就小菜一碟。

③ 每日一次请求

  现在我们需要明确一点,在哪里请求这个方法,必应每日一图,那么实际上一天只需要请求一次就可以了,上一篇文章中,我提到过这个方法,现在它就派上用场了。

在Constant中新增如下代码:

    /*** 是否使用必应壁纸*/public static final String USED_BING = "usedBing";/*** 必应图片地址*/public static final String BING_URL = "bingUrl";

  我们可以直接在启动页完成必应接口的数据请求,然后将图片的URL放到缓存中,那么就需要修改SplashViewModel中的代码,增加如下代码:

 public MutableLiveData<BingResponse> bingResponseMutableLiveData = new MutableLiveData<>();public void bing() {BingRepository.getInstance().bing(bingResponseMutableLiveData, failed);}

下面进入SplashActivity中,修改checkFirstRunToday()方法,代码如下所示:

    private void checkFirstRunToday() {long todayFirstRunTime = MVUtils.getLong(Constant.FIRST_STARTUP_TIME_TODAY);long currentTimeMillis = System.currentTimeMillis();long todayTwelveTimestamp = EasyDate.getTodayTwelveTimestamp();//满足更新启动时间的条件,1.为0表示没有保存过时间,2. 当前时间if (todayFirstRunTime == 0 || currentTimeMillis > todayTwelveTimestamp - (1000 * 60 * 10)) {MVUtils.put(Constant.FIRST_STARTUP_TIME_TODAY, currentTimeMillis);//今天第一次启动要做的事情viewModel.bing();}}

然后就是必应接口的回调处理,在onObserveData() 方法中增加如下所示代码:

        //必应壁纸数据返回viewModel.bingResponseMutableLiveData.observe(this, bingResponse -> {if (bingResponse.getImages() == null) {showMsg("未获取到必应的图片");return;}//得到的图片地址是没有前缀的,所以加上前缀否则显示不出来String bingUrl = "https://cn.bing.com" + bingResponse.getImages().get(0).getUrl();Log.d(TAG, "bingUrl: " + bingUrl);MVUtils.put(Constant.BING_URL, bingUrl);});//错误信息返回viewModel.failed.observe(this, this::showLongMsg);

  这里我将返回的图片地址进行了一次拼接,之后放入缓存中,最后记得要在onCreate()方法中,调用checkFirstRunToday()方法。

四、切换背景图

  现在我们有两个图,一个是app中自带的背景图,一个是必应的背景图,两个图片需要进行切换,我们可以再增加一个菜单项,修改menu_main.xml,增加如下所示代码:

    <itemandroid:id="@+id/item_bing"android:checkable="true"android:orderInCategory="20"android:title="必应壁纸" />

  这个菜单又和之前的菜单不同,这里有一个复选框,选中就表示使用必应壁纸,取消选中就表示使用默认的壁纸,而选中或不选中都会将值存放到缓存中,在运行App通过缓存就可以去设置壁纸了。

下面就是设置菜单了,修改onCreateOptionsMenu()方法代码,如下所示:

    @Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.menu_main, menu);mMenu = menu;//根据cityFlag设置重新定位菜单项是否显示mMenu.findItem(R.id.item_relocation).setVisible(cityFlag == 1);//根据使用必应壁纸的状态,设置item项是否选中mMenu.findItem(R.id.item_bing).setChecked(MVUtils.getBoolean(Constant.USED_BING));return true;}

  这里多加了一个对于必应壁纸item是否选中的处理,下面修改选中item的处理,修改onOptionsItemSelected()方法,代码如下所示:

    @SuppressLint("NonConstantResourceId")@Overridepublic boolean onOptionsItemSelected(@NonNull MenuItem item) {switch (item.getItemId()) {case R.id.item_switching_cities:if (cityDialog != null) cityDialog.show();break;case R.id.item_relocation:startLocation();//点击重新定位item时,再次定位一下。break;case R.id.item_bing:item.setChecked(!item.isChecked());MVUtils.put(Constant.USED_BING, item.isChecked());String bingUrl = MVUtils.getString(Constant.BING_URL);           break;}return true;}

  这里我们就已经拿到了必应壁纸了,那么怎么显示出来呢,需要用到Glide,在app的build.gradle的dependencies{}闭包下添加如下所示代码:

    //Glideimplementation 'com.github.bumptech.glide:glide:4.14.2'annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'

然后Sync Now,下面我们在utils包下新建一个GoodGlideModule类,里面的代码如下所示:

@GlideModule
public class GoodGlideModule extends AppGlideModule {}

然后再在utils包下新建一个GlideUtils类,代码如下所示:

public class GlideUtils {/*** 加载图片** @param context   上下文* @param url       图片url* @param viewGroup 布局*/public static void loadImg(Context context, String url, ViewGroup viewGroup) {Resources resources = context.getResources();DisplayMetrics dm = resources.getDisplayMetrics();int screenWidth = dm.widthPixels;int screenHeight = dm.heightPixels;Glide.with(context).load(url).override(screenWidth, screenHeight).centerCrop().into(new CustomTarget<Drawable>() {@Overridepublic void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {viewGroup.setBackground(resource);}@Overridepublic void onLoadCleared(@Nullable Drawable placeholder) {}});}
}

  这个工具类的作用就是用于给布局设置背景,因为我们的MainActivity的跟布局是ConstraintLayout,下面给它一个id,如下图所示:

然后回到MainActivity中,新增如下方法代码:

    private void updateBgImage(boolean usedBing, String bingUrl) {if (usedBing && !bingUrl.isEmpty()) {GlideUtils.loadImg(this, bingUrl, binding.layRoot);} else {binding.layRoot.setBackground(ContextCompat.getDrawable(this, R.drawable.main_bg));}}

这里就是判断当前显示什么背景图,然后有两个地方需要调用它,第一个地方如下图所示:

第二个地方则是需要在MainActivity中实现onResume()方法代码:

    @Overrideprotected void onResume() {super.onResume();//更新壁纸updateBgImage(MVUtils.getBoolean(Constant.USED_BING), MVUtils.getString(Constant.BING_URL));}

你直接添加到MainActivity中即可,下面就可以直接运行了,效果如下图所示:

是不是立竿见影,不过好像那个选中框的颜色有点不对劲,我们改一下。


现在选中就是黑色了,你可以改成其他你喜欢的颜色,都行,记得把values-night下的themes.xml也一起改了。

五、文章源码

欢迎 StarFork

第九篇文章源码地址:GoodWeather-New-9

旧版-------------------

在上一篇博客中已经实现了基本的功能,但是还有些美中不足,有一些细节问题要处理一下:
比如一进入页面的时候天气数据是通过网络加载的,这个时候网络慢的时候页面迟迟没有刷新,所以不太友好,常规的处理方式是给一个加载提示,告诉用户数据正在加载中,稍安勿躁。这就需要用到一个加载框了。

加载弹窗

加载框显示的图片:

加载框的背景图:

接下来自定义控件,在模块的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);}@Overrideprotected 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();}}}@Overrideprotected 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;//图片旋转点xprivate int mCenterRotateY;//图片旋转点yprivate 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()之前调用*/@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();if (mRunnable==null){mRunnable=new LoadingRunnable(this);}if (!mRunnable.isLoading){mRunnable.start();}}/*** view销毁时调用*/@Overrideprotected 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();}@Overridepublic 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.LoadingViewandroid:layout_width="wrap_content"android:layout_height="wrap_content" /><!--变色的字--><com.llw.mvplibrary.view.LoadingTextViewandroid: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中设置httpclientRetrofit 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图片加载框架。

 //获取必应每日一图返回@Overridepublic 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>() {@Overridepublic void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {Drawable drawable = new BitmapDrawable(context.getResources(), resource);bg.setBackground(drawable);}});} else {ToastUtils.showShortToast(context, "数据为空");}}

运行一下:

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

源码地址:GoodWeather
欢迎 StarFork

下一篇:Android 天气APP(十)继续优化、下拉刷新页面天气数据

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

  1. Android 天气APP(三十一)每日提醒弹窗

    上一篇:Android 天气APP(三十)分钟级降水 效果图 每日提醒弹窗 前言 正文 一.弹窗背景 二.每天第一次弹窗 三.弹出每日提示弹窗 四.弹窗的开关 文末 前言   为了增强用户的体验,所以 ...

  2. Android 天气APP(三十二)快捷切换常用城市

    上一篇:Android 天气APP(三十一)每日提醒弹窗 快捷切换常用城市及每日弹窗优化 前言 正文 一.创建适配器 二.配置列表加载常用城市数据 三.添加到常用城市列表 四.修改UI 文末 前言   ...

  3. Android 天气APP(三十)分钟级降水

    上一篇:Android 天气APP(二十九)壁纸设置.图片查看.图片保存 运行效果图 分钟级降水 前言 正文 一.新增分钟级降水API 二.修改布局 三.增加适配器 四.增加网络请求与回调 五.控件初 ...

  4. Android 天气APP(十)继续优化、下拉刷新页面天气数据

    上一篇:Android 天气APP(九)细节优化.必应每日一图 修复每日一图,增加下拉刷新,滑动改变标题 新版------------------- 一.修复每日请求必应壁纸Bug 二.增加下拉刷新 ...

  5. Android 天气APP(二十九)壁纸设置、图片查看、图片保存

    上一篇:Android 天气APP(二十八)地图搜索定位 效果图 开发流程 一.前情提要 二.正式开发 1. 列表数据填充 2. 浮动按钮的交互 3. 其他优化 4. 运行效果图 三.文末 一.前情提 ...

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

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

  7. Android 天气APP(十一)未来七天的天气预报、逐小时预报、UI优化

    上一篇:Android 天气APP(十)下拉刷新页面天气数据 逐小时天气预报.UI优化 新版------------------- 一.UI优化 二.逐小时天气预报 ① 添加逐小时天气API ② 使用 ...

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

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

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

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

最新文章

  1. hdu 1075 (字典树映射)
  2. gcc/g++编译器的优化
  3. .net html5 框架,ASP.NET - 介绍 ASP.NET Web 窗体框架的导航 | Microsoft Docs
  4. 从java读取Excel继续说大道至简 .
  5. virtualbox macos_MacOS 终于可以完美使用 Podman 了!
  6. java_数组插入001
  7. 虚拟化基础架构Windows 2008篇之2-域用户与域用户组管理
  8. Python天天美味(23) - enumerate遍历数组
  9. ubuntu命令查询版本和内核版本
  10. Delphi中的字符串压缩与解压缩
  11. Android实现组件之间同步的回调通信
  12. MATLAB 2017 b 安装+下载+破解(win10,linux,mac)
  13. 超声波传感器测距实验430
  14. Python关于pandas中 ValueError: Writing 0 cols but got ”XXX“ aliases的错误
  15. Node之使用os模块获取操作系统信息
  16. 梁念坚:从MOTO到微软 从无缝连接到统一沟通
  17. 春季实习生校园招聘总结
  18. 【调研】DRL优化网络通信领域顶会及期刊
  19. matlab画出周期为2的方波图形 傅立叶级数_matlab周期方波信号
  20. 【matlab郭彦甫课程答案】

热门文章

  1. JAVA web中的一点东西
  2. html中页面整体居中,css实现网页内容整体居中的三种方法总结
  3. 树莓派制作无线路由器
  4. 2021级程序设计ICODING答案分享
  5. MySQL审计插件使用和对比
  6. c# 获取照片的经纬度和时间
  7. ML-Agents案例之蠕虫
  8. 华宇软件华为鸿蒙,舒华体育携手华为打造:全球首款搭载鸿蒙操作系统跑步机面世...
  9. [学习][数据结构]二叉搜索树
  10. 百度富文本编辑器使用方法 html,配置使用百度提供的富文本编辑器ueditor(以项目实例图文步骤演示具体配置步骤)...