简介

参考《第一行代码》,开发出一款全国省市县的天气预报app.

创建数据库和表

使用LitePal对数据库进行操作,创建三个实体类分别是Province、City和County。

1. 添加依赖项

compile 'org.litepal.android:core:1.3.2'

2. 创建实体类

package com.example.stardream.coolweather.db;

import org.litepal.crud.DataSupport;

/**

* Created by StarDream on 2018/8/22.

*/

//LitePal中的每一个实体类都应该继承DataSupport

public class Province extends DataSupport {

private int id; //实体类具有的id

private String provinceName; //省份的名字

private int provinceCode; //省的代号

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public String getProvinceName() {

return provinceName;

}

public void setProvinceName(String provinceName) {

this.provinceName = provinceName;

}

public int getProvinceCode() {

return provinceCode;

}

public void setProvinceCode(int provinceCode) {

this.provinceCode = provinceCode;

}

}

City实体类和County实体类同理。每个实体类代表一张表,实体类中的属性代表表中的每一列。

3. 配置litepal.xml文件

4. 修改AndroidManifest.xml文件

将项目的application配置为org.litepal.LitePalApplication

android:name="org.litepal.LitePalApplication"

关于LitePal的具体内容详见LitePal详解

遍历全国省市县数据

1. 客户端与服务器的交互

package com.example.stardream.coolweather.util;

import okhttp3.OkHttpClient;

import okhttp3.Request;

/**

* Created by StarDream on 2018/8/22.

*/

//采用OkHttp与服务器进行通信

public class HttpUtil {

//与服务器进行交互发起一条http请求只需要调用sendOkHttpRequest()即可

//传入要请求的地址,注册一个回调来处理服务器的响应

public static void sendOkHttpRequest(String address,okhttp3.Callback callback){

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder().url(address).build();

client.newCall(request).enqueue(callback);

}

}

发起http请求只需调用sendOkHttprequest()这个方法,传入一个地址,并且注册一个回调来处理服务器的响应。

2. 处理服务器返回的Json格式的数据

新建一个Utility类处理和解析Json数据。

package com.example.stardream.coolweather.util;

import android.text.TextUtils;

import com.example.stardream.coolweather.db.City;

import com.example.stardream.coolweather.db.County;

import com.example.stardream.coolweather.db.Province;

import org.json.JSONArray;

import org.json.JSONException;

import org.json.JSONObject;

/**

* Created by StarDream on 2018/8/22.

*/

public class Utility {

//处理和解析省份的数据

public static boolean hanldeProvinceResponse(String response){

if(!TextUtils.isEmpty(response)){

try{

//json的对象的数组,用来接收传回的多个省份的数据

JSONArray allProvinces = new JSONArray(response);

for(int i=0;i

//取出每一个省份

JSONObject provinceObject = allProvinces.getJSONObject(i);

Province province = new Province();

//解析出省份的id并将其赋值给province对象

province.setProvinceCode(provinceObject.getInt("id"));

//解析出省份的name并将其赋值给province对象

province.setProvinceName(provinceObject.getString("name"));

//将这一个省份保存到表中

province.save();

}

//处理成功返回真

return true;

}catch(JSONException e){

e.printStackTrace();

}

}

//处理失败返回假

return false;

}

//处理和解析市的数据

public static boolean handleCityResponse(String response,int provinceId){

if(!TextUtils.isEmpty(response)){

try{

JSONArray allCity = new JSONArray(response);

for(int i=0;i

JSONObject cityObject = allCity.getJSONObject(i);

City city = new City();

city.setCityCode(cityObject.getInt("id"));

city.setCityName(cityObject.getString("name"));

city.setProvinceId(provinceId);

city.save();

}

}catch(JSONException e){

e.printStackTrace();

}

return true;

}

return false;

}

//处理和解析县的数据

public static boolean handleCountyResponse(String response,int cityId){

if(!TextUtils.isEmpty(response)){

try{

JSONArray allCounty = new JSONArray(response);

for(int i=0;i

JSONObject countyObject = allCounty.getJSONObject(i);

County county = new County();

county.setCountyName(countyObject.getString("name"));

county.setCityId(cityId);

county.setWeatherId(countyObject.getInt("weather_id"));

county.save();

}

}catch (JSONException e){

e.printStackTrace();

}

return true;

}

return false;

}

}

省级、市级和县级对服务器发来的数据的处理解析方式都是类似的,使用JSONArray和JSONObject进行解析,然后组装成实体类对象,再调用save()方法存储到数据库中。

3. 遍历省市县

遍历省市县功能.xml

因为遍历省市县的功能会用到多次,因此将其写为碎片的形式而不是写在活动里面,这样复用的时候可以直接在布局文件中调用碎片。

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="#fff">

android:layout_width="match_parent"

android:layout_height="?attr/actionBarSize"

android:background="?attr/colorPrimary">

android:id="@+id/title_text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:textColor="#fff"

android:textSize="20sp"/>

android:id="@+id/back_button"

android:layout_width="25dp"

android:layout_height="25dp"

android:layout_marginLeft="10dp"

android:layout_alignParentLeft="true"

android:layout_centerVertical="true"

android:background="@drawable/ic_back"/>

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/list_view/>

一般情况下标题栏可以采用ActionBar,但是碎片中最好不用ActionBar或Toolbar,否则会有问题。

遍历省市县碎片

1. 新建一个ChooseAreaFragment用于展示查询界面和实现基本查询功能,定义一些量值。

