作者:韩茹
公司:程序咖(北京)科技有限公司
鸿蒙巴士专栏作家
本案例用到了PageSlider,PageSliderIndicator,PageSliderProvider,service服务,json解析,网络下载等等。。
一、项目展示
首先我们先新建一个HarmonyOS的项目:
运行效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0rf1IK2R-1630899683472)(https://img.chengxuka.com/tianqiyubaoyunxing1.gif)]
二、布局文件
现在ability_main.xml中添加:
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="#E9F6FE"
ohos:left_padding="20vp"
ohos:right_padding="20vp"
ohos:top_padding="5vp"
ohos:orientation="vertical">
<Text
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="天气预报,丑是丑了点"
ohos:text_size="24fp"
ohos:text_alignment="center"
/>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:orientation="horizontal"
ohos:top_margin="10vp"
>
<TextField
ohos:id="$+id:input_city"
ohos:height="match_content"
ohos:width="200vp"
ohos:hint="请输入要查询的城市"
ohos:text_size="20fp"
ohos:padding="10vp"
ohos:background_element="#99D3D3D3"
ohos:layout_alignment="center"
/>
<Button
ohos:id="$+id:btn_query"
ohos:height="match_content"
ohos:width="100vp"
ohos:text="查 询"
ohos:text_size="20fp"
ohos:padding="10vp"
ohos:background_element="$graphic:button_bg"
ohos:layout_alignment="center"
ohos:left_margin="10vp"
/>
</DirectionalLayout>
<!-- 查询到到天气-->
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="#3CBFFE"
ohos:left_padding="20vp"
ohos:top_padding="10vp"
ohos:right_padding="20vp"
ohos:layout_alignment="center"
ohos:top_margin="10vp"
>
<Text
ohos:id="$+id:show_city"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="城市"
ohos:text_color="#FFFFFF"
ohos:text_size="30fp"
ohos:margin="10vp"
/>
<Text
ohos:height="2vp"
ohos:width="match_parent"
ohos:background_element="#99FFFFFF"
/>
<Text
ohos:id="$+id:show_date"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="日期"
ohos:text_size="24fp"
ohos:text_color="#FFFFFF"
ohos:margin="10vp"
/>
<Text
ohos:id="$+id:show_temp"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="温度"
ohos:text_size="55fp"
ohos:text_color="#FFFFFF"
ohos:left_margin="10vp"
ohos:bottom_margin="10vp"
/>
<Text
ohos:height="2vp"
ohos:width="match_parent"
ohos:background_element="#99FFFFFF"
/>
<Text
ohos:id="$+id:show_humidity"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="湿度"
ohos:text_size="20fp"
ohos:text_color="#FFFFFF"
ohos:margin="10vp"
/>
<Text
ohos:id="$+id:show_wind"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="风向"
ohos:text_size="20fp"
ohos:text_color="#FFFFFF"
ohos:left_margin="10vp"
ohos:bottom_margin="10vp"
/>
</DirectionalLayout>
</DirectionalLayout>
graphic下,button_bg.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<shape xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:shape="rectangle">
<solid
ohos:color="#D3D3D3"/>
<corners
ohos:radius="20vp"
/>
</shape>
预览效果:
三、Service服务
1、先获取布局中的组件
在MainAbilitySlice.java中,添加initComponent()方法先获取组件,并在onStart()中进行调用:
// 初始化组件
private void initComponent() {
textCity = (Text) findComponentById(ResourceTable.Id_show_city);
textDate = (Text) findComponentById(ResourceTable.Id_show_date);
textTemp = (Text) findComponentById(ResourceTable.Id_show_temp);
textHumidity = (Text) findComponentById(ResourceTable.Id_show_humidity);
textSpeed = (Text) findComponentById(ResourceTable.Id_show_wind);
inputCityTextField = (TextField) findComponentById(ResourceTable.Id_input_city);
Button btnQuery = (Button) findComponentById(ResourceTable.Id_btn_query);
}
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
initComponent();
}
2、创建Service服务
在生成的ServiceAbility中,先修改一下HiLogLabel:
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0x00201, "WeatherServiceAbility");
在MainAbilitySlice中,也添加一下HiLogLabel:
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0x00201, "MainAbilitySlice");
3、点击 按钮,连接 service
在MainAbilitySlice中,先 添加两个 成员变量:
private static final String BUNDLE_NAME = "com.example.hanruweather";
private static final String SERVICE_NAME = "WeatherServiceAbility";
添加一个方法,用于获取链接Service的Intent:
// 获取启动本地服务的Intent
private Intent getServiceIntent(String bundleName, String serviceName) {
Operation operation = new Intent.OperationBuilder().withDeviceId("")
.withBundleName(bundleName)
.withAbilityName(serviceName)
.build();
Intent intent = new Intent();
intent.setOperation(operation);
return intent;
}
在onStart()中,连接服务
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
initComponent();
HiLog.info(LABEL_LOG, "connectService------ ");
// 连接服务
connectAbility(getServiceIntent(BUNDLE_NAME, SERVICE_NAME), connection);
}
接下来创建IAbilityConnection类型的连接对象:
// 连接对象
private IAbilityConnection connection = new IAbilityConnection() {
@Override
public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
clientRemoteProxy = new ClientRemoteProxy(iRemoteObject);
}
@Override
public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
HiLog.info(LABEL_LOG, "%{public}s", "onAbilityDisconnectDone resultCode : " + resultCode);
}
};
这里需要一个IRemoteBroker实现类:
// 客户端代理类
public class ClientRemoteProxy implements IRemoteBroker {
private static final int RESULT_SUCCESS = 0;
private final IRemoteObject remoteObject;
public ClientRemoteProxy(IRemoteObject remoteObject) {
this.remoteObject = remoteObject;
}
// 这里是消息发送最关键的地方
public void todoServiceJob(int command) {
}
@Override
public IRemoteObject asObject() {
return remoteObject;
}
}
我们的网络请求分两种,一种是当天的天气,一种是预测未来4天的。
我们定义两个常量:
先定义个包constant,里面定义个常量类Constant.java:
package com.example.hanruweather.constant;
/**
* 常量
*/
public class Constant {
public static final int CURRENT = 1001;
public static final int FORECAST = 1002;
public static final int SUCCESS = 1;
public static final int FAIL = 0 ;
}
在initComponent()方法中,点击按钮,调用todoServiceJob()方法,进行请求服务:
// 初始化组件
private void initComponent() {
...
btnQuery.setClickedListener(component -> {
clientRemoteProxy.todoServiceJob(Constant.CURRENT);
});
}
接下来,我们就要在todoServiceJob()中,先封装数据,获取输入框中用户要查询天气的城市,然后通过sendRequest()进行请求:
public void todoServiceJob(int command) {
// 传过去的参数:城市名称
MessageParcel message = MessageParcel.obtain();
String city = inputCityTextField.getText();
HiLog.info(LABEL_LOG, "textfield-->city------ " + city);
message.writeString(city);
// 接收回来的参数
MessageParcel reply = MessageParcel.obtain();
try {
// 发送请求
HiLog.info(LABEL_LOG, "sendRequest------ ");
remoteObject.sendRequest(command, message, reply, option);
} catch (RemoteException e) {
e.printStackTrace();
}
}
然后在WeatherServiceAbility中,先创建RemoteObject子类并实现IRemoteBroker:
class MyRemote extends RemoteObject implements IRemoteBroker {
MyRemote() {
super("MyService_MyRemote");
HiLog.info(LABEL_LOG, "MyRemote()----->");
}
// 设置接收请求的条目。
// int code:表示从对等端发送的服务请求代码。
@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
return true;
}
@Override
public IRemoteObject asObject() {
return this;
}
}
声明一个成员变量:
public class WeatherServiceAbility extends Ability {
...
private MyRemote remote = new MyRemote();
...
}
在onConnect()方法中返回:
@Override
protected IRemoteObject onConnect(Intent intent) {
super.onConnect(intent);
HiLog.info(LABEL_LOG, "onConnect()---->");
return remote.asObject();
}
到此我们先运行一下,输入城市并点击查询按钮:
四、网络请求
1、这里我们使用https://openweathermap.org/api中的json接口,这个接口中提供的天气预报 的数据是全球的,也就是说我们的程序写完,你也可以查询国外城市的天气预报。
我们先去Constant常量类中定义json数据的网址:
// 天气预报的base地址
public static final String BASE_URI = "https://api.openweathermap.org/data/2.5/";
// private static final String API_KEY ="appid={{Add your Key}}";
public static final String API_KEY = "appid=accf59680d5b2039f92c468d4ac8e634";
// 预测4天
public static final String FORECAST_URL = "forecast";
// 当天天气
public static final String WEATHER_URL = "weather";
2、我们在onRemoteRequest()中获取MainAbilitySlice中请求传来的城市数据
String cityData = data.readString();
但是因为用户可能传入的是中文,如果是中国的城市名称,我们需要转为对应的汉语拼音。这里我们要借助于一个第三方jar包:
将这个jar包拷贝到libs目录下,并右键 Add as Library。
然后我们创建一个package,utils,里面专门存放一些工具类,先来一个TextUtils类:
package com.example.hanruweather.utils;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
public class TextUtils {
/**
* 将汉字转为汉语拼音
*
* @param text
* @return
*/
public static String convertToHanYuPinYin(String text) {
// 如果不是中文,直接 返回原字符串
if (!text.matches("[\\u4E00-\\u9FA5]+")) {
return text;
}
// 如果是中文,转为汉语拼音
char[] array = text.toCharArray();
StringBuffer sb = new StringBuffer();
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
// 不显示拼音的声调
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < array.length; i++) {
try {
sb.append(PinyinHelper.toHanyuPinyinStringArray(array[i],
format)[0]);
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
}
}
return sb.toString();
}
}
在onRemoteRequest()中:
HiLog.info(LABEL_LOG, "onRemoteRequest()----->");
String cityData = data.readString();
city = TextUtils.convertToHanYuPinYin(cityData);
HiLog.info(LABEL_LOG, "onRemoteRequest---->" + code + ",--->cityData:" + cityData + "---city:" + city);
这里我们再提供一个方法,用来拼接URL
// 拼接URL
private String concatUrl(String link, String params[]) {
StringBuilder urlFinal = new StringBuilder();
urlFinal.append(link);
urlFinal.append("?");
for (int i = 0; i < params.length; i++) {
urlFinal.append("&");
urlFinal.append(params[i]);
}
HiLog.info(LABEL_LOG, "urlFinal------ " + urlFinal.toString());
return urlFinal.toString();
}
然后我们再提供一个网络下载的工具类,在utils包下新建一个NetDownLoadUtils.java类:
package com.example.hanruweather.utils;
import ohos.hiviewdfx.HiLog;
import ohos.net.NetHandle;
import ohos.net.NetManager;
import ohos.net.NetStatusCallback;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
/**
* 网络下载天气预报数据的工具类
*/
public class NetDownLoadUtils {
/**
* 根据指定的url,下载天气预报的数据:json格式
* @param weatherUrl
* @return
*/
public static String downLoadWeatherData(String weatherUrl){
NetManager netManager = NetManager.getInstance(null);
if (!netManager.hasDefaultNet()) {
return null;
}
NetHandle netHandle = netManager.getDefaultNet();
// Listen to network state changes.
NetStatusCallback callback = new NetStatusCallback() {
// Override the callback for network state changes.
};
netManager.addDefaultNetStatusCallback(callback);
// Obtain a URLConnection using the openConnection method.
HttpURLConnection connection = null;
try {
URL url = new URL(weatherUrl);
URLConnection urlConnection = netHandle.openConnection(url,
java.net.Proxy.NO_PROXY);
if (urlConnection instanceof HttpURLConnection) {
connection = (HttpURLConnection) urlConnection;
}
connection.setRequestMethod("GET");
connection.connect();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
return sb.toString();
}
return null;
} catch (IOException e) {
e.printStackTrace();
return "IOException";
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}
记得在config.json中添加网络权限:
"reqPermissions": [
{
"name": "ohos.permission.GET_NETWORK_INFO"
},
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.SET_NETWORK_INFO"
}
]
到此,我们启动程序,并在输入框中输入:北京
能够将json数据下载到了。
五、json解析
我们首先观察json接口中的字段的描述:https://openweathermap.org/current#current_JSON
根据我们想要的字段,进行json解析,解析的方式有很多种,也有很多第三方的包,这里我使用的是JSON
可以将json字符串解析对应JavaBean的类,也可以解析到集合中,比如Map等。
然后我们在utils包下,新建一个json解析的工具类:
package com.example.hanruweather.utils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
public class JsonUtils {
/**
* 解析当前的天气预报-->Map集合
* @param json
* @return
*/
public static Map<String,String> parseCurrentWeatherJsonToMap(String json){
Map<String,String> map = new HashMap<>();
if(json==null|| "".equals(json)){
return map;
}
try {
JSONObject jSONObject = new JSONObject(json);
// 温度
JSONObject main = jSONObject.getJSONObject("main");
// 将开尔文转为摄氏度:开氏度 = 摄氏度+273.15。
map.put("temp",String.format("%.2f",main.optDouble("temp")-273.15)); // 温度
map.put("humidity",main.optString("humidity")); // 湿度
JSONObject wind = jSONObject.getJSONObject("wind");
map.put("speed",wind.optString("speed")); // 风速
JSONObject sys = jSONObject.getJSONObject("sys");
map.put("country", sys.optString("country"));
} catch (JSONException e) {
e.printStackTrace();
}
return map;
}
}
这里我将它解析到Map集合中了。
六、数据处理
解析后的数据,我们希望能够将它显示到UI界面上。在onRemoteRequest()中,需要将数据写出去。
// 设置接收请求的条目。
// int code:表示从对等端发送的服务请求代码。
@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
HiLog.info(LABEL_LOG, "onRemoteRequest()----->");
String cityData = data.readString();
city = TextUtils.convertToHanYuPinYin(cityData);
HiLog.info(LABEL_LOG, "onRemoteRequest---->" + code + ",--->cityData:" + cityData + "---city:" + city);
switch (code) {
case Constant.CURRENT:
// 下载当前的天气
String weatherUrl = concatUrl(Constant.BASE_URI + Constant.WEATHER_URL, new String[]{"q=" + city, Constant.API_KEY});
String jsonData = NetDownLoadUtils.downLoadWeatherData(weatherUrl);
HiLog.info(LABEL_LOG, "json-->" + jsonData);
Map<String, String> map = JsonUtils.parseCurrentWeatherJsonToMap(jsonData);
if (map.size() == 0) {
reply.writeInt(Constant.FAIL);
return false;
}
map.put("city", cityData);
reply.writeInt(Constant.SUCCESS);
reply.writeString(ZSONObject.toZSONString(map));
HiLog.info(LABEL_LOG, "map-->" + map);
break;
default:
}
return true;
}
在MainAbilitySlice中,todoServiceJob()的完整代码:
// 这里是消息发送最关键的地方
public void todoServiceJob(int command) {
// 传过去的参数:城市名称
MessageParcel message = MessageParcel.obtain();
String city = inputCityTextField.getText();
HiLog.info(LABEL_LOG, "textfield-->city------ " + city);
message.writeString(city);
// 接收回来的参数
MessageParcel reply = MessageParcel.obtain();
// 异步还是同步
MessageOption option = new MessageOption(MessageOption.TF_SYNC);
try {
// 发送请求
HiLog.info(LABEL_LOG, "sendRequest------ ");
remoteObject.sendRequest(command, message, reply, option);
// 处理结果
int result = reply.readInt();
if (result == Constant.FAIL) {
new ToastDialog(getContext()).setText("查无数据").setAlignment(LayoutAlignment.CENTER).show();
} else if (result == Constant.SUCCESS) {
switch (command) {
case Constant.CURRENT:
String jsonData = reply.readString();
HiLog.info(LABEL_LOG, "jsonData------ " + jsonData);
Map<String, String> map = ZSONObject.stringToClass(jsonData, Map.class);
HiLog.info(LABEL_LOG, "map------ " + map);
// 将接收到到数据,设置到UI组件上
textCity.setText(map.get("city") + "," + map.get("country"));
String date = DateUtils.getCurrentDate();
textDate.setText(date + ", " + DateUtils.getWeekOfCurrentDay());
textTemp.setText(map.get("temp") + "°C");
textHumidity.setText("空气湿度:" + map.get("humidity") + "%");
textSpeed.setText("风速:" + map.get("speed") + "km/hr");
break;
default:
}
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public IRemoteObject asObject() {
return remoteObject;
}
}
因为我们打算显示日期的时候,显示星期几,所以在utils包下,再提供一个日期的工具类:
package com.example.hanruweather.utils;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class DateUtils {
public static String getCurrentDate(){
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = sdf.format(date);
return dateStr;
}
/**
* 获取今天星期几
* @return
*/
public static String getWeekOfCurrentDay(){
String[] weekDays = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
Calendar cal = Calendar.getInstance();
int week = cal.get(Calendar.DAY_OF_WEEK);
return weekDays[week-1];
}
}
到此,我们运行一下程序:
七、获取更多天气
想获预测未来4天的天气,UI上可以借助于PageSlider,用来进行切换。获取未来4天的天气的json数据和上面获取当前天气的思路原理是一样的,只是更换json接口而已。然后进行相应的解析。再将数据通过PageSliderProvider,显示到PageSlider上。完整的代码,我放在github上了。
更多内容:
1、社区:鸿蒙巴士https://www.harmonybus.net/
2、公众号:HarmonyBus
3、技术交流QQ群:714518656
4、视频课:https://www.chengxuka.com