Android-第一行程式碼CoolWeather案例實戰
阿新 • • 發佈:2018-12-15
CoolWeather仿寫
環境資訊
AndroidStudio 3.2 JDK1.8 執行與Android7.1 (專案本身不需要申請許可權)
準備工作
1 新建專案 需要新增的四個庫(均是到目前為止最新的 庫 新增到build.gradle )
implementation 'com.squareup.okhttp3:okhttp:3.11.0' implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.github.bumptech.glide:glide:4.8.0' implementation 'org.litepal.android:core:2.0.0'
郵箱免費註冊➡ 登陸➡控制檯
專案編寫
1 定義各種包和檔案
2 編寫dbsql 下的 省市縣 實體類 並 配置 litepal庫
3 設定 litepal.xml 對映檔案 並編寫 Util包下 網路請求 方法
4 Util包下新建Utility類 解析和儲存網路獲取的資料
(因為上面圖片都可以看完程式碼,所以不再附上程式碼)
(這裡的程式碼較多 ,圖片顯示不完全,所以下面會附上程式碼)
public class Utility { //解析和處理伺服器返回的省級資料 public static boolean handleProvinceResponse(String response) { if (!TextUtils.isEmpty(response)) { try { JSONArray allProvinces = new JSONArray(response); for (int i = 0; i < allProvinces.length(); i++) { JSONObject provinceObject = allProvinces.getJSONObject(i); Province province = new Province(); province.setProvinceName(provinceObject.getString("name")); province.setProvinceCode(provinceObject.getInt("id")); 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 allCities = new JSONArray(response); for (int i = 0; i < allCities.length(); i++) { JSONObject cityObject = allCities.getJSONObject(i); City city = new City(); city.setCityName(cityObject.getString("name")); city.setCityCode(cityObject.getInt("id")); city.setProvinceId(provinceId); city.save(); } return true; } catch (JSONException e) { e.printStackTrace(); } } return false; } //解析和處理伺服器返回的縣級資料 public static boolean handleCountyResponse(String response, int cityId) { if (!TextUtils.isEmpty(response)) { try { JSONArray allCounties = new JSONArray(response); for (int i = 0; i < allCounties.length(); i++) { JSONObject countyObject = allCounties.getJSONObject(i); County county = new County(); county.setCountyName(countyObject.getString("name")); county.setWeatherId(countyObject.getString("weather_id")); county.setCityId(cityId); county.save(); } return true; } catch (JSONException e) { e.printStackTrace(); } } return false; } }
5 下面就開始 編寫介面了 Choose_area.xml
6 使用碎片載入佈局
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 ArrayAdapter<String> adapter;
private List<String> dataList = new ArrayList<>();
//省列表
private List<Province> provinceList;
//市列表
private List<City> cityList;
//縣列表
private List<County> countyList;
//選中的省份
private Province selectedProvince;
//選中的城市
private City selectedCity;
//當前選中的級別
private int currentLevel;
private TextView titleText;
private Button backButton;
private ListView listView;
private View view;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.choose_area, container, false);
initView();
adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, dataList);
listView.setAdapter(adapter);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//需要寫上這句話,來建立資料庫,要不然會報空指標
Connector.getDatabase();
//列表監聽器
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);
queryCities();
//如果選擇的是市
} else if (currentLevel == LEVEL_CITY) {
selectedCity = cityList.get(position);
queryCounties();
}
}
});
//回退按鈕監聽器
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (currentLevel == LEVEL_COUNTY) {
queryCities();
} else if (currentLevel == LEVEL_CITY) {
queryProvinces();
}
}
});
//呼叫 下面的方法
queryProvinces();
}
// 查詢全國所有的省,優先從資料庫查詢,如果沒有查詢到再去伺服器上查詢
private void queryProvinces() {
//設定 顯示標題為中國
titleText.setText("中國");
//設定回退按鈕隱藏
backButton.setVisibility(View.GONE);
//查詢 省實體類的資料
provinceList = LitePal.findAll(Province.class);
//判斷是不是有資料 如果有
if (provinceList.size() > 0) {
//把要顯示在listview的資料列表清空
dataList.clear();
//遍歷省資料
for (Province province : provinceList) {
//把資料加到 要顯示在listview的資料列表 上
dataList.add(province.getProvinceName());
}
//通知資料更新
adapter.notifyDataSetChanged();
//設定listview 預設選擇第一個
listView.setSelection(0);
// flag 設定為省 為回退做flag
currentLevel = LEVEL_PROVINCE;
//如果沒有資料
} else {
//去伺服器獲取資料
String address = "http://guolin.tech/api/china";
queryFromServier(address, "province");
}
}
//查詢全國所有的市,優先從資料庫查詢,如果沒有查詢到再去伺服器上查詢
private void queryCities() {
titleText.setText(selectedProvince.getProvinceName());
backButton.setVisibility(View.VISIBLE);
cityList = LitePal.where("provinceid=?", String.valueOf(selectedProvince.getId())).find(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 {
int provinceCode = selectedProvince.getProvinceCode();
String address = "http://guolin.tech/api/china/" + provinceCode;
queryFromServier(address, "city");
}
}
// 查詢全國所有的縣,優先從資料庫查詢,如果沒有查詢到再去伺服器上查詢
private void queryCounties() {
titleText.setText(selectedCity.getCityName());
backButton.setVisibility(View.VISIBLE);
countyList = LitePal.where("cityid=?", String.valueOf(selectedCity.getId())).find(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 {
int cityCode = selectedCity.getCityCode();
int provinceCode = selectedProvince.getProvinceCode();
String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
queryFromServier(address, "county");
}
}
// 根據傳入的地址和型別 從伺服器查詢省市縣資訊資料
private void queryFromServier(String address, final String type) {
//顯示對話方塊
showProgressDialog();
HttpUtil.sendOkHttpRequest(address, new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
//把獲取到的資料設定為字串
String responseText = response.body().string();
//設定結果
boolean result = false;
if ("province".equals(type)) {
//返回的省資料處理結果
result = Utility.handleProvinceResponse(responseText);
} else if ("city".equals(type)) {
//返回的市資料處理結果
result = Utility.handleCityResponse(responseText, selectedProvince.getId());
} else if ("county".equals(type)) {
//返回的縣資料處理結果
result = Utility.handleCountyResponse(responseText, selectedCity.getId());
}
//根據返回的資料結果更新資料
if (result) {
//獲取主執行緒(UI必須在主執行緒更新)
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if ("province".equals(type)) {
queryProvinces();
} else if ("city".equals(type)) {
queryCities();
} else if ("county".equals(type)) {
queryCounties();
}
}
});
}
}
@Override
public void onFailure(Call call, IOException e) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
Toast.makeText(getContext(), "載入失敗", Toast.LENGTH_LONG).show();
}
});
}
});
}
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();
}
}
private void initView() {
titleText = view.findViewById(R.id.title_text);
backButton = view.findViewById(R.id.back_button);
listView = view.findViewById(R.id.list_view);
}
}
7 在 activity_main.xml 載入碎片
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.gzl.exception.weatherreview.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
8 現在就可以執行出現這樣的結果
當然了 回退的圖示需要自己找
9 既然選擇城市的介面已經出來 那就應該點選縣 進入天氣介面
在上面的 ChooseAreaFragment 碎片中已經定義了 listview 的點選事件具體處理方法不過只有點選了 省和點選了市的 現在要新增點選了縣的
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);
queryCities();
//點選了市
} else if (currentLevel == LEVEL_CITY) {
selectedCity = cityList.get(position);
queryCounties();
}
//點選了縣
else if (currentLevel == LEVEL_COUNTY) {
//獲取縣的實體類中的 WeatherId
String weatherId = countyList.get(position).getWeatherId();
//如果這是在 MainActivity 的xml佈局中的碎片
if (getActivity() instanceof MainActivity) {
//用 Intent 傳值 直接跳轉到 WeatherActivity 活動
Intent intent = new Intent(getActivity(), WeatherActivity.class);
intent.putExtra("weather_id", weatherId);
startActivity(intent);
//關閉這個活動
getActivity().finish();
// 如果這是在 WeatherActivity的xml佈局中的碎片 也就是抽屜佈局中的
} else if (getActivity() instanceof WeatherActivity) {
//獲得 活動例項
WeatherActivity activity = (WeatherActivity) getActivity();
//使用 SharedPreferences 把 weather_id 儲存起來
SharedPreferences.Editor editor =PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
editor.putString("weather_id", weatherId);
editor.apply();
//關閉抽屜
activity.drawerLayout.closeDrawers();
//顯示重新整理圖示
activity.swipeRefresh.setRefreshing(true);
//請求天氣(下面會有)
activity.requestWeather(weatherId);
}
}
}
});
10 編寫天氣介面 書上是 分步寫的 ,所以新建檔案比較多,嫌麻煩就寫在一個裡
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
android:background="@color/colorPrimary">
<!-- 背景圖 -->
<ImageView
android:id="@+id/background_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<!-- 抽屜佈局 主要是不能點選一個去就再也不能選擇了吧 -->
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 重新整理 要能實時重新整理資料 -->
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 防止一屏顯示不夠 android:fitsSystemWindows="true" 是給狀態列流出空間-->
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:orientation="vertical">
<!-- 這就是那五個分步寫的佈局 其實可以扎這裡直接寫出介面佈局 很簡單 -->
<include layout="@layout/title" />
<include layout="@layout/now" />
<include layout="@layout/forecast" />
<include layout="@layout/aqi" />
<include layout="@layout/suggestion" />
</LinearLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
<!-- 在抽屜中顯示 省市縣的選擇 -->
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.gzl.exception.weatherreview.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
tools:layout="@layout/choose_area" />
</android.support.v4.widget.DrawerLayout>
</FrameLayout>
這五個佈局就不寫了很簡單
11 接下來就是 WeatherActivity.java
在此之前要定義一個工具方法 用來將返回的 JSON 資料 解析稱實體類
然後再進行WeatherActivity.java
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;private ImageView backgroundImg;
public SwipeRefreshLayout swipeRefresh;public DrawerLayout drawerLayout;
private Button navButton;private Intent intent;
private String mWeatherId;
//設定沉浸式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//沉浸模式
if (Build.VERSION.SDK_INT >= 21) {
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);
//呼叫初始化控制元件方法
initView();
}
//獲取控制元件
private void initView() {
weatherLayout = findViewById(R.id.weather_layout);
titleCity = findViewById(R.id.title_city);
titleUpdateTime = findViewById(R.id.title_update_time);
degreeText = findViewById(R.id.degree_text);
weatherInfoText = findViewById(R.id.weather_info_text);
forecastLayout = findViewById(R.id.forecast_layout);
aqiText = findViewById(R.id.aqi_text);
pm25Text = findViewById(R.id.pm25_text);
comfortText = findViewById(R.id.comfort_text);
carWashText = findViewById(R.id.car_wash_text);
sportText = findViewById(R.id.sport_text);
backgroundImg = findViewById(R.id.background_img);
swipeRefresh = findViewById(R.id.swipe_refresh);
swipeRefresh.setColorSchemeResources(R.color.colorPrimary);
drawerLayout = findViewById(R.id.drawer_layout);
navButton = findViewById(R.id.nav_button);
navButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.openDrawer(GravityCompat.START);
}
});
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = prefs.getString("weather", null);
String bing_pic = prefs.getString("bing_pic", null);
if (bing_pic != null) {
Glide.with(this).load(bing_pic).into(backgroundImg);
} else {
loadBingPic();
}
if (weatherString != null) {
//有快取時直接解析天氣
Weather weather = Utility.handleWeatherResponse(weatherString);
mWeatherId = weather.basic.weatherId;
ShowWeatherInfo(weather);
} else {
//沒有快取資訊去伺服器查詢資訊
mWeatherId = getIntent().getStringExtra("weather_id");
weatherLayout.setVisibility(View.INVISIBLE);
requestWeather(mWeatherId);
}
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
String weather_id = prefs.getString("weather_id", null);
requestWeather(weather_id);
}
});
}
//載入每日一圖
private void loadBingPic() {
String requestBinfPic = "http://guolin.tech/api/bing_pic";
HttpUtil.sendOkHttpRequest(requestBinfPic, new Callback() {
@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(backgroundImg);
}
});
}
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
});
}
//根據天氣 ID 請求城市天氣
public void requestWeather(final String weatherId) {
String weatherUrl = "http://guolin.tech/api/weather?cityid=" + weatherId +
"&key=d4892ac004c84b498d27455a89623852";
// http://guolin.tech/api/weather?cityid=CN101080507&key=d4892ac004c84b498d27455a89623852
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@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);
}
});
}
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(WeatherActivity.this, "獲取天氣資訊失敗"
, Toast.LENGTH_SHORT).show();
swipeRefresh.setRefreshing(false);
}
});
}
});
}
// 處理並顯示Weather 實體類中的資料
private void ShowWeatherInfo(Weather weather) {
if (weather != null && "ok".equals(weather.status)) {
String cityName = weather.basic.cityName;
String upadteTime = weather.basic.update.updateTime.split(" ")[1];
String degrss = weather.now.temperature + "℃";
String weatherinfo = weather.now.more.info;
titleCity.setText(cityName);
titleUpdateTime.setText(upadteTime);
degreeText.setText(degrss);
weatherInfoText.setText(weatherinfo);
forecastLayout.removeAllViews();
for (Forecast forecast : weather.forecastList) {
View view = LayoutInflater.from(this).inflate(R.layout.forecast_item, forecastLayout, false);
TextView dateText = view.findViewById(R.id.date_text);
TextView infoText = view.findViewById(R.id.info_text);
TextView maxText = view.findViewById(R.id.max_text);
TextView minText = 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 = "舒適度 :\n" + weather.suggestion.comfort.info;
String carwach = "洗車指數:\n" + weather.suggestion.carWash.info;
String sport = "運動建議:\n" + weather.suggestion.sport.info;
comfortText.setText(comfort);
carWashText.setText(carwach);
sportText.setText(sport);
weatherLayout.setVisibility(View.VISIBLE);
intent = new Intent(this, AutoUpdateService.class);
startService(intent);
} else {
Toast.makeText(WeatherActivity.this, "獲取天氣資訊失敗"
, Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onPause() {
super.onPause();
stopService(intent);
}
}
12 在Service包下 新建 Service
public class AutoUpdateService extends Service {
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
updateWeather();
updateBingPic();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHOur = 3*60 * 1000;
long triggerAtTime = SystemClock.elapsedRealtime() + anHOur;
Intent i = new Intent(this, AutoUpdateService.class);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
manager.cancel(pi);
return super.onStartCommand(intent, flags, startId);
}
//更新每日一圖 儲存到 SharedPreferences
private void updateBingPic() {
String requestBinfPic = "http://guolin.tech/api/bing_pic";
HttpUtil.sendOkHttpRequest(requestBinfPic, new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
final String bingpic = response.body().string();
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(AutoUpdateService.this).edit();
editor.putString("bing_pic", bingpic);
editor.apply();
}
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
});
}
//更新天氣 儲存到SharedPreferences
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=d4892ac004c84b498d27455a89623852";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@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();
}
}
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
});
}
}
}
這樣 基本專案就完成了
還可以做的事
可以 在Service 中 放 Notification 讓資料實時顯示到狀態列
增加 設定介面 是否顯示 Notification
增加一個 懸浮按鈕 顯示 選擇過的縣 不用再來回切換縣
。。。