public class ChooseAreaFragment extends Fragment {

public static final int LEVEL_PROVINCE = 0;

public static final int LEVEL_CITY = 1;

public static final int LEVEL_COUNTY =2;

private ProgressDialog progressDialog;

private TextView titleText;

private Button backButton;

private ListView listView;

private ArrayAdapter adapter;//适配器,与ListView配合使用

private List dataList = new ArrayList<>();//动态数组

//省列表

private List provinceList;

//市列表

private List cityList;

//县列表

private List countyList;

//选中的省份

private Province selectedProvince;

//选中的城市

private City selectedCity;

//当前选中的级别

private int currentLevel;

}

2. onCreateView()方法中获取到一些控件的实例,初始化了ArrayAdapter,将其设置为ListView的适配器。

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

/*

* 【LayoutInflater】其实是在res/layout/下找到xml布局文件,并且将其实例化,

* 对于一个没有被载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入;

* */

View view = inflater.inflate(R.layout.choose_area,container,false);

titleText = (TextView) view.findViewById(R.id.title_text);

backButton = (Button) view.findViewById(R.id.back_button);

listView = (ListView) view.findViewById(R.id.list_view);

adapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,dataList);

//载入listView

listView.setAdapter(adapter);

return view;

}

3. 在onActivityCreated()方法中设置ListView和Button的点击事件,在这里完成了基本的初始化操作,调用queryProvinces()方法,加载省级数据。

@Override

public void onActivityCreated(@Nullable Bundle savedInstanceState) {

super.onActivityCreated(savedInstanceState);

//对列表设置监听事件

listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){

@Override

public void onItemClick(AdapterView> parent, View view, int position, long id) {

if(currentLevel == LEVEL_PROVINCE){

//记住选中的省份

selectedProvince = provinceList.get(position);

//显示出省份对应下city的界面

queryCities();

}else if(currentLevel == LEVEL_CITY){

//记住选中的City

selectedCity = cityList.get(position);

//切换到相应的county界面

queryCounties();

}

}

});

//为返回按钮注册监听事件

backButton.setOnClickListener(new View.OnClickListener(){

public void onClick(View v){

//若在county切换到City

if(currentLevel == LEVEL_COUNTY){

queryCities();

}else if(currentLevel == LEVEL_CITY){

//若在City切换到province

queryProvinces();

}

}

});

//初始状态下显示province

queryProvinces();

}

4. 在queryProvinces()方法中,设置头布局的标题,返回按钮。调用LitePal查询结构读取省级数据,若读取到则将其显示在界面上,若没有则调用queryServer()方法从服务器查询数据。queryCities()方法和queryCounty()方法同理。

查询省级数据

/*查询全国所有的省,先从数据库查,没有的话去服务器查询

* */

private void queryProvinces(){

//设置标题栏

titleText.setText("中国");

//隐藏返回按钮

backButton.setVisibility(View.GONE);

//在数据库中查询所有省份

provinceList = DataSupport.findAll(Province.class);

if(provinceList.size()>0){

dataList.clear();

for(Province province:provinceList){

dataList.add(province.getProvinceName());

}

//更新适配器中的内容,变为省份数据

adapter.notifyDataSetChanged();

listView.setSelection(0);

currentLevel = LEVEL_PROVINCE;

}else{

//从服务器中查询

String address = "http://guolin.tech/api/china";

queryFromServer(address,"province");

}

}

查询市级数据

/*查询对应省的City数据,优先从数据库查,若没有,则去服务器查询

* */

private void queryCities(){

//设置标题栏

titleText.setText(selectedProvince.getProvinceName());

//设置返回按钮可见

backButton.setVisibility(View.VISIBLE);

//在数据库中查询对应的City数据

cityList = DataSupport.findAll(City.class);

if(cityList.size()>0){

dataList.clear();

for(City city : cityList){

dataList.add(city.getCityName());

}

adapter.notifyDataSetChanged();

listView.setSelection(0);

currentLevel = LEVEL_CITY;

}

else{

String address = "http://guolin.tech/api/china/"+selectedProvince.getProvinceCode();

queryFromServer(address,"city");

}

}

查询县级数据

/*查询选中的市内的所有县,优先从数据库查,若没有则去服务器查询

* */

private void queryCounties(){

titleText.setText(selectedCity.getCityName());

backButton.setVisibility(View.VISIBLE);

countyList = DataSupport.findAll(County.class);

if(countyList.size()>0){

dataList.clear();

for(County county:countyList){

dataList.add(county.getCountyName());

}

adapter.notifyDataSetChanged();

listView.setSelection(0);

currentLevel = LEVEL_COUNTY;

}else{

String address = "http://guolin.tech/api/china/"+

selectedProvince.getProvinceCode()+"/"+selectedCity.getCityCode();

queryFromServer(address,"county");

}

}

5. 在queryFromServer()方法中,调用HttpUtil中的sendOkHttpRequest()方法向服务器发送请求,响应的数据回调到onResponse()方法中,然后调用Utility中的handleProvincesResponse()方法解析和处理服务器返回的数据,然后将其存储到数据库中。最后再次调用queryProvinces()方法重新加载省级数据,因为queryProvinces()涉及UI操作,则须在主线程中调用,因此借助runOnUiThread()方法从子线程切换到主线程。

(问题:queryFromServer()在哪里说明是是在子线程中执行的???)

/*根据传入的地址和类型从服务器上获取数据

* */

private void queryFromServer(String address,final String type){

//未查出之前显示出进度条框

showProgressDialog();

HttpUtil.sendOkHttpRequest(address, new Callback() {

@Override

public void onFailure(Call call, IOException e) {

//通过runOnUiThread回到主线程处理逻辑

getActivity().runOnUiThread(new Runnable() {

@Override

public void run() {

closeProgressDialog();

Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show();

}

});

}

@Override

public void onResponse(Call call, Response response) throws IOException {

String responseText = response.body().string();

boolean result = false;

if(type.equals("province")){

result = Utility.hanldeProvinceResponse(responseText);

}else if(type.equals("city")){

result = Utility.handleCityResponse(responseText,selectedProvince.getId());

}else if(type.equals("county")){

result = Utility.handleCountyResponse(responseText,selectedCity.getId());

}

if(result){

getActivity().runOnUiThread(new Runnable() {

@Override

public void run() {

closeProgressDialog();

if(type.equals("province")){

queryProvinces();

}else if(type.equals("city")){

queryCities();

}else if(type.equals("county")){

queryCounties();

}

}

});

}

}

});

}

