目录
文章目录
天气预报总结
自己仿写了一个小项目,在里面有一些新的东西需要学习,并且整体思路整理一下,这里从头开始一点点讲解这个项目,会尽量细一点
起初是根据第一行代码第三版写的,不过这里使用的语言是java,在第一行语言中他将其整体分为了三个部分(三个功能),书上使用了
MVVM框架,他把数据和UI分割开来,然后有一个连接二者的桥梁,一共三个部分,这里先根据他的思路讲解,注意这里我使用的不是书上的彩云天气,而是和风天气,所以后面的数据导入会和书上不同
1.搜索城市数据
1.逻辑层代码
使用MVVM框架,因为找不到在ViewModel层开始就不和Activity直接联系所以很容易找不到Context,这里给这个项目提供一个获取全局获取Context的方法
public class SunnyWeatherApplication extends Application {
public static Context context;//获取Context对象
public static final String TOKEN = "";//和风天气的密钥
public static Sum summm1;
public static List<Sum> summm2; //这俩个变量负责后面数据库存储数据时传递数据,开始写可以不写
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
public static Context getContext() {
return context;
}
}
在AndroidManifest.xml中的application标签下指定该项目
android:name=".SunnyWeatherApplication"
<application
android:name=".SunnyWeatherApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Weather"
tools:targetApi="31">
<activity
android:name="ui.weather.baocun"
android:exported="false" />
<activity
android:name="ui.weather.WeatherActivity"
android:exported="false" />
这里完成主活动的设置,下面开始第二步,写之前打开开发文档,看一下,了解API的数据模型,网络地址,参数等
设置数据模型,建议设置一个包来放置所有的数据类,这里的place有俩个类,一个是Place一个是PlaceResponse,Place负责接收后面使用的数据,PlaceResponse负责从接口收到数据
- List item
PlaceResponse
package logic.model;
import java.util.List;
public class PlaceResponse {
private String code;//他和其他数据不同
private List<Place> location;
public PlaceResponse(String status, List<Place> places) {
this.code = status;
this.location = places;
}
public String getStatus() {
return code;
}
public List<Place> getPlaces() {
return location;
}
}
Place
package logic.model;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class Place {
private String name;
private String id;
private String lat;
private String lom;
private String adm2;
private String adm1;
private String country;
private String tz;
private String utcOffset;//这里的所有变量都和接口内的类同名,不然可能导致数据无法正常导入
public Place(String name, String id, String lat, String lom, String adm2, String adm1, String country, String tz, String utcOffset) {
this.name = name;
this.id = id;
this.lat = lat;
this.lom = lom;
this.adm2 = adm2;
this.adm1 = adm1;
this.country = country;
this.tz = tz;
this.utcOffset = utcOffset;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLat() {
return lat;
}
public void setLat(String lat) {
this.lat = lat;
}
public String getLom() {
return lom;
}
public void setLom(String lom) {
this.lom = lom;
}
public String getAdm2() {
return adm2;
}
public void setAdm2(String adm2) {
this.adm2 = adm2;
}
public String getAdm1() {
return adm1;
}
public void setAdm1(String adm1) {
this.adm1 = adm1;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getTz() {
return tz;
}
public void setTz(String tz) {
this.tz = tz;
}
public String getUtcOffset() {
return utcOffset;
}
public void setUtcOffset(String utcOffset) {
this.utcOffset = utcOffset;
}
}
网络层的书写(建议开辟一个包),这里没有使用OKHTTP,而是使用了Retrofit,(后面和有博客),这里先写接口
public interface PlaceService {
@GET("v2/city/lookup?")
Call<PlaceResponse> searchPlaces(
@Query("location") String location, // 必选参数:需要查询地区的名称
@Query("key") String apiKey // 必选参数:用户认证key
);
}
这里还需要创建一个Retrofit的实例
package ui.network;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class ServiceCreator {
static Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://geoapi.qweather.com/") // 设置基础 URL
.addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 解析器
.build();
static Retrofit retrofit2 = new Retrofit.Builder()
.baseUrl("https://devapi.qweather.com/") // 设置基础 URL
.addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 解析器
.build();
static Retrofit retrofit3 = new Retrofit.Builder()
.baseUrl("https://devapi.qweather.com/") // 设置基础 URL
.addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 解析器
.build();
static Retrofit retrofit4 = new Retrofit.Builder()
.baseUrl("https://devapi.qweather.com/") // 设置基础 URL
.addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 解析器
.build();
static Retrofit retrofit5 = new Retrofit.Builder()
.baseUrl("https://devapi.qweather.com/") // 设置基础 URL
.addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 解析器
.build();
public static <T> T create(Class<T> serviceClass) { return retrofit.create(serviceClass); }
public static <T> T create2(Class<T> serviceClass) { return retrofit2.create(serviceClass); }
public static <T> T create3(Class<T> serviceClass) { return retrofit3.create(serviceClass); }
public static <T> T create4(Class<T> serviceClass) { return retrofit4.create(serviceClass); }
public static <T> T create5(Class<T> serviceClass) { return retrofit5.create(serviceClass); }
}
这里创建了五个实例是因为之后的数据请求Retrofit实例我也放到了这个类中,理解意思即可,不建议全部抄,后续会把项目上传到Github上,,完成这些我们就有了得到数据的方法,下面我们会在一个类中对所有的网络申请API进行封装,新建一个单例类
package ui.network;
import android.util.Log;
import java.io.IOException;
import logic.model.AirResponse;
import logic.model.NewResponse;
import logic.model.PlaceResponse;
import logic.model.LifeResponse;
import logic.model.TimeResponse;
import logic.model.somedailyresponse;
import retrofit2.Response;
public class SunnyWeatherNetwork {
private static final PlaceService placeService = ServiceCreator.create(PlaceService.class);//搜索城市
private static final WeatherService weather= ServiceCreator.create2(WeatherService.class);//天气信息
private static final WeatherService life= ServiceCreator.create3(WeatherService.class);//生活建议
private static final WeatherService time = ServiceCreator.create4(WeatherService.class); //24小时信息
private static final WeatherService air = ServiceCreator.create5(WeatherService.class); //24小时信息
public static PlaceResponse searchPlaces(String query, String apiKey) {
try {
Response<PlaceResponse> response = placeService.searchPlaces(query, apiKey).execute();
if (response.isSuccessful()) {
return response.body();
} else {
throw new IOException("Unexpected HTTP code: " + response.code());
}
} catch (IOException e) {
e.printStackTrace();
return null; // 或者抛出适当的异常
}
}
public static NewResponse getNewdailyweather(String id) {
try {
Response<NewResponse> response1 = weather.getNewdailyweather("101010100","1bbbb4d603ae4affa79d4e5824b837d4").execute();
if (response1.isSuccessful()) {
return response1.body();
} else {
Log.d("TAG1234","NEW获取失败"+response1.code());
throw new IOException("Unexpected HTTP code: ");
}
} catch (IOException e) {
e.printStackTrace();
return null; // 或者抛出适当的异常
}
}
public static somedailyresponse getsomedailyweather(String id) {
try {
Response<somedailyresponse> response1 = weather.getsomedailyweather("101010100", "1bbbb4d603ae4affa79d4e5824b837d4").execute();
if (response1.isSuccessful()) {
Log.d("TAG1234","很多天数据");
return response1.body();
} else {
Log.d("TAG1234","some获取失败"+response1.code());
throw new IOException("Unexpected HTTP code: " + response1.code());
}
} catch (IOException e) {
e.printStackTrace();
return null; // 或者抛出适当的异常
}
}
public static LifeResponse getdailylife(String id, String type) {
try {
Response<LifeResponse> response3 = life.getdailylife(id,"1bbbb4d603ae4affa79d4e5824b837d4",type).execute();
if (response3.isSuccessful()) {
return response3.body();
} else {
throw new IOException("Unexpected HTTP code: " + response3.code());
}
} catch (IOException e) {
e.printStackTrace();
return null; // 或者抛出适当的异常
}
}
public static TimeResponse gettime(String id)
{
try{
Response<TimeResponse> response4 = time.gettime(id,"1bbbb4d603ae4affa79d4e5824b837d4").execute();
if (response4.isSuccessful()) {
return response4.body();
} else {
throw new IOException("Unexpected HTTP code: " + response4.code());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static AirResponse getAir(String id)
{
try{
Response<AirResponse> response5 = air.getAir(id,"1bbbb4d603ae4affa79d4e5824b837d4").execute();
if (response5.isSuccessful()) {
return response5.body();
} else {
throw new IOException("Unexpected HTTP code: " + response5.code());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
这里也是多个方向,这里挑一个方法即searchPlaces,来进行讲解
public static PlaceResponse searchPlaces(String query, String apiKey) {
try {
Response<PlaceResponse> response = placeService.searchPlaces(query, apiKey).execute();
if (response.isSuccessful()) {
return response.body();
} else {
throw new IOException("Unexpected HTTP code: " + response.code());
}
} catch (IOException e) {
e.printStackTrace();
return null; // 或者抛出适当的异常
}
}
这里开始了一个异常块处理,请求失败发生异常会抛出一个 IOException
异常。这个方法是静态的,参数是前面API所需要的参数
Response是 Retrofit 库中的一个类,这里的apiKey可以直接写死在这里,当时把它传递到后面进行处理的。这里最后进行一个仓库层的书写。
package ui.network;
import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import java.util.List;
import logic.dao.PlaceDao;
import logic.model.AirResponse;
import logic.model.LifeResponse;
import logic.model.NewResponse;
import logic.model.Place;
import logic.model.PlaceResponse;
import logic.model.TimeResponse;
import logic.model.Weather;
import logic.model.somedailyresponse;
public class Repository {
// 保存地点信息
public static void savePlace(Place place) {
PlaceDao.savePlace(place);
}
// 获取保存的地点信息
public static Place getSavedPlace() {
return PlaceDao.getSavedPlace();
}
// 检查是否有保存的地点信息
public static boolean isPlaceSaved() {
return PlaceDao.isPlaceSaved();
}
public static LiveData<List<Place>> getPlaceLiveData(MutableLiveData<String> searchLiveData, MutableLiveData<String> apiKeyLiveData) {
return Transformations.switchMap(searchLiveData, query -> {
MutableLiveData<List<Place>> resultLiveData = new MutableLiveData<>();
String apiKey = apiKeyLiveData.getValue();
if (apiKey != null) {
searchPlaces(query);
}
return resultLiveData;
});
}
public interface ResultCallback<T> {
void onSuccess(T result);
void onFailure(Exception e);
}
public static LiveData<List<Place>> searchPlaces(String query) {
MutableLiveData<List<Place>> resultLiveData = new MutableLiveData<>();
new Thread(() -> {
PlaceResponse placeResponse = SunnyWeatherNetwork.searchPlaces(query, "1bbbb4d603ae4affa79d4e5824b837d4");
if ("200".equals(placeResponse.getStatus())) {
List<Place> places = placeResponse.getPlaces();
resultLiveData.postValue(places);
} else {
resultLiveData.postValue(null); // 或者传递一个默认值或错误信息
}
}).start();
return resultLiveData;
}
public static LiveData<Weather> refreshWeather(String id) {
MutableLiveData<Weather> liveData = new MutableLiveData<>();
new Thread(() -> {
NewResponse newdailyweather = SunnyWeatherNetwork.getNewdailyweather(id);
somedailyresponse somedailyresponse = SunnyWeatherNetwork.getsomedailyweather(id);
LifeResponse lifeResponse = SunnyWeatherNetwork.getdailylife(id, "0");
TimeResponse timeResponse = SunnyWeatherNetwork.gettime(id);
AirResponse airResponse = SunnyWeatherNetwork.getAir(id);
if(newdailyweather != null&&somedailyresponse!=null&&lifeResponse!=null&&timeResponse!=null)
{
if (newdailyweather.getCode().equals("200") && somedailyresponse.getCode().equals("200")&&lifeResponse.getCode().equals("200")) {
Weather weather = new Weather(newdailyweather.getNow(),somedailyresponse.getDaily(),lifeResponse.getDaily(),timeResponse.getHourly(),airResponse.getNow());
liveData.postValue(weather);
} else {
liveData.postValue(null); // 或者传递一个默认值或错误信息
}
}
}).start();
return liveData;
}
}
这里也是多个函数的入口,所以先进行其中searchPlaces函数的讲解
public static LiveData<List<Place>> searchPlaces(String query) {
MutableLiveData<List<Place>> resultLiveData = new MutableLiveData<>();
new Thread(() -> {
PlaceResponse placeResponse = SunnyWeatherNetwork.searchPlaces(query, "1bbbb4d603ae4affa79d4e5824b837d4");
if ("200".equals(placeResponse.getStatus())) {
List<Place> places = placeResponse.getPlaces();
resultLiveData.postValue(places);
} else {
resultLiveData.postValue(null); // 或者传递一个默认值或错误信息
}
}).start();
return resultLiveData;
}
这里定义了返回了一个LiveData<List> 类型的值,LiveData是JetPack的部分内容后面会有他的博客,建议学习。
首先他创建了一个MutableLiveData对象,他的泛型是List这个 resultLiveData
对象将被用于在异步任务中存储地点搜索的结果
然后开辟了一个新的线程,在线程中进行了数据的获取,如果收到的数据状态码为200则表示请求成功,否则传递的其他值查看开发文档是什么问题,当请求成功时从 placeResponse
中获取地点信息列表。将获取到的地点列表 places
更新到 LiveData
中,以便通知观察者。
这里主要是将数据储存在 LiveData
对象中,以便在 UI 层观察并处理相应的数据。
然后进行逻辑层的最后一步,书写ViewModel
package ui.place;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import java.util.ArrayList;
import java.util.List;
import logic.model.Place;
import ui.network.Repository;
public class PlaceViewModel extends ViewModel {
// 保存地点信息
public static void savePlace(Place place) {
Repository.savePlace(place);
}
// 获取保存的地点信息
public static Place getSavedPlace() {
return Repository.getSavedPlace();
}
// 检查是否有保存的地点信息
public static boolean isPlaceSaved() {
return Repository.isPlaceSaved();
}
public MutableLiveData<String> searchLiveData = new MutableLiveData<>();
public List<Place> placeList;
public PlaceViewModel() {
placeList = new ArrayList<>();
}
LiveData<List<Place>> placeLiveData = Transformations.switchMap(searchLiveData, new androidx.arch.core.util.Function<String, LiveData<List<Place>>>() {
@Override
public LiveData<List<Place>> apply(String query) {
return Repository.searchPlaces(query);
}
});
public List<Place> getPlaceList() {
return placeList;
}
public void setSearchData(String query, String apiKey) {
searchLiveData.setValue(query);
}
public void clearPlaceList() {
if (placeList != null) {
placeList.clear();
}
}
public void setPlaceList(List<Place> places) {
placeList = places;
}
public void searchPlaces(String content) {
searchLiveData.setValue(content);
}
public LiveData<List<Place>> getPlaceLiveData() {
return placeLiveData;
}
}
这里同样有其他操作的函数,对这里要用到的东西进行分析
public MutableLiveData<String> searchLiveData = new MutableLiveData<>();
public List<Place> placeList;
LiveData<List<Place>> placeLiveData = Transformations.switchMap(searchLiveData, new androidx.arch.core.util.Function<String, LiveData<List<Place>>>() {
@Override
public LiveData<List<Place>> apply(String query) {
return Repository.searchPlaces(query);
}
});
public List<Place> getPlaceList() {
return placeList;
}
public void setSearchData(String query, String apiKey) {
searchLiveData.setValue(query);
}
public void clearPlaceList() {
if (placeList != null) {
placeList.clear();
}
}
public void setPlaceList(List<Place> places) {
placeList = places;
}
public void searchPlaces(String content) {
searchLiveData.setValue(content);
}
public LiveData<List<Place>> getPlaceLiveData() {
return placeLiveData;
}
说一下这里的主要作用,核心是 Transformations.switchMap,在这个函数中他没有直接调前面的SearchLiveData,而是通过switchMap来观察,在searchLiveData发生改变及有操作发生时,Transformations.switchMap就可以观察地点信息的变化
2.UI层代码
这里的 有一个不一样的点,这个功能在后面用会用到,在这个项目中这个功能会在俩个地方用到,一个下载后第一次打开确定城市,一个是显示天气页面中搜索城市,为了简化代码这里没有使用Activity,而是选择了使用Fragment,方便后面的复用
首先在res/layout中新建一个布局,这里叫fragment_place,
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/bgImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_marginTop="-100dp"
android:layout_marginBottom="-85dp" />
<FrameLayout
android:id="@+id/actionBarLayout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@drawable/bj">
<EditText
android:id="@+id/searchPlaceEdit"
android:layout_width="match_parent"
android:layout_height="60dp"
android:paddingTop="0dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="@color/white"
android:hint="输入地址"
android:paddingStart="10dp"
android:paddingEnd="60dp" />
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recyclerView"
android:layout_below="@+id/actionBarLayout"
android:visibility="gone"/>
</RelativeLayout>
这里使用了RelativeLayout是为了ImageView中位置的处理,ImageView的功能是设置一个背景图,这里我没有设置,
FrameLayout则是为了各个控件的叠放,最后就是一个简单的recyclerview,用于展示各个城市的信息
这里用到了recyclerview,就要写他的子项布局
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="130dp"
android:layout_margin="12dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="18dp"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/placeName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp" />
<TextView
android:id="@+id/placeAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
这里使用了MaterialCardView布局,这个是用于显示卡片的一个效果,其他很好理解,现在开始写适配器
public class PlaceAdapter extends RecyclerView.Adapter<PlaceAdapter.ViewHolder> {
private PlaceFragment fragment;
private List<Place> placeList;
private Place place;
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView placeName;
public TextView placeAddress;
public ViewHolder(View view) {
super(view);
placeName = view.findViewById(R.id.placeName);
placeAddress = view.findViewById(R.id.placeAddress);
}
}
public PlaceAdapter(PlaceFragment fragment, List<Place> placeList) {
this.fragment = fragment;
this.placeList = placeList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Place place = placeList.get(viewType);
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.place_item, parent, false);
ViewHolder holder = new ViewHolder(view);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(parent.getContext(), WeatherActivity.class);
intent.putExtra("name", place.getName());
intent.putExtra("id", place.getId());
intent.putExtra("TTT", "1");//说明是搜索框进行数据跳转
Log.d("TAGG",place.getName());
fragment.viewModel.savePlace(place);
fragment.startActivity(intent);
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
place = placeList.get(position);
holder.placeName.setText(place.getName());
holder.placeAddress.setText(place.getCountry()+" "+place.getAdm1()+" "+place.getAdm2());
}
@Override
public int getItemCount() {
return placeList.size();
}
}
这个代码没有什么特别的点,ViewHolder内部类中保存了视图的,也都比较容易
这里进行了点击事件中进行了活动的跳转,和位置信息的储存,所以第一次写可以改变点击事件到这里,这样就需要完成Fragment了,
public class PlaceFragment extends Fragment {
public PlaceViewModel viewModel;
private PlaceAdapter adapter;
RecyclerView recyclerView;
TextView searchPlaceEdit;
ImageView bgImageView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {//初始化页面
View view = inflater.inflate(R.layout.fragment_place, container, false);
recyclerView = view.findViewById(R.id.recyclerView);
searchPlaceEdit = view.findViewById(R.id.searchPlaceEdit);
bgImageView = view.findViewById(R.id.bgImageView);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {//初始化功能
super.onActivityCreated(savedInstanceState);
if (viewModel.isPlaceSaved()&&getActivity() instanceof MainActivity) {//数据储存
Place place = viewModel.getSavedPlace();
Intent intent = new Intent(context, WeatherActivity.class);
intent.putExtra("name", place.getName());
intent.putExtra("TTT", "0");
intent.putExtra("id", place.getId());
startActivity(intent);
if (getActivity() != null) {
getActivity().finish();
}
return;
}
viewModel = new ViewModelProvider(this).get(PlaceViewModel.class);
adapter = new PlaceAdapter(this, viewModel.getPlaceList());
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
searchPlaceEdit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String content = s.toString();
if (content != null) {
viewModel.searchPlaces(content);
} else {
viewModel.searchPlaces(content);
recyclerView.setVisibility(View.GONE);
bgImageView.setVisibility(View.VISIBLE);
viewModel.getPlaceList().clear();
adapter.notifyDataSetChanged();
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
viewModel.getPlaceLiveData().observe(getViewLifecycleOwner(), new Observer<List<Place>>() {
@Override
public void onChanged(List<Place> places) {
if (places != null) {
recyclerView.setVisibility(View.VISIBLE);
bgImageView.setVisibility(View.GONE);
viewModel.getPlaceList().clear();
viewModel.getPlaceList().addAll(places);
adapter.notifyDataSetChanged();
} else {
Toast.makeText(getActivity(), "未能查询到任何地点", Toast.LENGTH_SHORT).show();
}
}
});
}
}
在onCreateView中完成了各个视图控件的引入,这个方法会在Fragment被创建的时候调用,onActivityCreated会在Activity 的 onCreate
方法执行完成后会被调用,负责完成一些初始化工作,在这个方法中,首先进行了地点的储存,这个是后面加的功能可以先不进行处理,然后获取ViewModel,和recycleView的引用处理
searchPlaceEdit.addTextChangedListener这是一个文本变化监听器,用于监听搜索框中的文本变化,当用户在搜索框中输入文字时,onTextChanged
方法会被调用。三个方法分别是文本发送改变的不同时候调用的,在文本中获取了搜索框的信息,进行了处理,当有信息时进行搜索,若没有信息则将背景展示出来,将RecyclerView设置为不可见,即隐藏搜索结果列表。清空地点列表,确保不显示之前的搜索结果。adapter.notifyDataSetChanged();
通知适配器数据发生了变化,需要刷新显示。这样做是为了清空之前的搜索结果,确保界面显示正确。
viewModel.getPlaceLiveData().observe和之前的ViewModel有关,viewModel.getPlaceLiveData()返回的是一个 LiveData<List> 就是ViewModel中的方法,当数据发生改变时,就将会触发 Transformations.switchMap
的回调函数,他就会然后调用 Repository.searchPlaces(query)
方法进行搜索。如果搜索成功,searchPlaces(query)
会返回一个包含地点信息的 LiveData<List<Place>>
对象,这个对象会被设置为 placeLiveData
的值,从而引起 placeLiveData
的变化。一旦 placeLiveData
发生变化,与之关联的观察者就会被触发执行。在 onChanged
方法中,会根据返回的地点信息更新界面的显示状态。
而观察者内的操作就比较简单了,隐藏背景,将 RecyclerView
设置为可见状态,使其在界面上可见。清空 viewModel
中的地点列表。将从搜索结果中获取到的地点列表添加到 viewModel
中的地点列表中。通知适配器数据集发生了变化,以便更新界面显示。
到这里Fragment就写完了,但是碎片是不能直接显示在界面上的,因此需要将他和ACtivity绑定,
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/placeFragement"
android:name="ui.place.PlaceFragment"
/>
</FrameLayout>
修改styles,设置网络申请权限,
<style name="Theme.Weather" parent="Theme.AppCompat.Light.NoActionBar">
这个是在res/values/theme修改的,目的是取消它自带的ActionBar
<uses-permission android:name="android.permission.INTERNET" />
添加网络申请的权限5
到这里就基本完成了搜索城市的功能,现在开始显示天气的部分
2.显示天气(包含切换城市的部分)
1.逻辑层代码
他的逻辑层和前面的基本差不多,都是,先定义数据模型,然后使用Retrofit调用API,然后在仓库层进行封装,
这里我书上有部分不同书上子导入了现在的天气信息,未来几天的天气,生活建议,我额外添加了未来几个小时的天气信息和空气信息,这里先设计数据模型
数据模型的设计
现在的天气信息
package logic.model;
import java.util.List;
public class NewResponse {
private String code;
private New now;
public NewResponse(String code, New now) {
this.code = code;
this.now = now;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public New getNow() {
return now;
}
public void setNow(New now) {
this.now = now;
}
}
package logic.model;
public class New {
private String obsTime; //数据观测时间
private String temp; //温度,默认单位:摄氏度
private String feelsLike; //体感温度,默认单位:摄氏度
private String icon; //天气状况的图标代码,另请参考天气图标项目
private String text; //天气状况的文字描述,包括阴晴雨雪等天气状态的描述
private String wind360; //风向360角度
private String windDir; //风向
private String windScale; //风力等级
private String windSpeed; //风速,公里/小时
private String humidity; //相对湿度,百分比数值
private String precip; //当前小时累计降水量,默认单位:毫米
private String pressure; //大气压强,默认单位:百帕
private String vis; //能见度,默认单位:公里
private String cloud; //云量,百分比数值。可能为空
private String dew; //露点温度。可能为空
public New(String obsTime, String temp, String feelsLike, String icon, String text, String wind360, String windDir, String windScale, String windSpeed, String humidity, String precip, String pressure, String vis, String cloud, String dew) {
this.obsTime = obsTime;
this.temp = temp;
this.feelsLike = feelsLike;
this.icon = icon;
this.text = text;
this.wind360 = wind360;
this.windDir = windDir;
this.windScale = windScale;
this.windSpeed = windSpeed;
this.humidity = humidity;
this.precip = precip;
this.pressure = pressure;
this.vis = vis;
this.cloud = cloud;
this.dew = dew;
}
public String getObsTime() {
return obsTime;
}
public void setObsTime(String obsTime) {
this.obsTime = obsTime;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
public String getFeelsLike() {
return feelsLike;
}
public void setFeelsLike(String feelsLike) {
this.feelsLike = feelsLike;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getWind360() {
return wind360;
}
public void setWind360(String wind360) {
this.wind360 = wind360;
}
public String getWindDir() {
return windDir;
}
public void setWindDir(String windDir) {
this.windDir = windDir;
}
public String getWindScale() {
return windScale;
}
public void setWindScale(String windScale) {
this.windScale = windScale;
}
public String getWindSpeed() {
return windSpeed;
}
public void setWindSpeed(String windSpeed) {
this.windSpeed = windSpeed;
}
public String getHumidity() {
return humidity;
}
public void setHumidity(String humidity) {
this.humidity = humidity;
}
public String getPrecip() {
return precip;
}
public void setPrecip(String precip) {
this.precip = precip;
}
public String getPressure() {
return pressure;
}
public void setPressure(String pressure) {
this.pressure = pressure;
}
public String getVis() {
return vis;
}
public void setVis(String vis) {
this.vis = vis;
}
public String getCloud() {
return cloud;
}
public void setCloud(String cloud) {
this.cloud = cloud;
}
public String getDew() {
return dew;
}
public void setDew(String dew) {
this.dew = dew;
}
}
未来24小时的天气信息
package logic.model;
import java.util.List;
public class TimeResponse {
private String code;
private List<Time> hourly;
public TimeResponse(String code, List<Time> hourly) {
this.code = code;
this.hourly = hourly;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public List<Time> getHourly() {
return hourly;
}
public void setHourly(List<Time> hourly) {
this.hourly = hourly;
}
}
package logic.model;
public class Time {
private String fxTime; //预报时间
private String temp; //温度,默认单位:摄氏度
private String icon; //天气状况的图标代码,另请参考天气图标项目
private String text;
private String wind360;
private String windDir;
private String windScale;
private String windSpeed;
private String humidity;
private String precip;
private String pop;
private String pressure;
private String cloud;
private String dew;
public Time(String fxTime, String temp, String icon, String text, String wind360, String windDir, String windScale, String windSpeed, String humidity, String precip, String pop, String pressure, String cloud, String dew) {
this.fxTime = fxTime;
this.temp = temp;
this.icon = icon;
this.text = text;
this.wind360 = wind360;
this.windDir = windDir;
this.windScale = windScale;
this.windSpeed = windSpeed;
this.humidity = humidity;
this.precip = precip;
this.pop = pop;
this.pressure = pressure;
this.cloud = cloud;
this.dew = dew;
}
public String getFxTime() {
return fxTime;
}
public void setFxTime(String fxTime) {
this.fxTime = fxTime;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getWind360() {
return wind360;
}
public void setWind360(String wind360) {
this.wind360 = wind360;
}
public String getWindDir() {
return windDir;
}
public void setWindDir(String windDir) {
this.windDir = windDir;
}
public String getWindScale() {
return windScale;
}
public void setWindScale(String windScale) {
this.windScale = windScale;
}
public String getWindSpeed() {
return windSpeed;
}
public void setWindSpeed(String windSpeed) {
this.windSpeed = windSpeed;
}
public String getHumidity() {
return humidity;
}
public void setHumidity(String humidity) {
this.humidity = humidity;
}
public String getPrecip() {
return precip;
}
public void setPrecip(String precip) {
this.precip = precip;
}
public String getPop() {
return pop;
}
public void setPop(String pop) {
this.pop = pop;
}
public String getPressure() {
return pressure;
}
public void setPressure(String pressure) {
this.pressure = pressure;
}
public String getCloud() {
return cloud;
}
public void setCloud(String cloud) {
this.cloud = cloud;
}
public String getDew() {
return dew;
}
public void setDew(String dew) {
this.dew = dew;
}
}
未来几天的天气信息
package logic.model;
import java.util.List;
public class somedailyresponse {
private String code;
private List<Somedaily> daily;
public somedailyresponse(String code, List<Somedaily> daily) {
this.code = code;
this.daily = daily;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public List<Somedaily> getDaily() {
return daily;
}
public void setDaily(List<Somedaily> daily) {
this.daily = daily;
}
}
package logic.model;
public class Somedaily {
private String fxDate; //预报日期
private String sunrise; //日出时间,在高纬度地区可能为空
private String sunset; //日落时间,在高纬度地区可能为空
private String moonrise; //当天月升时间,可能为空
private String moonset ; //当天月落时间,可能为空
private String moonPhase; // 月相名称
private String moonPhaseIcon; // 月相图标代码,另请参考天气图标项目
private String tempMax; //预报当天最高温度
private String tempMin; //预报当天最高温度
private String iconDay; // 预报白天天气状况的图标代码,另请参考天气图标项目
private String textDay; // 预报白天天气状况文字描述,包括阴晴雨雪等天气状态的描述
private String iconNight; // 预报夜间天气状况的图标代码,另请参考天气图标项目
private String textNight; //预报晚间天气状况文字描述,包括阴晴雨雪等天气状态的描述
private String wind360Day; // 预报白天风向360角度
private String windDirDay; // 预报白天风向
private String windScaleDay; // 预报白天风速,公里/小时
private String wind360Night; //预报夜间风向360角度
private String windDirNight; // 预报夜间当天风向
private String windScaleNight ; //预报夜间风力等级
private String windSpeedNight; // 预报夜间风速,公里/小时
private String precip; // 预报当天总降水量,默认单位:毫米
private String uvIndex; //紫外线强度指数
private String humidity; //相对湿度,百分比数值
private String pressure; // 大气压强,默认单位:百帕
private String vis; // 能见度,默认单位:公里
private String cloud; // 云量,百分比数值。可能为空
public Somedaily(String fxDate, String sunrise, String sunset, String moonrise, String moonset, String moonPhase, String moonPhaseIcon, String tempMax, String tempMin, String iconDay, String textDay, String iconNight, String textNight, String wind360Day, String windDirDay, String windScaleDay, String wind360Night, String windDirNight, String windScaleNight, String windSpeedNight, String precip, String uvIndex, String humidity, String pressure, String vis, String cloud) {
this.fxDate = fxDate;
this.sunrise = sunrise;
this.sunset = sunset;
this.moonrise = moonrise;
this.moonset = moonset;
this.moonPhase = moonPhase;
this.moonPhaseIcon = moonPhaseIcon;
this.tempMax = tempMax;
this.tempMin = tempMin;
this.iconDay = iconDay;
this.textDay = textDay;
this.iconNight = iconNight;
this.textNight = textNight;
this.wind360Day = wind360Day;
this.windDirDay = windDirDay;
this.windScaleDay = windScaleDay;
this.wind360Night = wind360Night;
this.windDirNight = windDirNight;
this.windScaleNight = windScaleNight;
this.windSpeedNight = windSpeedNight;
this.precip = precip;
this.uvIndex = uvIndex;
this.humidity = humidity;
this.pressure = pressure;
this.vis = vis;
this.cloud = cloud;
}
public String getFxDate() {
return fxDate;
}
public void setFxDate(String fxDate) {
this.fxDate = fxDate;
}
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 String getMoonPhase() {
return moonPhase;
}
public void setMoonPhase(String moonPhase) {
this.moonPhase = moonPhase;
}
public String getMoonPhaseIcon() {
return moonPhaseIcon;
}
public void setMoonPhaseIcon(String moonPhaseIcon) {
this.moonPhaseIcon = moonPhaseIcon;
}
public String getTempMax() {
return tempMax;
}
public void setTempMax(String tempMax) {
this.tempMax = tempMax;
}
public String getTempMin() {
return tempMin;
}
public void setTempMin(String tempMin) {
this.tempMin = tempMin;
}
public String getIconDay() {
return iconDay;
}
public void setIconDay(String iconDay) {
this.iconDay = iconDay;
}
public String getTextDay() {
return textDay;
}
public void setTextDay(String textDay) {
this.textDay = textDay;
}
public String getIconNight() {
return iconNight;
}
public void setIconNight(String iconNight) {
this.iconNight = iconNight;
}
public String getTextNight() {
return textNight;
}
public void setTextNight(String textNight) {
this.textNight = textNight;
}
public String getWind360Day() {
return wind360Day;
}
public void setWind360Day(String wind360Day) {
this.wind360Day = wind360Day;
}
public String getWindDirDay() {
return windDirDay;
}
public void setWindDirDay(String windDirDay) {
this.windDirDay = windDirDay;
}
public String getWindScaleDay() {
return windScaleDay;
}
public void setWindScaleDay(String windScaleDay) {
this.windScaleDay = windScaleDay;
}
public String getWind360Night() {
return wind360Night;
}
public void setWind360Night(String wind360Night) {
this.wind360Night = wind360Night;
}
public String getWindDirNight() {
return windDirNight;
}
public void setWindDirNight(String windDirNight) {
this.windDirNight = windDirNight;
}
public String getWindScaleNight() {
return windScaleNight;
}
public void setWindScaleNight(String windScaleNight) {
this.windScaleNight = windScaleNight;
}
public String getWindSpeedNight() {
return windSpeedNight;
}
public void setWindSpeedNight(String windSpeedNight) {
this.windSpeedNight = windSpeedNight;
}
public String getPrecip() {
return precip;
}
public void setPrecip(String precip) {
this.precip = precip;
}
public String getUvIndex() {
return uvIndex;
}
public void setUvIndex(String uvIndex) {
this.uvIndex = uvIndex;
}
public String getHumidity() {
return humidity;
}
public void setHumidity(String humidity) {
this.humidity = humidity;
}
public String getPressure() {
return pressure;
}
public void setPressure(String pressure) {
this.pressure = pressure;
}
public String getVis() {
return vis;
}
public void setVis(String vis) {
this.vis = vis;
}
public String getCloud() {
return cloud;
}
public void setCloud(String cloud) {
this.cloud = cloud;
}
}
空气信息
空气信息的API数据模型和之前的有些不同所有这里设计了三个类,可以自行查看开发文档
package logic.model;
import java.util.List;
public class AirResponse {
private String code;
private Air now;
private List<Airlocal> station;
public AirResponse(String code, Air now, List<Airlocal> station) {
this.code = code;
this.now = now;
this.station = station;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Air getNow() {
return now;
}
public void setNow(Air now) {
this.now = now;
}
public List<Airlocal> getStation() {
return station;
}
public void setStation(List<Airlocal> station) {
this.station = station;
}
}
package logic.model;
import java.util.List;
public class Airlocal {
private String pubTime;
private String aqi;
private String level;
private String category;
private String primary;
private String pm10;
private String pm2p5;
private String no2;
private String so2;
private String co;
private String o3;
public Airlocal(String pubTime, String aqi, String level, String category, String primary, String pm10, String pm2p5, String no2, String so2, String co, String o3) {
this.pubTime = pubTime;
this.aqi = aqi;
this.level = level;
this.category = category;
this.primary = primary;
this.pm10 = pm10;
this.pm2p5 = pm2p5;
this.no2 = no2;
this.so2 = so2;
this.co = co;
this.o3 = o3;
}
public String getPubTime() {
return pubTime;
}
public void setPubTime(String pubTime) {
this.pubTime = pubTime;
}
public String getAqi() {
return aqi;
}
public void setAqi(String aqi) {
this.aqi = aqi;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getPrimary() {
return primary;
}
public void setPrimary(String primary) {
this.primary = primary;
}
public String getPm10() {
return pm10;
}
public void setPm10(String pm10) {
this.pm10 = pm10;
}
public String getPm2p5() {
return pm2p5;
}
public void setPm2p5(String pm2p5) {
this.pm2p5 = pm2p5;
}
public String getNo2() {
return no2;
}
public void setNo2(String no2) {
this.no2 = no2;
}
public String getSo2() {
return so2;
}
public void setSo2(String so2) {
this.so2 = so2;
}
public String getCo() {
return co;
}
public void setCo(String co) {
this.co = co;
}
public String getO3() {
return o3;
}
public void setO3(String o3) {
this.o3 = o3;
}
}
package logic.model;
import java.util.List;
public class Air {
private String pubTime;
private String aqi;
private String level;
private String category;
private String primary;
private String pm10;
private String pm2p5;
private String no2;
private String so2;
private String co;
private String o3;
public Air(String pubTime, String aqi, String level, String category, String primary, String pm10, String pm2p5, String no2, String so2, String co, String o3) {
this.pubTime = pubTime;
this.aqi = aqi;
this.level = level;
this.category = category;
this.primary = primary;
this.pm10 = pm10;
this.pm2p5 = pm2p5;
this.no2 = no2;
this.so2 = so2;
this.co = co;
this.o3 = o3;
}
public String getPubTime() {
return pubTime;
}
public void setPubTime(String pubTime) {
this.pubTime = pubTime;
}
public String getAqi() {
return aqi;
}
public void setAqi(String aqi) {
this.aqi = aqi;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getPrimary() {
return primary;
}
public void setPrimary(String primary) {
this.primary = primary;
}
public String getPm10() {
return pm10;
}
public void setPm10(String pm10) {
this.pm10 = pm10;
}
public String getPm2p5() {
return pm2p5;
}
public void setPm2p5(String pm2p5) {
this.pm2p5 = pm2p5;
}
public String getNo2() {
return no2;
}
public void setNo2(String no2) {
this.no2 = no2;
}
public String getSo2() {
return so2;
}
public void setSo2(String so2) {
this.so2 = so2;
}
public String getCo() {
return co;
}
public void setCo(String co) {
this.co = co;
}
public String getO3() {
return o3;
}
public void setO3(String o3) {
this.o3 = o3;
}
}
生活助手信息
package logic.model;
import java.util.List;
public class LifeResponse {
private String code;
private List<Life> daily;
public LifeResponse(String code, List<Life> daily) {
this.code = code;
this.daily = daily;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public List<Life> getDaily() {
return daily;
}
public void setDaily(List<Life> daily) {
this.daily = daily;
}
}
package logic.model;
public class Life {
private String date;
private String type;
private String name;
private String level;
private String category;
private String text;
public Life(String date, String type, String name, String level, String category, String text) {
this.date = date;
this.type = type;
this.name = name;
this.level = level;
this.category = category;
this.text = text;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
到这里所有的数据模型都已经完毕了,未来方便信息的处理,我在这里吧所有要在显示天气界面展示的信息打包到了一个类中方便信息的处理
package logic.model;
import java.util.List;
public class Weather {
public New newdaily;
public List<Somedaily> somedaily;
public List<Life> life;
public List<Time> time;
public Air air;
public Weather(New newdaily, List<Somedaily> somedaily, List<Life> life, List<Time> time, Air air) {
this.newdaily = newdaily;
this.somedaily = somedaily;
this.life = life;
this.time = time;
this.air = air;
}
public New getNewdaily() {
return newdaily;
}
public void setNewdaily(New newdaily) {
this.newdaily = newdaily;
}
public List<Somedaily> getSomedaily() {
return somedaily;
}
public void setSomedaily(List<Somedaily> somedaily) {
this.somedaily = somedaily;
}
public List<Life> getLife() {
return life;
}
public void setLife(List<Life> life) {
this.life = life;
}
public List<Time> getTime() {
return time;
}
public void setTime(List<Time> time) {
this.time = time;
}
public Air getAir() {
return air;
}
public void setAir(Air air) {
this.air = air;
}
}
到这里数据模型的处理就全部完成了,开始网络层的处理
网络层处理
这里大部分和前面对城市信息的获取差不多,首先创建一个类WeaherService来写API的Retrofit接口,
package ui.network;
import logic.model.AirResponse;
import logic.model.NewResponse;
import logic.model.LifeResponse;
import logic.model.TimeResponse;
import logic.model.somedailyresponse;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface WeatherService {
@GET("v7/weather/now?")
Call<NewResponse> getNewdailyweather(
@Query("location") String location, // 必选参数:需要查询地区的名称
@Query("key") String apiKey // 必选参数:用户认证key
);
@GET("v7/weather/7d?")
Call<somedailyresponse> getsomedailyweather(
@Query("location") String location, // 必选参数:需要查询地区的名称
@Query("key") String apiKey // 必选参数:用户认证key
);
@GET("v7/indices/1d?")
Call<LifeResponse> getdailylife(
@Query("location") String location, // 必选参数:需要查询地区的名称
@Query("key") String apiKey,
@Query("type") String type
);
@GET("v7/weather/24h?")
Call<TimeResponse> gettime(
@Query("location") String location, // 必选参数:需要查询地区的名称
@Query("key") String apiKey
);
@GET("v7/air/now?")
Call<AirResponse> getAir(
@Query("location") String location, // 必选参数:需要查询地区的名称
@Query("key") String apiKey
);
}
然后在就是在ServiceCreate中创建各自的Retrofit实例,这部分代码我在前面给出过了(搜索城市数据—逻辑层代码—获取Retrofit实例),就不重复给出了
然后就在SunnyWearherNetwork对这些接口进行封装,代码和前面基本差不多只是改变了参数和泛型,和上面一样在之前给出过了
到这里网络层就基本完了下面开始处理仓库层的代码
仓库层
仓库层的代码前面给出了这里分析一下这个方法
public static LiveData<Weather> refreshWeather(String id) {
MutableLiveData<Weather> liveData = new MutableLiveData<>();
new Thread(() -> {
NewResponse newdailyweather = SunnyWeatherNetwork.getNewdailyweather(id);
somedailyresponse somedailyresponse = SunnyWeatherNetwork.getsomedailyweather(id);
LifeResponse lifeResponse = SunnyWeatherNetwork.getdailylife(id, "0");
TimeResponse timeResponse = SunnyWeatherNetwork.gettime(id);
AirResponse airResponse = SunnyWeatherNetwork.getAir(id);
if(newdailyweather != null&&somedailyresponse!=null&&lifeResponse!=null&&timeResponse!=null)
{
if (newdailyweather.getCode().equals("200") && somedailyresponse.getCode().equals("200")&&lifeResponse.getCode().equals("200")) {
Weather weather = new Weather(newdailyweather.getNow(),somedailyresponse.getDaily(),lifeResponse.getDaily(),timeResponse.getHourly(),airResponse.getNow());
liveData.postValue(weather);
} else {
liveData.postValue(null); // 或者传递一个默认值或错误信息
}
}
}).start();
return liveData;
}
这个函数其实没有什么难以理解的点,将需要的各个信息获取,封装到weather中,然后将其封装给一个LiveData对象方便后续回调处理
ViewMOdel
package ui.weather;
import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import logic.model.Weather;
import ui.network.Repository;
public class weatherViewModel extends ViewModel {
public MutableLiveData<String> locationLiveData = new MutableLiveData<>();
public String id;
public LiveData<Weather> weatherLiveData = Transformations.switchMap(locationLiveData, id -> {
return Repository.refreshWeather(id);
});
public void refreshWeather(String id) {
this.id = id;
locationLiveData.postValue(id);
}
这个和之前的也基本一样,而且比前面的看起来简单一点,这里一旦locationLiveData发生改变,就会引起weatherLiveData的改变,他的观察者中就可以进行后续逻辑
到了这里逻辑层就基本结束了,也没有什么特殊的点下面开始UI层的处理
2.UI层代码
首先创建一个ACtivity用于展示显示天气信息,创建一个活动这里我叫做weatherActivity,并且指定他的布局activity_weather,这里为了让他看起来整洁方便处理修改,采用引用布局技术,将不同的部分写在不同的布局中然后将布局引入到主布局中,为了方便理解,我在布局中加上了他们各自展示那个功能信息的注释,最后几个重复的我将展示其中某一个部分的注释
主布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout"
android:orientation="vertical"
android:layout_width="match_parent"
tools:context=".MainActivity"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/LinearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/weatherLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:scrollbars="none"
tools:context="ui.weather.WeatherActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/now" />//展示实时信息,比如温度
<include layout="@layout/time"/>//展示未来24小时的信息,温度与天气状况
<include layout="@layout/forecast" />//和上面差不多,但是他展示的是未来几天的信息
<include layout="@layout/pm"/>//空气相关信息
<include layout="@layout/life_item"/>//生活助手信息
</LinearLayout>
</ScrollView>
</LinearLayout>
<LinearLayout//设置一个侧滑菜单展示搜索城市部分,这里使用到了之前的碎片
android:layout_width="300dp"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:layout_gravity="start"
android:background="@color/white">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="ui.place.PlaceFragment"
android:id="@+id/placeFragement2"
android:layout_marginTop="25dp"/>
</LinearLayout>
</androidx.drawerlayout.widget.DrawerLayout>
这里的UI最外面使用了一个DrawerLayout包裹,,他为这个布局设置了一个侧滑菜单来用于展示搜索城市的一个界面,在侧滑菜单中展示了内个碎片的内容。主要的视图中使用了一个ScrollView,他的作用是为超出屏幕的视图提供支持,他默认支持垂直滑动。在这个里面就主要引用几个主要部分的视图,选择开始一个个展示视图
now 视图(现在天气信息)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="530dp"
android:id="@+id/newlayout"
android:background="@drawable/background"
android:orientation="vertical">
<FrameLayout
android:id="@+id/newtitlelayout"
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="@+id/home"//设置一个用于打开搜索页面的控件
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="45dp"
android:layout_marginStart="15dp"
android:background="@drawable/sousuo"
/>
<TextView
android:id="@+id/placename"//一个用于展示当前页面城市名字的控件
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_gravity="center_horizontal"
android:ellipsize="middle"
android:singleLine="true"
android:textColor="#fff"
android:textSize="22sp" />
<ImageView
android:id="@+id/tianjia"//设置一个用于打开储存页面(储存已经打开过的城市)的控件
android:layout_width="32dp"
android:layout_height="23dp"
android:layout_gravity="end"
android:layout_marginTop="45dp"
android:layout_marginEnd="15dp"
android:background="@drawable/jia" />
</FrameLayout>
<LinearLayout
android:id="@+id/bodyLayout"//在这里主要展示了当前信息
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
<TextView
android:id="@+id/currentTemp"//当前温度
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="#fff"
android:textSize="70sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp">
<TextView
android:id="@+id/feelsLike"//当前体感温度
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"//用来分隔开俩个数据
android:layout_height="wrap_content"
android:layout_marginStart="13dp"
android:text="l"
android:textColor="#fff"
android:textSize="18sp" />
<TextView
android:id="@+id/weathertext"//用来展示当前天气状态如晴 , 雨
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="13dp"
android:textColor="#fff"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
并没有什么难以理解的地方,这里使用RelativeLayout是为了方便条件位置
Time(未来24小时的布局)
这里使用了RelativeLayout布局方便横向滑动,所以他这里分成了俩个布局,适配器和使用在后面会进行详细处理,这里先给出布局
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="15dp"
app:cardCornerRadius="4dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/timeLayout"
android:layout_width="wrap_content"
android:orientation="horizontal"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
</com.google.android.material.card.MaterialCardView>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="70dp"
android:layout_height="130dp"
android:orientation="vertical">
<TextView
android:id="@+id/timeInfo"//该子项对应的时间
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center_horizontal|center_vertical" />
<TextView
android:id="@+id/iconIcon"//用来展示天气状态的图标
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center_horizontal|center_vertical" />
<TextView
android:id="@+id/skyInfo"//天气状态
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center_horizontal|center_vertical" />
<TextView
android:id="@+id/tempInfo"//该子项对应的温度
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:layout_gravity="center_horizontal|center_vertical"
android:gravity="center" />
</LinearLayout>
这里没有什么特别,布局注意设置一下方向就不会出现什么问题
forecast(未来几天的数据)
这里没有使用RecycleView来展示信息,而是使用一种布局加载的方法来展示,详细会在后面。活动中使用的时候详细介绍,这里先介绍他也有俩个布局
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="15dp"
app:cardElevation="4dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:text="预报"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"/>
<LinearLayout
android:id="@+id/forecastLayout"//后面会把下面的子项布局填充到这个布局中
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp">
<TextView
android:id="@+id/dateInfo"//时间
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="4" />
<TextView
android:id="@+id/skyIcon"//天气状态图标
android:layout_width="20dp"
android:layout_height="20dp"
/>
<TextView
android:id="@+id/skyInfo"//天气状态
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="3"
android:gravity="center" />
<TextView
android:id="@+id/temperatureInfo"//温度
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="3"
android:gravity="end" />
</LinearLayout>
pm(空气质量)
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="15dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:id="@+id/kqzk"
android:layout_width="150dp"
android:layout_height="110dp"
android:layout_marginStart="200dp"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity = "center_horizontal"
android:layout_marginTop="10dp"
android:text="空气质量"
android:textSize="20sp" />
<TextView
android:id="@+id/kqText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity = "center"
android:layout_marginTop="25dp"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/pm"
android:layout_width="150dp"
android:layout_height="50dp"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="pm2.5"
android:textSize="17sp" />
<TextView
android:id="@+id/pmText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_gravity="center_horizontal"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/tx"
android:layout_width="150dp"
android:layout_height="50dp"
android:orientation="vertical"
android:layout_marginTop="60dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:text="主要污染物"
android:textSize="17sp" />
<TextView
android:id="@+id/txText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="4dp"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
life(生活助手信息)
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="15dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="20dp"
android:text="生活指数"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"/>
<LinearLayout//这里使用俩个将布局分为了上下俩部分
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp">
<RelativeLayout//一个代表一个小部分
android:id="@+id/life1"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1">
<ImageView
android:id="@+id/coldRiskImg"//展示当前部分的图片
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:src="@drawable/ganmao"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/coldRiskImg"
android:layout_marginStart="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="感冒"/>
<TextView
android:id="@+id/coldRiskText"//是否适宜
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/life2"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1">
<ImageView
android:id="@+id/diaouImg"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:src="@drawable/diaoyu"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/diaouImg"
android:layout_marginStart="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="钓鱼"/>
<TextView
android:id="@+id/diaoyuText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/life3"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1">
<ImageView
android:id="@+id/dressingImg"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:src="@drawable/shirt"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/dressingImg"
android:layout_marginStart="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="穿衣"/>
<TextView
android:id="@+id/dressingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
<RelativeLayout
android:id="@+id/life4"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1">
<ImageView
android:id="@+id/ultravioletImg"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:src="@drawable/xiche"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_toEndOf="@id/ultravioletImg"
android:layout_marginStart="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="洗车"/>
<TextView
android:id="@+id/ultravioletText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/life5"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1">
<ImageView
android:id="@+id/taiyangjingImg"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:src="@drawable/fs"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/taiyangjingImg"
android:layout_marginStart="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="防晒"/>
<TextView
android:id="@+id/fsText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/life6"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1">
<ImageView
android:id="@+id/carWashingImg"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:src="@drawable/yundun"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_toEndOf="@id/carWashingImg"
android:layout_marginStart="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="运动"/>
<TextView
android:id="@+id/carWashingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>>
</com.google.android.material.card.MaterialCardView>
这里我展示一下最后基本效果方便理解
到这里他基本就写的不多了,就剩下在WeatherActivity中请求并展示天气数据了,这也是比较麻烦,功能比较多的一个部分了,先展示代码,然后进行讲解
WeatherActivity(主活动获取并展示信息)
public class WeatherActivity extends AppCompatActivity {
public weatherViewModel viewModel;
public DrawerLayout drawerLayout;//这个活动的主要展示布局,一个抽屉布局
public BottomSheetDialog bottomSheetDialog;//底层抽屉
public static SumDao database;//数据库
// private SwipeRefreshLayout swipeRefreshLayout ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);//引入该活动布局
ImageView homeButton = findViewById(R.id.home);//引入展示搜索的控件
drawerLayout = findViewById(R.id.drawerLayout);//引入布局
//数据库
database= SumDatabase.getSumDatabase(this).sumDao();
//搜索按钮相关内容
homeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
drawerLayout.openDrawer(GravityCompat.START);
}
});
drawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
}
@Override
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
}
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
}
@Override
public void onDrawerClosed(View drawerView) {
InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
manager.hideSoftInputFromWindow(drawerView.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
super.onDrawerClosed(drawerView);
}
});
// 设置状态为透明
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
getWindow().setStatusBarColor(Color.TRANSPARENT);
// setContentView(R.layout.activity_weather);
//1
viewModel = new ViewModelProvider(this).get(weatherViewModel.class);
if (viewModel.id == null)//00
{
String id = getIntent().getStringExtra("id");
if (id == null) {
viewModel.id = " ";
} else {
viewModel.id = id;
}
}
viewModel.weatherLiveData.observe(this, weather -> {
if (weather != null) {
String name = getIntent().getStringExtra("name");
String id = getIntent().getStringExtra( "id");
Sum sum = new Sum(weather,name,id);
new Thread(new Runnable() {//开辟一个新线程
@Override
public void run() {
if(database.getSumByPlaceId(id)==null)
{
database.insert(sum);//添加数据库数据
Log.d("TAGGG","数据库写入");
}
}
}).start();
showWeatherInfo(weather,name);
} else {
Toast.makeText(WeatherActivity.this, "可能今天使用次数用完", Toast.LENGTH_SHORT).show();
}
});
String ttt = getIntent().getStringExtra("TTT");
//处理打开的视图是搜索还是上次还是报储存
new Thread(new Runnable() {//开辟一个新线程
@Override
public void run() {
Sum latestSum =database.getLatestSum();
if (latestSum != null&&ttt.equals("0")) {//保存上一次退出
runOnUiThread(new Runnable() {//保证在主线程中处理UI
@Override
public void run() {
showWeatherInfo(latestSum.getWeather(), latestSum.getPlace());
}
});
}else if(latestSum != null&&ttt.equals("2"))//在保存框中点击跳转
{
runOnUiThread(new Runnable() {
@Override
public void run() {
showWeatherInfo(SunnyWeatherApplication.summm1.getWeather(), SunnyWeatherApplication.summm1.getPlace());
}
});
}else
{
viewModel.refreshWeather(viewModel.id);
}
}
}).start();
}
private void showWeatherInfo(Weather weather,String name) {
new Thread(new Runnable() {
@Override
public void run() {
SunnyWeatherApplication.summm2=database.getAllSums();
}
}).start();
//获取相关功能的变量
New aNew = weather.getNewdaily();
List<Somedaily> somedaily=weather.getSomedaily();
List<Life> life = weather.getLife();
List<Time> times= weather.getTime();
Air air = weather.getAir();
//多天的数据
RecyclerView timeRecyclerView = findViewById(R.id.timeLayout);
timeRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
TimeAdapter timeAdapter = new TimeAdapter(times,this);
timeRecyclerView.setAdapter(timeAdapter);
//生活点击事件
RelativeLayout recyclerView1 =findViewById(R.id.life1);
RelativeLayout recyclerView2 =findViewById(R.id.life2);
RelativeLayout recyclerView3 =findViewById(R.id.life3);
RelativeLayout recyclerView4 =findViewById(R.id.life4);
RelativeLayout recyclerView5 =findViewById(R.id.life5);
RelativeLayout recyclerView6 =findViewById(R.id.life6);
// BottomSheetLayout bottomSheetLayout = findViewById(R.id.bottomSheetLayout);
// TextView textViewInBottomSheet = findViewById(R.id.textViewInBottomSheet);
// 定义变量
TextView placeName = findViewById(R.id.placename);
TextView currentTemp = findViewById(R.id.currentTemp);
TextView feelsLike = findViewById(R.id.feelsLike);
TextView weathertext = findViewById(R.id.weathertext);
ImageView buttonjia = findViewById(R.id.tianjia);
LinearLayout forecastLayout = findViewById(R.id.forecastLayout);
TextView coldRiskText = findViewById(R.id.coldRiskText);
TextView dressingText =findViewById(R.id.dressingText);
TextView ultravioletText =findViewById(R.id.ultravioletText);
TextView carWashingtext = findViewById(R.id.carWashingText);
TextView diaoyu = findViewById(R.id.diaoyuText);
TextView fs = findViewById(R.id.fsText);
TextView airweight =findViewById(R.id.kqText);
TextView pm=findViewById(R.id.pmText);
TextView tx =findViewById(R.id.txText);
//开始导入现在的数据
// Log.e("TAG", );
if (placeName != null) {
placeName.setText(name);//城市名字
} else {
Log.e("TAG", "在布局中找不到placeName TextView");
}
currentTemp.setText(aNew.getTemp()+"℃");
feelsLike.setText("体感 "+aNew.getFeelsLike()+"℃");
weathertext.setText(aNew.getText());
buttonjia.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(WeatherActivity.this,baocun.class);
startActivity(intent);
}
});
//导入未来几天的数据
// 填充forecast.xml 布局中的数据
forecastLayout.removeAllViews();//清空布局确保之前这个布局是干净的
for (int i = 0; i < 7; i++) {//前面导入了7天的数据
Somedaily somedaily1= somedaily.get(i);
// 创建一个新的视图,使用 forecast_item 作为模板
View view = LayoutInflater.from(this).inflate(R.layout.forcast_item, forecastLayout, false);
// 在新视图中找到日期信息的 TextView 组件
TextView dateInfo = view.findViewById(R.id.dateInfo);
dateInfo.setText(somedaily1.getFxDate());
// 在新视图中找到天气图标的 ImageView 组件
TextView textView = view.findViewById(R.id.skyIcon);
Typeface font = Typeface.createFromAsset(getAssets(), "qweather-icons.ttf");//加载图标字体
textView.setTypeface(font);//设置textView使用图标字体。
String iconday= somedaily1.getIconDay();
if (iconday.equals("100")){
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("101")){
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("102")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("103")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("104")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("150")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("151")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("152")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("153")) {
textView.setText(Html.fromHtml(""));
} else if(iconday.equals("300")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("301")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("302")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("303")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("304")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("305")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("306")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("307")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("308")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("309")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("310")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("311")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("312")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("313")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("314")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("315")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("316")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("317")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("318")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("399")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("350")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("351")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("400")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("401")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("402")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("403")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("404")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("405")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("406")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("407")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("408")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("409")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("410")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("456")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("457")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("499")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("500")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("501")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("502")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("503")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("504")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("507")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("508")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("509")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("510")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("511")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("512")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("513")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("514")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("515")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("900")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("901")) {
textView.setText(Html.fromHtml(""));
}else{
textView.setText(Html.fromHtml(""));
}
// 在新视图中找到天气信息的 TextView 组件
TextView skyInfo = view.findViewById(R.id.skyInfo);
if(somedaily1.getTextDay().equals("晴")&&!somedaily1.getTextNight().equals("晴"))
{
skyInfo.setText(somedaily1.getTextNight());
}
else if(somedaily1.getTextNight().equals("晴")&&!somedaily1.getTextDay().equals("晴"))
{
skyInfo.setText(somedaily1.getTextDay());
}
else
{
skyInfo.setText(somedaily1.getTextDay());
}
// 在新视图中找到温度信息的 TextView 组件
TextView temperatureInfo = view.findViewById(R.id.temperatureInfo);
temperatureInfo.setText(somedaily1.getTempMin()+"℃ " + "~ " +somedaily1.getTempMax()+"℃");
// 将填充好数据的新视图添加到 forecastLayout 布局中
forecastLayout.addView(view);
}
//开始写生活助手
coldRiskText.setText(life.get(8).getCategory());
diaoyu.setText(life.get(3).getCategory());
dressingText.setText(life.get(2).getCategory());
carWashingtext.setText(life.get(1).getCategory());
fs.setText(life.get(15).getCategory());
ultravioletText.setText(life.get(0).getCategory());
// 初始化 BottomSheetDialog
bottomSheetDialog = new BottomSheetDialog(WeatherActivity.this);
bottomSheetDialog.setContentView(R.layout.bottom_sheet);
// 设置点击外部区域关闭
bottomSheetDialog.setCanceledOnTouchOutside(true);
// 可选:设置行为
View bottomSheetView = bottomSheetDialog.findViewById(R.id.bottomsheet);
TextView bottomtext=bottomSheetView.findViewById(R.id.bottom);
if (bottomSheetView != null) {
BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheetView);
behavior.setPeekHeight(500); // 设置最小高度
}
recyclerView1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomtext.setText(life.get(8).getText());
bottomSheetDialog.show();
}
});
recyclerView2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomtext.setText(life.get(3).getText());
bottomSheetDialog.show();
}
});
recyclerView3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomtext.setText(life.get(2).getText());
bottomSheetDialog.show();
}
});
recyclerView4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomtext.setText(life.get(1).getText());
bottomSheetDialog.show();
}
});
recyclerView5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomtext.setText(life.get(15).getText());
bottomSheetDialog.show();
}
});
recyclerView6.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomtext.setText(life.get(0).getText());
bottomSheetDialog.show();
}
});
//空气问题
airweight.setText(air.getCategory());
pm.setText(air.getPm2p5());
if(air.getPrimary()==null)
{
tx.setText("质量较好");
}
else {
tx.setText(air.getPrimary());
}
}
}
在这个类中整体分为俩个部分,一个是onCreate,用于实现大部分功能,一个是showWeatherInfo用于展示实现UI信息。这里onCreate中包含了一些其他功能的部分先放置,到了那些功能在进行详细讲解,先进行讲解一部分
首先各个变量的引入我写了注释,方便理解这里就不进行处理了,首先展示搜索问题。我使用注释的方法来进行解释每个部分起到,什么作用
//搜索按钮相关内容
homeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
drawerLayout.openDrawer(GravityCompat.START);//这个实现打开侧边栏
}
});
drawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {//设置一个抽屉监听器,他用于监听抽屉的打开和关闭和状态改变事件等提,供了一组默认实现,方便快速使用而不必重写所有方法。
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {//当DrawerLayout滑动时,会调用该方法
super.onDrawerSlide(drawerView, slideOffset);
}
@Override
public void onDrawerStateChanged(int newState) {//当DrawerLayout状态发生改变时,会调用该方法。可能的状态包括Dragging(正在拖动)、Idle(空闲)和Settling(定位中)等。
super.onDrawerStateChanged(newState);
}
@Override
public void onDrawerOpened(View drawerView) {//当DrawerLayout打开时,会调用该方法
super.onDrawerOpened(drawerView);
}
@Override
public void onDrawerClosed(View drawerView) {//当DrawerLayout关闭时,会调用该方法
InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
manager.hideSoftInputFromWindow(drawerView.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);//是隐藏软键盘的方法。该方法需要传入一个window token参数和一个flag参数,可以控制软键盘的显示状态。在这里使用了HIDE_NOT_ALWAYS flag,表示隐藏软键盘并且不总是显示。
super.onDrawerClosed(drawerView);
}
});
在这里并没有实现很多功能只是简单的实现了打开这个视图,和当关闭这个视图时隐藏软键盘
下面我设置状态栏为透明,这样可以好看一点(这部分是书上的内容可以深入学习一下)
// 设置状态为透明
View decorView = getWindow().getDecorView();//获取当前Activity的DecorView(装饰视图)。DecorView是Activity的顶级视图,包含了整个布局和所有控件。
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);//方法用于设置系统UI的可见性。在这里,使用了SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和SYSTEM_UI_FLAG_LAYOUT_STABLE标志,分别表示布局可以延伸到状态栏的下面,而且状态栏可以保持稳定
getWindow().setStatusBarColor(Color.TRANSPARENT);//设置状态栏的颜色。在这里,将状态栏的颜色设置为透明。
下面这个部分为了判断进入情况所以进行了一些判断
viewModel = new ViewModelProvider(this).get(weatherViewModel.class);//获取对象
if (viewModel.id == null)//因为搜索框在另一个活动中所有这里无法直接获取搜索框中内容,这里判断是为了避免空指针
{
String id = getIntent().getStringExtra("id");//利用Intent传递一个id来进行展示数据
if (id == null) {
viewModel.id = " ";
} else {
viewModel.id = id;
}
}
viewModel.weatherLiveData.observe(this, weather -> {//这里观察者的使用和之前搜索城市观察者的使用是一样的,主要解析一下方法中的内容
if (weather != null) {
String name = getIntent().getStringExtra("name");
String id = getIntent().getStringExtra( "id");
Sum sum = new Sum(weather,name,id);
new Thread(new Runnable() {//开辟一个新线程
@Override
public void run() {
if(database.getSumByPlaceId(id)==null)
{
database.insert(sum);//添加数据库数据
Log.d("TAGGG","数据库写入");
}
}
}).start();//上面大部分的功能和数据可的处理有关第一次可以先不写
showWeatherInfo(weather,name);//展示数据
} else {
Toast.makeText(WeatherActivity.this, "可能今天使用次数用完", Toast.LENGTH_SHORT).show();
}
});
String ttt = getIntent().getStringExtra("TTT");
//处理打开的视图是搜索还是上次打开还是储存
new Thread(new Runnable() {//开辟一个新线程
@Override
public void run() {
Sum latestSum =database.getLatestSum();
if (latestSum != null&&ttt.equals("0")) {//保存上一次退出
runOnUiThread(new Runnable() {//保证在主线程中处理UI
@Override
public void run() {
showWeatherInfo(latestSum.getWeather(), latestSum.getPlace());
}
});
}else if(latestSum != null&&ttt.equals("2"))//在保存框中点击跳转
{
runOnUiThread(new Runnable() {
@Override
public void run() {
showWeatherInfo(SunnyWeatherApplication.summm1.getWeather(), SunnyWeatherApplication.summm1.getPlace());
}
});
}else
{
viewModel.refreshWeather(viewModel.id);//上面的if都是为了处理不同路径打开这个页面进行的数据库操作,第一次直接写这个代码即可不需要开辟线程,这里会改变LIveData使其在观察者中打开展示功能,处理UI部分
}
}
}).start();
到这里完成了onCreate的分析下面开始showWeatherInfo的分析,在这里面大部分都是简单的数据填充,有三个点,一个是使用for循环实现多个子项的处理,一个是天气状态图像转化为图像文字进行处理(这个是别人的一篇博客,链接(https://blog.csdn.net/kim5659/article/details/122402139),最后一个是使用bottomSheetDialog一个控件来实现一个底部弹出的功能,第一个和第二个是一起使用的所以这里一起讲解
//导入未来几天的数据
// 填充forecast.xml 布局中的数据
forecastLayout.removeAllViews();//清空布局确保之前这个布局是干净的
for (int i = 0; i < 7; i++) {//前面导入了7天的数据
Somedaily somedaily1= somedaily.get(i);
// 创建一个新的视图,使用 forecast_item 作为模板
View view = LayoutInflater.from(this).inflate(R.layout.forcast_item, forecastLayout, false);
// 在新视图中找到日期信息的 TextView 组件
TextView dateInfo = view.findViewById(R.id.dateInfo);
dateInfo.setText(somedaily1.getFxDate());
// 在新视图中找到天气图标的 ImageView 组件
TextView textView = view.findViewById(R.id.skyIcon);
Typeface font = Typeface.createFromAsset(getAssets(), "qweather-icons.ttf");//加载图标字体
textView.setTypeface(font);//设置textView使用图标字体。
String iconday= somedaily1.getIconDay();
if (iconday.equals("100")){
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("101")){
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("102")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("103")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("104")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("150")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("151")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("152")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("153")) {
textView.setText(Html.fromHtml(""));
} else if(iconday.equals("300")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("301")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("302")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("303")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("304")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("305")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("306")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("307")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("308")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("309")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("310")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("311")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("312")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("313")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("314")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("315")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("316")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("317")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("318")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("399")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("350")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("351")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("400")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("401")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("402")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("403")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("404")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("405")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("406")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("407")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("408")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("409")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("410")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("456")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("457")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("499")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("500")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("501")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("502")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("503")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("504")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("507")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("508")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("509")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("510")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("511")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("512")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("513")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("514")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("515")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("900")) {
textView.setText(Html.fromHtml(""));
}else if(iconday.equals("901")) {
textView.setText(Html.fromHtml(""));
}else{
textView.setText(Html.fromHtml(""));
}
// 在新视图中找到天气信息的 TextView 组件
TextView skyInfo = view.findViewById(R.id.skyInfo);
if(somedaily1.getTextDay().equals("晴")&&!somedaily1.getTextNight().equals("晴"))
{
skyInfo.setText(somedaily1.getTextNight());
}
else if(somedaily1.getTextNight().equals("晴")&&!somedaily1.getTextDay().equals("晴"))
{
skyInfo.setText(somedaily1.getTextDay());
}
else
{
skyInfo.setText(somedaily1.getTextDay());
}
// 在新视图中找到温度信息的 TextView 组件
TextView temperatureInfo = view.findViewById(R.id.temperatureInfo);
temperatureInfo.setText(somedaily1.getTempMin()+"℃ " + "~ " +somedaily1.getTempMax()+"℃");
// 将填充好数据的新视图添加到 forecastLayout 布局中
forecastLayout.addView(view);
}
这里的很多if是字体图标的处理,而for循环填充视图则是使用R.layout.forcast_item作为模板创建视图,然后使用findViewById()方法找到各个组件,在其对应位置填充数据。
而bottomSheetDialog的使用则是提前创建一个布局,在使用时,创建实例,引入布局,然后设置一些功能,他的调用在上面的代码中都有简单的注释可以帮助理解
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/botttomsheet"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/bottomsheet"
android:layout_width="match_parent"
android:layout_height="150dp"
android:orientation="vertical"
app:layout_behavior="@string/bottom_sheet_behavior">
<TextView
android:id="@+id/bottom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="45dp"
android:textSize="17dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
到这里基本就完成了,但是记得处理适配器点击事件(就是之前搜索城市PlaceAdapter中,使用Intent传递数据的部分),方便跳转,到这里就基本完成了,剩下的就是一些简单的事情了,负责处理持久化储存的问题了,这里主要有俩个部分,第一个是保存上次数据,避免在没有网络的情况下打开,显示不出来的问题,即打开是会显示上一次退出时的信息,所以这里需要保存上次的数据,然后第二个就是,保存搜索城市然后确定点击打开的城市信息
3.储存信息
1.保存上次打开的数据
这个还是按照书上的部分写的,使用了SharedPreferences储存,首先封装数据接口
package logic.dao;
import android.content.Context;
import android.content.SharedPreferences;
import com.example.weather.SunnyWeatherApplication;
import com.google.gson.Gson;
import logic.model.Place;
public class PlaceDao {
// 保存地点信息
public static void savePlace(Place place) {
SharedPreferences.Editor editor = sharedPreferences().edit();//通过调用sharedPreferences()方法获取SharedPreferences对象,并使用edit()方法创建一个SharedPreferences.Editor对象,用于向SharedPreferences中写入数据。
editor.putString("place", new Gson().toJson(place));//将地点对象转换为JSON字符串,并使用putString()方法将JSON字符串保存到SharedPreferences.Editor对象中,键名为"place"。
editor.apply();//将更改应用到SharedPreferences中。
}
// 获取保存的地点信息
public static Place getSavedPlace() {
String placeJson = sharedPreferences().getString("place", "");//通过调用sharedPreferences()方法获取SharedPreferences对象,并使用getString()方法从SharedPreferences中读取键名为"place"的值,如果没有找到该键,则返回空字符串。
return new Gson().fromJson(placeJson, Place.class);//使用Gson库的fromJson()方法将JSON字符串转换为地点(Place)对象并返回。
}
// 检查是否有保存的地点信息
public static boolean isPlaceSaved() {
return sharedPreferences().contains("place");//并使用contains()方法检查SharedPreferences中是否包含键名为"place"的值。
}
private static SharedPreferences sharedPreferences() {//使用了应用程序的上下文SunnyWeatherApplication.context来获取SharedPreferences对象,并指定了SharedPreferences的名称为"sunny_weather",模式为Context.MODE_PRIVATE。这意味着,只有本应用程序才能够访问和修改这个SharedPreferences对象,其他应用程序无法访问它,保证了数据的安全性和隐私性。
return SunnyWeatherApplication.context.getSharedPreferences("sunny_weather", Context.MODE_PRIVATE);
}
}
然后在仓库层(Repository)进行实现
// 保存地点信息
public static void savePlace(Place place) {
PlaceDao.savePlace(place);
}
// 获取保存的地点信息
public static Place getSavedPlace() {
return PlaceDao.getSavedPlace();
}
// 检查是否有保存的地点信息
public static boolean isPlaceSaved() {
return PlaceDao.isPlaceSaved();
}
在PlaceViewModel中进行处理
// 保存地点信息
public static void savePlace(Place place) {
Repository.savePlace(place);
}
// 获取保存的地点信息
public static Place getSavedPlace() {
return Repository.getSavedPlace();
}
// 检查是否有保存的地点信息
public static boolean isPlaceSaved() {
return Repository.isPlaceSaved();
}
然后在适配器中处理
fragment.viewModel.savePlace(place);
最后在碎片中处理
if (viewModel.isPlaceSaved()&&getActivity() instanceof MainActivity) {//数据储存
Place place = viewModel.getSavedPlace();
Intent intent = new Intent(context, WeatherActivity.class);
intent.putExtra("name", place.getName());
intent.putExtra("TTT", "0");
intent.putExtra("id", place.getId());
startActivity(intent);
if (getActivity() != null) {
getActivity().finish();
}
return;
这些在之前的代码中都出现过这里在提出,使其逻辑更连贯
2.保存储存城市信息
这里使用了Room来进行数据的保存,首先进行数据模型的设置,这里我有些问题,他其实可以只保存一个地址,然后天气信息实时刷新的,但是我这里把天气信息也一起保存了.
package database;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import logic.model.Place;
import logic.model.Weather;
@Entity
public class Sum {
private Weather weather;
private String place;
private String placeid;
@PrimaryKey(autoGenerate = true)
private long id;
public Sum(Weather weather, String place, String placeid) {
this.weather = weather;
this.place = place;
this.placeid = placeid;
this.id = id;
}
public Weather getWeather() {
return weather;
}
public void setWeather(Weather weather) {
this.weather = weather;
}
public String getPlace() {
return place;
}
public void setPlace(String place) {
this.place = place;
}
public String getPlaceid() {
return placeid;
}
public void setPlaceid(String placeid) {
this.placeid = placeid;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
这里没什么可说的,这里的Weather是一种复杂数据类型所以需要使用Gson 进行数据处理
package database;
import androidx.room.TypeConverter;
import com.google.gson.Gson;
import logic.model.Weather;
public class WeatherConverter {
@TypeConverter
public static String fromWeather(Weather weather) {
// 使用 Gson 将 Weather 对象转换成 JSON 字符串
return new Gson().toJson(weather);
}
@TypeConverter
public static Weather toWeather(String json) {
// 使用 Gson 将 JSON 字符串转换回 Weather 对象
return new Gson().fromJson(json, Weather.class);
}
}
接下来设置数据库访问对象接口
package database;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
import database.Sum;
@Dao
public interface SumDao {
@Insert
void insert(Sum sum);//插入
@Query("SELECT * FROM Sum")
List<Sum> getAllSums();//获取
@Delete
void deleteUser(Sum sum);//删除
@Query("SELECT * FROM Sum WHERE placeid = :placeId")
Sum getSumByPlaceId(String placeId);//查找
@Update
void updateSum(Sum sum);//对已有数据进行更新
@Query("SELECT * FROM Sum ORDER BY id DESC LIMIT 1")
Sum getLatestSum();//按id逆序排序,输出最后一个数据,即最新数据
}
定义一个数据库实例
package database;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.DatabaseConfiguration;
import androidx.room.InvalidationTracker;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
@Database(version = 1, entities = {Sum.class},exportSchema = false)
@TypeConverters(WeatherConverter.class)
public abstract class SumDatabase extends RoomDatabase {
public abstract SumDao sumDao();
public static SumDatabase sInstance;
public static SumDatabase getSumDatabase (Context context)
{
if(sInstance==null)
{
sInstance= Room.databaseBuilder(context.getApplicationContext(),SumDatabase.class,"weather").build();
}
return sInstance;
}
}
接下来就是数据库的使用了
在这之前我们还没有写储存页面的UI,这里还是使用了RecycleView,老样子俩个布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:text="储存城市"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/baocrecyclerView"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="90dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="12dp"
android:orientation="vertical"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="18dp"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/placeName2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:layout_gravity="center_vertical"
android:layout_marginStart="15dp"
android:textSize="20sp" />
<TextView
android:id="@+id/placetemp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginStart="15dp"
android:textColor="?android:attr/textColorSecondary"
android:textSize="25sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
适配器
package ui.weather;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.weather.R;
import com.example.weather.SunnyWeatherApplication;
import java.util.List;
import database.Sum;
public class BaocunAdapter extends RecyclerView.Adapter<BaocunAdapter.baocunViewHolder> {
private List<Sum> sums;
private Context weatherActivity;
public BaocunAdapter(List<Sum> sums, Context weatherActivity) {
this.sums = sums;
this.weatherActivity = weatherActivity;
}
public class baocunViewHolder extends RecyclerView.ViewHolder {
public TextView placename;
public TextView temp;
public baocunViewHolder(@NonNull View itemView) {
super(itemView);
placename=itemView.findViewById(R.id.placeName2);
temp = itemView.findViewById(R.id.placetemp);
}
}
@NonNull
@Override
public BaocunAdapter.baocunViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.baocun_item, parent, false);
baocunViewHolder holder = new baocunViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull BaocunAdapter.baocunViewHolder holder, int position) {
Sum sum = sums.get(position);
holder.placename.setText(sum.getPlace());
holder.temp.setText(sum.getWeather().getNewdaily().getTemp()+"℃");
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// 在这里处理长按事件
showDeleteDialog(holder.getAdapterPosition());
return true; // 返回 true 表示已处理此事件
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SunnyWeatherApplication.summm1=sum;
Intent intent = new Intent(view.getContext(), WeatherActivity.class);
intent.putExtra("name",sum.getPlace());
intent.putExtra("id", sum.getPlaceid());
intent.putExtra("TTT", "2");//说明是搜索框进行数据跳转;
weatherActivity.startActivity(intent);
}
});
}
private void showDeleteDialog(final int position) {
AlertDialog.Builder builder = new AlertDialog.Builder(weatherActivity);
builder.setTitle("删除");
builder.setMessage("确定要删除吗?");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
removeItem(position);
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
public void removeItem(int position) {
new Thread(new Runnable() {
@Override
public void run() {
WeatherActivity.database.deleteUser(sums.get(position));
SunnyWeatherApplication.summm2=WeatherActivity.database.getAllSums();
}
}).start();;
sums.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, getItemCount());
}
@Override
public int getItemCount() {
if(sums==null)
{
return 0;
}
return sums.size();
}
}
这个有俩个点需要注意一下,一个是长按删除,一个是在点击时传递信息,打开显示天气页面,转换页面这里没有什么,注意还是需要注意我这里SunnyWeatherApplication.summm1=sum;使用了一个中间变量来实现信息传递(一般需要使用Intent,但是这样比较方便),这里重点讲一下长按删除
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {//长按时,将参数传递到函数中处理
@Override
public boolean onLongClick(View v) {
// 在这里处理长按事件
showDeleteDialog(holder.getAdapterPosition());
return true; // 返回 true 表示已处理此事件
}
});
private void showDeleteDialog(final int position) {//参数代表要删除的项在列表中的位置
AlertDialog.Builder builder = new AlertDialog.Builder(weatherActivity);
builder.setTitle("删除");//设置对话框的标题为"删除
builder.setMessage("确定要删除吗?");//消息为"确定要删除吗?"。
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
removeItem(position);//对话框的确定按钮的点击监听器。当用户点击确定按钮时,会调用removeItem()方法进行删除操作
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
public void removeItem(int position) {
new Thread(new Runnable() {
@Override
public void run() {
WeatherActivity.database.deleteUser(sums.get(position));//从数据库中删除相应的数据。
SunnyWeatherApplication.summm2=WeatherActivity.database.getAllSums();//更新全局变量SunnyWeatherApplication.summm2为数据库中的最新数据
}
}).start();;
sums.remove(position);//删除适配器集合中的数据
notifyItemRemoved(position);//通知适配器有一个项被删除
notifyItemRangeChanged(position, getItemCount());//使用notifyItemRangeChanged()方法刷新被删除项之后的所有项的位置。
}
到这里就剩下显示天气页面中的添加数据库数据
viewModel.weatherLiveData.observe(this, weather -> {
if (weather != null) {
String name = getIntent().getStringExtra("name");
String id = getIntent().getStringExtra( "id");
Sum sum = new Sum(weather,name,id);
new Thread(new Runnable() {//开辟一个新线程
@Override
public void run() {
if(database.getSumByPlaceId(id)==null)
{
database.insert(sum);//添加数据库数据
Log.d("TAGGG","数据库写入");
}
}
}).start();
showWeatherInfo(weather,name);
} else {
Toast.makeText(WeatherActivity.this, "可能今天使用次数用完", Toast.LENGTH_SHORT).show();
}
});
String ttt = getIntent().getStringExtra("TTT");
//处理打开的视图是搜索还是上次还是报储存
new Thread(new Runnable() {//开辟一个新线程
@Override
public void run() {
Sum latestSum =database.getLatestSum();
if (latestSum != null&&ttt.equals("0")) {//保存上一次退出
runOnUiThread(new Runnable() {//保证在主线程中处理UI
@Override
public void run() {
showWeatherInfo(latestSum.getWeather(), latestSum.getPlace());
}
});
}else if(latestSum != null&&ttt.equals("2"))//在保存框中点击跳转
{
runOnUiThread(new Runnable() {
@Override
public void run() {
showWeatherInfo(SunnyWeatherApplication.summm1.getWeather(), SunnyWeatherApplication.summm1.getPlace());
}
});
}else
{
viewModel.refreshWeather(viewModel.id);
}
}
}).start();
加上注释并不难理解,不是吗,就这样这个项目就结束了,他可能还有一些bug ,他还可以在添加一些功能就看你自己喽,这篇不可到这里就结束了,后面还会出一些这里面相关知识的博客