6. 涉及到的进度条框

进度条框的显示

//显示进度条框

private void showProgressDialog(){

if(progressDialog == null){

progressDialog = new ProgressDialog(getActivity());

progressDialog.setMessage("正在加载…");

progressDialog.setCanceledOnTouchOutside(false);

}

progressDialog.show();

}

进度条框的关闭

//关闭进度框

private void closeProgressDialog(){

if(progressDialog != null){

progressDialog.dismiss();

}

}

将碎片添加到活动

1. 修改activity_main.xml中的代码

定义一个FrameLayout,将ChooseAreaFragment加入,并让其充满整个布局。

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/choose_area_fragment"

android:name="com.example.stardream.coolweather.activity.ChooseAreaFragment"

android:layout_width="match_parent"

android:layout_height="match_parent" />

2. 删除原生的ActionBar

在style.xml中,

3. 权限问题

在AndroidManifest.xml中添加网络权限。

显示天气信息

因为和风天气返回的天气信息的JSON数据结构非常复杂,若使用JSONObject解析比较麻烦,因此使用GSON对天气信息进行解析。

在这里应该加入JSONObject和GSON解析和处理数据的区别

1. 定义GSON实体类

返回天气信息的格式:

返回数据的格式

要为basic、aqi、now、suggestion和daily_forecase定义实体类。

basic

basic具体内容

在gson包下建立Basic类

package com.example.stardream.coolweather.gson;

import com.google.gson.annotations.SerializedName;

/**

* Created by StarDream on 2018/8/24.

*/

/*由于JSON中的一些字段不太适合直接作为Java字段命名,

这里使用@SerializedName朱姐的方式让JSON字段和java字段建立映射关系

* */

public class Basic {

//"city"与cityName建立映射关系

@SerializedName("city")

public String cityName;

//"id"与weatherId建立映射关系

@SerializedName("id")

public String weatherId;

@SerializedName("update")

public Update update;

public class Update{

//"loc"与updateTime建立映射关系

@SerializedName("loc")

public String updateTime;

}

}

aqi

aqi具体内容

package com.example.stardream.coolweather.gson;

import com.google.gson.annotations.SerializedName;

/**

* Created by StarDream on 2018/8/24.

*/

public class AQI {

public AQICity city;

public class AQICity{

@SerializedName("aqi")

String aqi;

@SerializedName("pm25")

String pm25;

}

}

为什么这里的“aqi”与“pm25”没有使用SerilizedName???

now

now具体内容

package com.example.stardream.coolweather.gson;

import com.google.gson.annotations.SerializedName;

/**

* Created by StarDream on 2018/8/24.

*/

public class Now {

@SerializedName("tmp")

public String temperature;

@SerializedName("cond")

public More more;

public class More{

@SerializedName("txt")

public String info;

}

}

suggestion

suggestion

package com.example.stardream.coolweather.gson;

import com.google.gson.annotations.SerializedName;

/**

* Created by StarDream on 2018/8/24.

*/

public class Suggestion {

@SerializedName("comf")

public Comfort comfort;

@SerializedName("cw")

public CarWash carWash;

@SerializedName("sport")

public Sport sport;

public class Comfort{

@SerializedName("txt")

public String info;

}

public class CarWash{

@SerializedName("txt")

public String info;

}

public class Sport{

@SerializedName("txt")

public String info;

}

}

daily_forecast

daily_forecast具体内容

daily_forecase内包含的是一个数组,只定义出单日天气的实体类,在声明实体类引用的时候使用集合类型来进行声明。

package com.example.stardream.coolweather.gson;

import com.google.gson.annotations.SerializedName;

/**

Created by StarDream on 2018/8/24.

*/

public class Forecast {

@SerializedName("date")

public String date;

@SerializedName("tmp")

public Temperature temperature;

@SerializedName("cond")

public More more;

public class Temperature{

@SerializedName("max")

public String max;

@SerializedName("min")

public String min;

}

public class More{

@SerializedName("txt_d")

public String info;

}

}

总实体类Weather

创建一个总的实体类来引用印上的各个实体类。

package com.example.stardream.coolweather.gson;

import com.google.gson.annotations.SerializedName;

import java.util.List;

/**

* Created by StarDream on 2018/8/24.

*/

public class Weather {

public String status;

public Basic basic;

public AQI aqi;

public Now now;

public Suggestion suggestion;

//由于daily_forecase中包含的是一个数组,

//这里使用List集合来引用Forecast类

@SerializedName("daily_forecast")

public List forecastList;

}

2. 编写天气界面

天气信息界面,activity_weather.xml

为了让代码相对整齐,采用引用布局技术,将界面的不同部分写在不同的文件中,再通过引入布局的方式集成到activity_weather.xml中。

title.xml头布局

android:layout_width="match_parent"

android:layout_height="?attr/actionBarSize">

android:id="@+id/title_city"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:textColor="#fff"

android:textSize="20sp"/>

android:id="@+id/title_update_time"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginRight="10dp"

android:layout_alignParentRight="true"

android:layout_centerVertical="true"

android:textColor="#fff"

android:textSize="16sp"/>

其中,一个居中显示城市名,一个居右显示更新时间。

now.xml当前天气信息布局

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="15dp">

android:id="@+id/degree_text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="end"

android:textColor="#fff"

android:textSize="60sp"/>

android:id="@+id/weather_info_text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="end"

android:textColor="#fff"

android:textSize="20sp"/>

其中,放置了两个textView,一个用来显示气温,另外一个用于显示天气概况。

forecast.xml未来几天天气信息的布局

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="15dp"

android:background="#8000">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginLeft="15dp"

android:layout_marginTop="15dp"

android:text="预报"

android:textColor="#fff"

android:textSize="20sp"/>

android:id="@+id/forecast_layout"

android:layout_width="match_parent"

android:orientation="vertical"

android:layout_height="wrap_content">

布局使用了半透明背景,TextView定义了标题“预报”,使用LinearLayout定义了一个显示未来几天天气的布局,但未放入内容,要根据服务器返回的数据在代码中动态添加。

forecast_item.xml未来天气信息的子项布局

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="15dp">

android:id="@+id/date_text"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_gravity="center_vertical"

android:layout_weight="2"

android:textColor="#fff"/>

android:id="@+id/info_text"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_gravity="center_vertical"

android:layout_weight="1"

android:gravity="center"

android:textColor="#fff"/>

android:id="@+id/max_text"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_gravity="center_vertical"

android:layout_weight="1"

android:gravity="right"

android:textColor="#fff"/>

android:id="@+id/min_text"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_gravity="center_vertical"

android:layout_weight="1"

android:gravity="left"

android:textColor="#fff"/>

子项布局中放置了4个textView,分别用于显示天气预报日期,天气概况,最高温度和最低温度。

aqi空气质量信息的布局

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="15dp"

android:background="#8000">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginLeft="15dp"

android:layout_marginTop="15dp"

android:text="空气质量"

android:textColor="#fff"

android:textSize="20sp"/>

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="15dp">

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1">

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_centerInParent="true">

android:id="@+id/aqi_text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:textColor="#fff"

android:textSize="40sp"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:textColor="#fff"

android:text="AQI指数"/>

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1">

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_centerInParent="true">

android:id="@+id/pm25_text"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:textColor="#fff"

android:textSize="40sp"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:text="PM2.5指数"

android:textColor="#fff"/>

使用半透明背景,最上方定义了“空气质量”标题,然后实现了左右平分且居中对齐的布局,分别用于显示AQI指数和PM2.5指数。

suggestion.cml作为生活建议信息的布局

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="15sp"

android:background="#8000">

android:layout_marginLeft="15dp"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginTop="15dp"

android:text="生活建议"

android:textColor="#fff"

android:textSize="20sp"/>

android:id="@+id/comfort_text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_margin="15dp"

android:textColor="#fff"/>

android:id="@+id/car_wash_text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_margin="15dp"

android:textColor="#fff"/>

android:id="@+id/sport_text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_margin="15dp"

android:textColor="#fff"/>

同样使用半透明背景和一个标题,用三个textView显示舒适度、洗车指数和运动建议的相关数据。

将以上布局文件引入activity_weather.xml中

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/activity_weather"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@color/colorPrimary"

tools:context="com.example.stardream.coolweather.activity.WeatherActivity">

android:id="@+id/weahter_layout"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:scrollbars="none"

android:overScrollMode="never">

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content">

最外层布局使用了FrameLayout,然后嵌套了一个ScrollView,可以滚动查看屏幕之外的内容。因为ScrollView内部只允许存在一个子布局,因此嵌入垂直方向的Lin,将其余所有布局引入。

将天气显示在界面

解析天气JSON数据

//将返回的JSON数据解析成Weather实体类

public static Weather handleWeatherResponse(String response){

try{

JSONObject jsonObject = new JSONObject(response);

JSONArray jsonArray = jsonObject.getJSONArray("HeWeahter");

String weatherContent = jsonArray.getJSONObject(0).toString();

return new Gson().fromJson(weatherContent,Weather.class);

}catch (Exception ex){

ex.printStackTrace();

}

return null;

}

在Utility中添加解析JSON数据的方法,handleWeatherResponse()方法中先通过JSONObject和JSONArray将天气中的主体内容解析出来,之后可通过调用dromJson()方法将JSON转换成Weather对象。

编写WeatherActivity()中的代码

定义控件的变量

public class WeatherActivity extends AppCompatActivity {

private ScrollView weatherLayout;

private TextView titleCity;

private TextView titleUpdateTime;

private TextView degreeText;

private TextView weatherInfoText;

private LinearLayout forecastLayout;

private TextView aqiText;

private TextView pm25Text;

private TextView comfortText;

private TextView carWashText;

private TextView sportText;

……

}

onCreate()方法

获取控件的实例,从本地缓存读取天气数据,若没有,则会从Intent中读出天气Id,然后调用requestWeahter()方法请求服务器上的数据。

注意:这里缓存中保存数据采用SharedPreferences的方式,具体用法见 SharedPreferences存储

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_weather);

//初始化控件

weatherLayout = (ScrollView)findViewById(R.id.weahter_layout);

titleCity = (TextView)findViewById(R.id.title_city);

titleUpdateTime = (TextView)findViewById(R.id.title_update_time);

degreeText = (TextView)findViewById(R.id.degree_text);

weatherInfoText = (TextView)findViewById(R.id.weather_info_text);

forecastLayout = (LinearLayout)findViewById(R.id.forecast_layout);

aqiText = (TextView)findViewById(R.id.aqi_text);

pm25Text = (TextView)findViewById(R.id.pm25_text);

comfortText = (TextView)findViewById(R.id.comfort_text);

carWashText = (TextView)findViewById(R.id.car_wash_text);

sportText = (TextView)findViewById(R.id.sport_text);

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

String weatherString = prefs.getString("weather",null);

if(weatherString != null){

//若有缓存直接解析天气数据

Weather weather = Utility.handleWeatherResponse(weatherString);

showWeatherInfo(weather);

}else{

//无缓存时去服务器查询天气

String weatherId = getIntent().getStringExtra("weather_id");

weatherLayout.setVisibility(View.INVISIBLE);

requestWeather(weatherId);

}

}

向服务器请求天气信息

/*

* 根据天气的Id向服务器请求天气信息*/

public void requestWeather(final String weatherId){

String weatherUrl = "http://guolin.tech/api/weather?cityid="+

weatherId+"&key=";

HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {

@Override

public void onFailure(Call call, IOException e) {

e.printStackTrace();

runOnUiThread(new Runnable(){

public void run(){

Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT).show();

}

});

}

@Override

public void onResponse(Call call, Response response) throws IOException {

final String responseText = response.body().string();

final Weather weather = Utility.handleWeatherResponse(responseText);

runOnUiThread(new Runnable() {

@Override

public void run() {

if(weather !=null && "ok".equals(weather.status)){

SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();

editor.putString("weather",responseText);

editor.apply();

showWeatherInfo(weather);

}else{

Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT).show();

}

}

});

}

});

}

requestWeather()方法先使用传入的天气id和APIKEY拼装出接口地址,然后调用HttpUtil.sendOkHttpRequest()方法向该地址发送请求,服务器会以JSON格式返回天气信息。然后在onResponse()回调中调用Utility.handleWeatherResponse()将返回的JSON数据转换成Weather对象,将线程切换到主线程。若服务器返回的status状态是ok,则将返回数据缓存到SharedPreferences中,并进行显示。

showWeatherInfo()显示天气信息

/*处理冰战士Weather实体类中的数据

* */

private void showWeatherInfo(Weather weather){

String cityName = weather.basic.cityName;

String updateTime = weather.basic.update.updateTime.split(" ")[1];

String degree = weather.now.temperature+"℃";

String weatherInfo = weather.now.more.info;

titleCity.setText(cityName);

titleUpdateTime.setText(updateTime);

degreeText.setText(degree);

weatherInfoText.setText(weatherInfo);

forecastLayout.removeAllViews();

for(Forecast forecast:weather.forecastList){

View view = LayoutInflater.from(this).inflate(R.layout.forecas_item,forecastLayout,false);

TextView dateText = (TextView)view.findViewById(R.id.date_text);

TextView infoText = (TextView)view.findViewById(R.id.info_text);

TextView maxText = (TextView)view.findViewById(R.id.max_text);

TextView minText = (TextView)view.findViewById(R.id.min_text);

dateText.setText(forecast.date);

infoText.setText(forecast.more.info);

maxText.setText(forecast.temperature.max);

minText.setText(forecast.temperature.min);

forecastLayout.addView(view);

}

if(weather.aqi != null){

aqiText.setText(weather.aqi.city.aqi);

pm25Text.setText(weather.aqi.city.pm25);

}

String comfort = "舒适度:"+weather.suggestion.comfort.info;

String carwash = "洗车指数:"+weather.suggestion.carWash.info;

String sport = "运动建议:"+weather.suggestion.sport.info;

comfortText.setText(comfort);

carWashText.setText(carwash);

sportText.setText(sport);

weatherLayout.setVisibility(View.VISIBLE);

}

从Weather对象获取数据,然后显示到相应的空间上。

从县列表跳转到天气界面

public class ChooseAreaFragment extends Fragment {

……

@Override

public void onActivityCreated(@Nullable Bundle savedInstanceState) {

super.onActivityCreated(savedInstanceState);

//对列表设置监听事件

listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){

@Override

public void onItemClick(AdapterView> parent, View view, int position, long id) {

if(currentLevel == LEVEL_PROVINCE){

//记住选中的省份

selectedProvince = provinceList.get(position);

//显示出省份对应下city的界面

queryCities();

}else if(currentLevel == LEVEL_CITY){

//记住选中的City

selectedCity = cityList.get(position);

//切换到相应的county界面

queryCounties();

}else if(currentLevel == LEVEL_COUNTY){

String weatherId = countyList.get(position).getWeatherId();

Intent intent = new Intent(getActivity(),WeatherActivity.class);

intent.putExtra("weather_id",weatherId);

startActivity(intent);

getActivity().finish();

}

}

});

//为返回按钮注册监听事件

backButton.setOnClickListener(new View.OnClickListener(){

public void onClick(View v){

//若在county切换到City

if(currentLevel == LEVEL_COUNTY){

queryCities();

}else if(currentLevel == LEVEL_CITY){

//若在City切换到province

queryProvinces();

}

}

});

//初始状态下显示province

queryProvinces();

}

……

}

在点击县级列表之后,将选中的县的天气id传递出去,启动WeatherActivity。

#### 修改MainActivity

若在缓存中存在天气信息时,不再进行选择而是直接跳转到天气界面。

package com.example.stardream.coolweather.activity;

import android.content.Intent;

import android.content.SharedPreferences;

import android.preference.PreferenceManager;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import com.example.stardream.coolweather.R;

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

if(prefs.getString("weather",null) != null){

Intent intent = new Intent(this,WeatherActivity.class);

startActivity(intent);

finish();

}

}

}

###获取每日一图改变天气背景

####修改activity_weather.xml中的代码

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/activity_weather"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@color/colorPrimary"

tools:context="com.example.stardream.coolweather.activity.WeatherActivity">

android:id="@+id/bing_pic_img"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:scaleType="centerCrop"/>

android:id="@+id/weahter_layout"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:scrollbars="none"

android:overScrollMode="never">

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content">

增加ImageView作为背景图片。

####修改WeatherActivity中的代码

##### ImageView控件的定义

private ImageView bingPicImg;

##### 在onCreate()中

bingPicImg = (ImageView)findViewById(R.id.bing_pic_img);

String bingPic = prefs.getString("bing_pic",null);

if(bingPic !=null){

Glide.with(this).load(bingPic).into(bingPicImg);

}else{

loadBingPic();

}

···

若缓存中有图片,则直接调用Glide方式取出,若没有,向服务器请求。

loadBingPic()方法

/*

* 加载必应图片,每日一图*/

private void loadBingPic(){

String requestBingPic = "http://guolin.tech/api/bing_pic";

HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {

@Override

public void onFailure(Call call, IOException e) {

e.printStackTrace();

}

@Override

public void onResponse(Call call, Response response) throws IOException {

final String bingPic = response.body().string();

SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();

editor.putString("bing_pic",bingPic);

editor.apply();

runOnUiThread(new Runnable() {

@Override

public void run() {

Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg)

}

});

}

});

}

在requestWeather()方法中

loadBingPic();

每次请求天气信息时也会刷新背景图片

解决背景图片和状态栏没有融合的问题

在onCreate()方法中

//Android5.0及以上系统才支持,即版本号大于等于21

if(Build.VERSION.SDK_INT>=21){

//调用getWindow().getDecorView()拿到当前活动的DecorView

View decorView = getWindow().getDecorView();

//改变系统的UI显示,传入的两个值表示活动的布局会显示在状态栏上面

decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_STABLE);

//将状态栏设置成透明色

getWindow().setStatusBarColor(Color.TRANSPARENT);

}

头布局和系统状态栏紧贴到了一起,修改activity_weather.xml中的代码,在ScrollView的LinearLayout中增加了android:fitsSystemWindows属性,设置成true表示会为系统状态栏留出空间。

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:fitsSystemWindows="true">

手动更新天气和切换城市

手动更新天气

采用下拉刷新方式手动更新天气

修改activity_weather.xml

android:id="@+id/swipe_refresh"

android:layout_width="match_parent"

android:layout_height="match_parent">

……

修改WeatherActivity中的代码

定义刷新控件

private SwipeRefreshLayout swipeRefresh;

加载控件,设置下拉进度条颜色

swipeRefresh = (SwipeRefreshLayout)findViewById(R.id.swipe_refresh);

swipeRefresh.setColorSchemeResources(R.color.colorPrimary);

//定义一个weatherId

final String weatherId;

实现更新

if(weatherString != null){

//若有缓存直接解析天气数据

Weather weather = Utility.handleWeatherResponse(weatherString);

//若有缓存得到weatherId

weatherId = weather.basic.weatherId;

showWeatherInfo(weather);

}else{

//无缓存时去服务器查询天气

weatherId = getIntent().getStringExtra("weather_id");

weatherLayout.setVisibility(View.INVISIBLE);

requestWeather(weatherId);

}

swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener(){

public void onRefresh(){

requestWeather(weatherId);

}

});

隐藏刷新进度条

/*

* 根据天气的Id向服务器请求天气信息*/

public void requestWeather(final String weatherId){

String weatherUrl = "http://guolin.tech/api/weather?cityid="+

weatherId+"&key=";

HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {

@Override

public void onFailure(Call call, IOException e) {

e.printStackTrace();

runOnUiThread(new Runnable(){

public void run(){

Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT).show();

//刷新事件结束,隐藏刷新进度条

swipeRefresh.setRefreshing(false);

}

});

}

@Override

public void onResponse(Call call, Response response) throws IOException {

final String responseText = response.body().string();

final Weather weather = Utility.handleWeatherResponse(responseText);

runOnUiThread(new Runnable() {

@Override

public void run() {

if(weather !=null && "ok".equals(weather.status)){

SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();

editor.putString("weather",responseText);

editor.apply();

showWeatherInfo(weather);

}else{

Toast.makeText(WeatherActivity.this,"获取天气信息失败",Toast.LENGTH_SHORT).show();

}

//刷新事件结束,隐藏刷新进度条

swipeRefresh.setRefreshing(false);

}

});

}

});

loadBingPic();//每次请求天气信息时会刷新背景图片

}

切换城市

在title.xml标题栏设置按钮

android:id="@+id/nav_button"

android:layout_width="30dp"

android:layout_height="30dp"

android:layout_marginLeft="10dp"

android:layout_alignParentLeft="true"

android:layout_centerVertical="true"

android:background="@drawable/ic_home"/>

修改activity_weather.xml布局加入滑动菜单功能

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/activity_weather"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@color/colorPrimary"

tools:context="com.example.stardream.coolweather.activity.WeatherActivity">

android:id="@+id/bing_pic_img"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:scaleType="centerCrop"/>

android:id="@+id/drawer_layout"

android:layout_width="match_parent"

android:layout_height="match_parent"

>

android:id="@+id/swipe_refresh"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/weahter_layout"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:scrollbars="none"

android:overScrollMode="never">

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:fitsSystemWindows="true">

android:id="@+id/choose_area_fragment"

android:name="com.example.stardream.coolweather.activity.ChooseAreaFragment"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_gravity="start"/>

DrawerLayout中的第一个子控件用于作为主屏幕中显示的内容;

第二个子控件用于作为滑动菜单中显示的内容,添加了用于遍历省市县数据的碎片。

修改WeatherActivity中的代码加入滑动菜单的逻辑控制

定义Button和DrawerLayout

public DrawerLayout drawerLayout;

private Button navButton;

onCreate()

drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);

navButton = (Button)findViewById(R.id.nav_button);

navButton.setOnClickListener(new View.OnClickListener(){

public void onClick(View v){

drawerLayout.openDrawer(GravityCompat.START);

}

});

在onCreate()中获取新增的DrawerLayout和Button的实例,然后在Button点击事件中调用DrawerLayout的openDrawer()方法打开活动菜单即可。

请求新选择的城市的天气信息

因为原来的跳转是从MainActivity中跳转过去的,现在就在WeatherActivity中,所以就关闭滑动菜单,显示下拉刷新进度条,请求新城市的天气信息。

在ChooseAreaFragment中的onActivityCreated()中,

listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){

@Override

public void onItemClick(AdapterView> parent, View view, int position, long id) {

if(currentLevel == LEVEL_PROVINCE){

//记住选中的省份

selectedProvince = provinceList.get(position);

//显示出省份对应下city的界面

queryCities();

}else if(currentLevel == LEVEL_CITY){

//记住选中的City

selectedCity = cityList.get(position);

//切换到相应的county界面

queryCounties();

}else if(currentLevel == LEVEL_COUNTY){

String weatherId = countyList.get(position).getWeatherId();

if(getActivity()instanceof MainActivity){

Intent intent = new Intent(getActivity(),WeatherActivity.class);

intent.putExtra("weather_id",weatherId);

startActivity(intent);

getActivity().finish();

}else if(getActivity() instanceof WeatherActivity){

WeatherActivity activity = (WeatherActivity)getActivity();

activity.drawerLayout.closeDrawers();

activity.swipeRefresh.setRefreshing(true);

activity.requestWeather(weatherId);

}

}

}

});

后台自动更新天气

要想自动更新天气,需要创建一个长期在后台运行额定时任务,因此新建一个服务AutoUpdateService。

onStartCommand()方法

package com.example.stardream.coolweather.service;

import android.app.AlarmManager;

import android.app.PendingIntent;

import android.app.Service;

import android.content.Intent;

import android.content.SharedPreferences;

import android.os.IBinder;

import android.os.SystemClock;

import android.preference.PreferenceManager;

import com.example.stardream.coolweather.gson.Weather;

import com.example.stardream.coolweather.util.HttpUtil;

import com.example.stardream.coolweather.util.Utility;

import java.io.IOException;

import okhttp3.Call;

import okhttp3.Callback;

import okhttp3.Response;

public class AutoUpdateService extends Service {

public AutoUpdateService() {

}

@Override

public IBinder onBind(Intent intent) {

return null;

}

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

updateWeather();

updateBingPic();

AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);

int anHour = 8*60*60*1000;//8个小时的毫秒数

long triggerAtTime = SystemClock.elapsedRealtime()+anHour;

Intent i = new Intent(this,AutoUpdateService.class);

PendingIntent pi = PendingIntent.getService(this,0,i,0);

manager.cancel(pi);

manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);

return super.onStartCommand(intent, flags, startId);

}

在onStartCommand()方法中先调用了updateWeather()方法更新天气,调用updateBingPic()方法更新背景图片,将更新时间设置为8小时,定时闹钟见AlarmManager用法

updateWeather()方法

/*更新天气信息

* */

private void updateWeather(){

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

String weatherString = prefs.getString("weather",null);

if(weatherString !=null){

//有缓存时直接解析天气数据

Weather weather = Utility.handleWeatherResponse(weatherString);

String weatherId = weather.basic.weatherId;

String weatherUrl = "http://guolin.tech/api/weather?cityid="+

weatherId+"&key=";

HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {

@Override

public void onFailure(Call call, IOException e) {

e.printStackTrace();

}

@Override

public void onResponse(Call call, Response response) throws IOException {

String responseText = response.body().string();

Weather weather = Utility.handleWeatherResponse(responseText);

if(weather !=null && "ok".equals(weather.status)){

SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();

editor.putString("weather",responseText);

editor.apply();

}

}

});

}

}

updateBingPic()方法

/*更新必应每日一图

* */

private void updateBingPic(){

String requestBingPic ="http://guolin.tech/api/bing_pic";

HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {

@Override

public void onFailure(Call call, IOException e) {

e.printStackTrace();

}

@Override

public void onResponse(Call call, Response response) throws IOException {

String bingPic = response.body().string();

SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();

editor.putString("bing_pic",bingPic);

editor.apply();

}

});

}

修改WeatherActivity

在showWeather()方法的最后加入启动AutoUpdateService这个服务的代码,一旦选中某个城市并成功更新天气之后,AutoUpdateService就会一直在后台运行,并保证每8小时更新一次天气。

private void showWeatherInfo(Weather weather){

……

}

if(weather.aqi != null){

aqiText.setText(weather.aqi.city.aqi);

pm25Text.setText(weather.aqi.city.pm25);

Intent intent = new Intent(this, AutoUpdateService.class);

startService(intent);

}

……

}

修改图标和名称

在AndroidManifest.xml修改图标

android:icon="@mipmap/logo"

在strings.xml修改app名称

我的天气

终于学(照猫画虎)完了这个天气预报,可是还没有正常运行,据说是和风天气接口过期了,汗颜。可能还要重新找新的API调用。

emmmmm……我来为其正名,接口没问题,是自己的原因,天气数据是可以成功调出来的,现在界面处理方面还有点问题,debug中……

我发现这本书的代码还是有问题的,如下:

1.ListView与ArrayAdapter的使用不当,每当适配器中的内容发生变化时,要再一次载入listView。

adapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,dataList);

//载入listView

listView.setAdapter(adapter);

2.每一次在数据库中查询是否存在省市县以及天气的数据,若存在则直接取出显示出来。这个完全没有考虑存在于数据库中的数据是否是选中的省市县,不然会导致选中的省市县和显示出来的不相符,因此要添加限制条件。整个这一块的代码都很混乱。。。在经历心情第一丧之后终于调出来了,细节修改的地方忘记了,核心在这里。

queryCities()

//在数据库中查询对应的City数据

//原来代码的问题时把所有City的数据取出来了,然后就发生混乱

//应该取出的是选中省份的city

cityList = DataSupport.where("provinceId = ?",String.valueOf(selectedProvince.getId())).find(City.class);

queryCounties()

//在数据库中查询对应的county数据

//原来代码的问题时把所有County的数据取出来了,然后就发生混乱

//应该取出的是选中city的county

countyList = DataSupport.where("cityId = ?",String.valueOf(selectedCity.getId())).find(County.class);

界面展示

Icon

主界面

省级—甘肃

市级—临夏

最关键的界面图片总是上传失败,不知道是什么原因。。。不传了,真是想吐槽这个上传图片功能。

详细代码请见CoolWeather源代码

android开发酷欧天气,酷欧天气的开发相关推荐

  1. android 新浪天气,新浪天气通发布Android 1.5版 助盲人听天气

    "我想给你们产品提点意见,在语音播报里最好能加上城市,不然如果切换城市就不知道是哪里."世界盲人日前最后一个工作日,天气通工作人员收到安徽芜湖陈先生对产品提出的建议.陈先生是一位盲 ...

  2. 本人独立开发的天气app-口袋天气成功在应用宝上线

    经过这次的app上线经历,我真心觉得一个app的开发到上线的过程是一个不简单的过程. 口袋天气下载地址:http://sj.qq.com/myapp/detail.htm?apkName=com.zh ...

  3. android仿优酷菜单,Android编程实现仿优酷旋转菜单效果(附demo源码)

    本文实例讲述了Android编程实现仿优酷旋转菜单效果.分享给大家供大家参考,具体如下: 首先,看下效果: 不好意思,不会制作动态图片,只好上传静态的了,如果谁会,请教教我吧. 首先,看下xml文件: ...

  4. 【UI学习】Android github开源项目,酷炫自定义控件(View)汇总

    [UI学习]Android github开源项目,酷炫自定义控件(View)汇总 转载  2016年09月04日 23:23:15 3484 近期整理的比较酷炫并且我们会经常用到的custom vie ...

  5. 酷狗软件测试用例,仿酷狗的音乐搜软件的开发与测试.doc

    仿酷狗的音乐搜软件的开发与测试 仿酷狗的音乐搜索软件的开发与测试 摘 要 当前,面对浩瀚的网络资源,搜索引擎为所有网上冲浪的用户提供了一个入口,毫不夸张的说,所有的用户都可以搜索引擎到达自己想去的网上 ...

  6. 优酷java_youtubie 仿优酷的视频网站,采用JAVA开发,支持Oracle数据库。主要功能包含注册登录, 上传 Jsp/Servlet 238万源代码下载- www.pudn.com...

    文件名称: youtubie下载 收藏√  [ 5  4  3  2  1 ] 开发工具: Java 文件大小: 12657 KB 上传时间: 2015-04-23 下载次数: 1 详细说明:仿优酷的 ...

  7. android 优酷 自动全屏播放,Android如何实现仿优酷视频的悬浮窗播放效果

    Android如何实现仿优酷视频的悬浮窗播放效果 发布时间:2020-07-11 10:24:43 来源:亿速云 阅读:228 作者:清晨 这篇文章主要介绍Android如何实现仿优酷视频的悬浮窗播放 ...

  8. 酷q插件开发Java_如何使用Java开发QQ机器人 方法一

    使用Java开发QQ机器人- CQ & HTTP API 使用库Q平台下的CQ HTTP API插件以及simple-robot核心标准库作为依赖. 此插件与框架的 简陋 简单介绍:文档处简介 ...

  9. android仿墨迹天气预报,手机天气预报 墨迹天气安卓版使用教程

    墨迹天气安卓版是一款免费的天气信息查询软件,目前它已经支持2488个城市,几乎覆盖国内所有的县级以上城市.作为一款拥有5000万用户的热门软件,很多用户都想深入了解墨迹天气的具体使用技巧.那么今天笔者 ...

最新文章

  1. 视频分辨率无损放大软件 Topaz Video Enhance AI 2.3.0
  2. Oracle-index索引解读
  3. [Ext JS 4]性能优化
  4. codova添加android慢_从 0 开始学 Linux 内核之 android 内核栈溢出 ROP 利用
  5. 遗传算法的matlab代码实现
  6. unix系列系统镜像下载
  7. 天地图 + geojson 绘制中国行政区划
  8. pytorch搭建分类网络并进行训练和测试
  9. vue中实现打包时代码压缩
  10. html5 答题器页面,梦幻西游网页版科举答题器答案大全
  11. React Native 布局实现测试
  12. Encrypt加密解密
  13. 计算机编程在哪里学,高中毕业想学计算机编程,不知道从哪开始学起。
  14. Linux操纵细碎以太网卡的装配及设置-2
  15. 系统开发建设要经过哪些流程?
  16. Java基础day2【谷】
  17. 如何用一个鼠标控制多台电脑?
  18. 3. 描述性统计分析
  19. 可积与原函数存在的区别
  20. 如何组建LoRaWAN无线网络以传输温度数据

热门文章

  1. Leetcode 318答案详解(基于C++位操作)
  2. win10+Xming+Xshell显示远程linux服务器的图形程序窗口
  3. python 实现SOM: 函数更新
  4. 双层卷积神经网络--tf
  5. 如何将composer设置为全局变量?
  6. 2018.11.02 洛谷P2661 信息传递(拓扑排序+搜索)
  7. linux文件的基本权限、默认权限、特殊权限总结
  8. struct 和typedef struct的区别
  9. apache+weblogic获取客户端访问的真正ip
  10. firefox与ie的javascript兼容性编程汇编【转载】