上一篇:Android 天氣APP(十七)熱門城市 - 國內城市
完成此篇文章實現的效果圖如下:
前言
常用城市對於那些經常在外面出差的朋友來說相信是不陌生的,因爲涉及到在不同城市之間居住,所以對於其他城市的天氣是比較在意的,假如我要去一個城市的話,肯定要先了解天氣怎麼樣,不然過去之後身體都受不了,何談工作和生活呢,所以說我們需要在去之前做好準備工作,未雨綢繆,說實話這個功能是應該早就要有的,所以爲了提高可用性,這裏增加常用城市的功能。
正文 Commonly used city
① 創建Activity
既然是一個新的功能當然是通過創建Activity來實現了,在app模塊下的ui包下創建一個Empty Activity,命名爲CommonlyUsedCityActivity
然後修改佈局
這是佈局中用到的圖標
然後是顏色,
<color name="shallow_gray">#F2F2F2</color><!--淺灰色-->
<color name="dark_gray">#707070</color><!--深灰色-->
<color name="shallow_black">#6D6D6D</color><!--褐黑色-->
<color name="red">#FF0A00</color><!--紅色-->
<color name="line_gray">#E3E5E8</color><!--灰色分割線-->
<color name="shallow_yellow">#E7C373</color><!--淺黃色-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/shallow_gray"
tools:context=".ui.CommonlyUsedCityActivity">
<!--頭部-->
<androidx.appcompat.widget.Toolbar
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white"
android:elevation="@dimen/dp_2"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@mipmap/icon_return"
app:popupTheme="@style/AppTheme.PopupOverlay">
<!--輸入框佈局-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_30"
android:layout_marginRight="@dimen/dp_12"
android:layout_weight="1"
android:background="@drawable/shape_gray_bg_14"
android:gravity="center_vertical"
android:paddingLeft="@dimen/dp_12"
android:paddingRight="@dimen/dp_12">
<!--搜索圖標-->
<ImageView
android:layout_width="@dimen/dp_16"
android:layout_height="@dimen/dp_16"
android:src="@mipmap/icon_search" />
<!--輸入框-->
<EditText
android:id="@+id/edit_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@null"
android:hint="添加城市"
android:imeOptions="actionSearch"
android:paddingLeft="@dimen/dp_8"
android:paddingRight="@dimen/dp_4"
android:singleLine="true"
android:textColor="@color/black"
android:textCursorDrawable="@drawable/cursor_style"
android:textSize="@dimen/sp_14" />
<!--清除輸入的內容-->
<ImageView
android:id="@+id/iv_clear_search"
android:layout_width="@dimen/dp_16"
android:layout_height="@dimen/dp_16"
android:src="@mipmap/icon_delete"
android:visibility="gone" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
<!--沒有數據時顯示-->
<LinearLayout
android:visibility="gone"
android:id="@+id/lay_normal"
android:gravity="center"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="@dimen/dp_160"
android:layout_height="@dimen/dp_160"
android:src="@mipmap/icon_normal"/>
<TextView
android:textSize="@dimen/sp_16"
android:text="空空如也~"
android:textColor="@color/dark_gray"
android:layout_marginTop="@dimen/dp_12"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!--常用城市展示列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_commonly_used"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
<!--搜索城市展示列表-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_search"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
可以看到這裏佈局和搜索城市的佈局有些類似,但不一樣,這裏的搜索出來的結果不會產生搜索記錄,當點擊搜索出來的城市時,就去查詢這個城市的天氣,同時這個城市也會放入常用城市列表裏面,這裏可以用緩存來做處理,也可以通過數據庫來處理。
② Android SQLite
相信很多從事Android開發的程序員都瞭解過SQLite,但是用過的人並不多,這是爲什麼呢?因爲一旦數據量很多的情況下我們不會用SQLite,而是通過服務器的數據庫返回數據,而數據量少的時候用緩存就可以解決問題,所以這也是SQLite尷尬的地方,這是我個人看法,不過這個SQLite還是很重要的,不然我還是會用緩存的,如果是使用原生的SQLite代碼就會比較的繁瑣,所以這裏我們可以用第三方庫來快速實現功能,這裏使用郭霖大神的LitePal框架
首先是在mvplibrary下的build.gradle中添加依賴
//Android SQLite操作框架
api 'org.litepal.guolindev:core:3.1.1'
//列表item側滑刪除
api 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0'
應該是一目瞭然吧,記得Sync哦~
然後配置litepal.xml,將項目預覽模式切換爲Project,然後打開mvplibrary,創建一個assets文件夾,再創建一個litepal.xml文件
文件中的代碼如下
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<!--數據庫名稱-->
<dbname value="GoodWeather" />
<!--數據庫版本-->
<version value="1" />
<!--數據列表-->
<list>
</list>
</litepal>
比較的簡單
然後要在app下的WeatherApplication中進行初始化
現在你可以創建數據實體了,然後在mvplibrary下創建一個數據實體bean
代碼如下:
package com.llw.mvplibrary.bean;
import org.litepal.crud.LitePalSupport;
public class ResidentCity extends LitePalSupport {
private int id;//編號
private String location;//地區/城市名稱
private String parent_city;//該地區/城市的上級城市
private String admin_area;//該地區/城市所屬行政區域
private String cnty;//該地區/城市所屬國家名稱
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getParent_city() {
return parent_city;
}
public void setParent_city(String parent_city) {
this.parent_city = parent_city;
}
public String getAdmin_area() {
return admin_area;
}
public void setAdmin_area(String admin_area) {
this.admin_area = admin_area;
}
public String getCnty() {
return cnty;
}
public void setCnty(String cnty) {
this.cnty = cnty;
}
}
然後在litepal.xml中增加一個mapping
最後在WeatherApplication中的onCreate方法中初始化,初始化的時候,你的數據庫就創建好了,數據庫名稱是GoodWeather,表名是ResidentCity
那麼這一塊的內容就寫完了,只需要在實際應用中結合業務邏輯使用就可以了,當然你也可以去自己嘗試一下,感興趣的可以看Android LitePal的簡單使用這篇文章。
③ 佈局item
通過最上面的效果圖可以看到是兩個列表,其中一個是已經添加的城市列表,另一個是搜索出來的城市列表,既然兩個列表就要有兩個item,當然你也可以用一個item來寫,只不過用的時候要多寫一些代碼,首先當然是從佈局開始着手了。
在app中res下的layout中創建兩個佈局文件
item_commonly_city_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--支持側滑的佈局-->
<com.mcxtzhang.swipemenulib.SwipeMenuLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_50"
android:clickable="true"
android:paddingBottom="1dp">
<!--顯示文本-->
<TextView
android:id="@+id/tv_city_name"
android:gravity="center_vertical"
android:paddingLeft="@dimen/dp_16"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="@color/shallow_black"
android:background="?android:attr/selectableItemBackground"
android:text="城市"/>
<!-- 側滑菜單的內容 現在裏面只有一個按鈕 -->
<Button
android:id="@+id/btn_delete"
android:layout_width="@dimen/dp_100"
android:layout_height="match_parent"
android:background="@color/red"
android:text="刪除"
android:textColor="@android:color/white"/>
</com.mcxtzhang.swipemenulib.SwipeMenuLayout>
<!--分隔線-->
<View
android:background="@color/line_gray"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_1"/>
</LinearLayout>
item_commonly_city_add_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:id="@+id/item_add_city"
android:background="?android:attr/selectableItemBackground"
android:layout_height="wrap_content"
android:orientation="vertical">
<!--城市信息-->
<TextView
android:id="@+id/tv_location"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:textSize="@dimen/sp_16"
android:paddingLeft="@dimen/dp_16"
android:layout_height="@dimen/dp_50"
android:textColor="@color/shallow_black" />
<!--分隔線-->
<View
android:layout_width="match_parent"
android:layout_height="@dimen/dp_1"
android:background="@color/line_gray" />
</LinearLayout>
④ 列表適配器
然後創建適配器
CommonlyCityAdapter.java
package com.llw.goodweather.adapter;
import androidx.annotation.Nullable;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.goodweather.R;
import com.llw.mvplibrary.bean.ResidentCity;
import java.util.List;
/**
* 常用城市列表適配器
*/
public class CommonlyCityAdapter extends BaseQuickAdapter<ResidentCity, BaseViewHolder> {
public CommonlyCityAdapter(int layoutResId, @Nullable List<ResidentCity> data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, ResidentCity item) {
helper.setText(R.id.tv_city_name, item.getLocation());
//添加點擊事件
helper.addOnClickListener(R.id.tv_city_name)
.addOnClickListener(R.id.btn_delete);
}
}
CommonlyCityAddAdapter.java
package com.llw.goodweather.adapter;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.goodweather.R;
import com.llw.goodweather.bean.SearchCityResponse;
import java.util.List;
import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
/**
* 添加城市時搜索返回結果列表適配器
*/
public class CommonlyCityAddAdapter extends BaseQuickAdapter<SearchCityResponse.HeWeather6Bean.BasicBean, BaseViewHolder> {
private String edStr;//關鍵字
public CommonlyCityAddAdapter(int layoutResId, @Nullable List<SearchCityResponse.HeWeather6Bean.BasicBean> data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, SearchCityResponse.HeWeather6Bean.BasicBean item) {
TextView textView = helper.getView(R.id.tv_location);
String result = item.getLocation() + " , " + item.getParent_city() + " , " + item.getAdmin_area() + " , " + item.getCnty();
if (edStr != null && edStr.length() > 0) {
textView.setText(matcherSearchText(mContext.getResources().getColor(R.color.shallow_yellow),result,edStr));
} else {
textView.setText(item.getLocation() + " , " +
item.getParent_city() + " , " +
item.getAdmin_area() + " , " +
item.getCnty());
}
helper.addOnClickListener(R.id.item_add_city);
}
/**
* 改變顏色
* @param str 輸入的文本
*/
public void changTxColor(String str) {
edStr = str;
notifyDataSetChanged();
}
/**
* 改變一段文本中第一個關鍵字的文字顏色
* @param color 要改變文字的顏色
* @param string 文本字符串
* @param keyWord 關鍵字
* @return
*/
public static CharSequence matcherSearchText(int color, String string, String keyWord) {
SpannableStringBuilder builder = new SpannableStringBuilder(string);
int indexOf = string.indexOf(keyWord);
if (indexOf != -1) {
builder.setSpan(new ForegroundColorSpan(color), indexOf, indexOf + keyWord.length(), SPAN_EXCLUSIVE_EXCLUSIVE);
}
return builder;
}
}
適配器的代碼都是比較簡單的,其中matcherSearchText方法是根據傳入的關鍵字找到它在一段字符串中的第一次出現的位置,並修改字體顏色。
⑤ 代碼整合
打開CommonlyUsedCityActivity.java
然後會出現五個構造方法,分別是
//數據初始化
@Override
public void initData(Bundle savedInstanceState) {
}
@Override
public int getLayoutId() {
return R.layout.activity_commonly_used_city;
}
@Override
protected SearchCityContract.SearchCityPresenter createPresent() {
return new SearchCityContract.SearchCityPresenter();
}
/**
* 請求數據返回處理
* @param response
*/
@Override
public void getSearchCityResult(Response<SearchCityResponse> response) {
}
/**
* 網絡異常返回處理
*/
@Override
public void getDataFailed() {
dismissLoadingDialog();//關閉彈窗
ToastUtils.showShortToast(context, "網絡異常");//這裏的context是框架中封裝好的,等同於this
}
現在你可以把onCreate方法刪掉了。
首先初始化控件
@BindView(R.id.edit_query)
EditText editQuery;//輸入框
@BindView(R.id.iv_clear_search)
ImageView ivClearSearch;//清除輸入框內容的圖標
@BindView(R.id.toolbar)
Toolbar toolbar;//標題控件
@BindView(R.id.rv_commonly_used)
RecyclerView rvCommonlyUsed;//常用城市列表
@BindView(R.id.rv_search)
RecyclerView rvSearch;//搜索城市列表
@BindView(R.id.lay_normal)
LinearLayout layNormal;//常用城市爲空時展示的佈局
CommonlyCityAdapter mAdapter;//常用城市列表適配器
List<SearchCityResponse.HeWeather6Bean.BasicBean> mList = new ArrayList<>();//數據源
CommonlyCityAddAdapter mAdapterAdd;//搜索城市列表適配器
List<ResidentCity> cityList;//常用城市列表
根據常用城市數據來進行頁面控件顯示/隱藏
/**
* 根據常用城市數據來進行頁面控件顯示/隱藏
*/
private void initHideOrShow() {
ivClearSearch.setVisibility(View.GONE);//隱藏清除輸入框內容的圖標
rvSearch.setVisibility(View.GONE);//隱藏搜索結果列表
if (cityList != null && cityList.size() > 0) {//有數據
rvCommonlyUsed.setVisibility(View.VISIBLE);//顯示常用城市列表
layNormal.setVisibility(View.GONE);//隱藏沒有數據時的佈局
} else {//沒數據
rvCommonlyUsed.setVisibility(View.GONE);//隱藏常用城市列表
layNormal.setVisibility(View.VISIBLE);//顯示沒有數據時的佈局
}
}
初始化常用城市列表數據
這個方法主要是查詢表中的所有數據,有數據就渲染出來,沒有數據就更換爲相應的表示佈局,其中對item中的點擊事件做了處理,分別item的點擊和側滑菜單的點擊。
/**
* 初始化常用城市列表數據
*/
private void initCityList() {
//查詢ResidentCity表中所有數據
cityList = LitePal.findAll(ResidentCity.class);
if (cityList.size() > 0 && cityList != null) {
mAdapter = new CommonlyCityAdapter(R.layout.item_commonly_city_list, cityList);
rvCommonlyUsed.setLayoutManager(new LinearLayoutManager(context));
rvCommonlyUsed.setAdapter(mAdapter);
mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
switch (view.getId()) {
case R.id.tv_city_name:
SPUtils.putString(Constant.LOCATION, cityList.get(position).getLocation(), context);
//發送消息
EventBus.getDefault().post(new SearchCityEvent(cityList.get(position).getLocation(),
cityList.get(position).getParent_city()));
finish();
break;
case R.id.btn_delete://刪除
LitePal.delete(ResidentCity.class, cityList.get(position).getId());//刪除指定id
initCityList();
//刪除數據後判斷一下顯示和隱藏的控件
initHideOrShow();
break;
}
}
});
mAdapter.notifyDataSetChanged();
} else {
rvCommonlyUsed.setVisibility(View.GONE);
layNormal.setVisibility(View.VISIBLE);
}
}
添加城市列表item,點擊保存數據併發送事件
/**
* 添加城市列表item,點擊保存數據併發送事件
*
* @param position
*/
private void QueryWeather(int position) {
ResidentCity residentCity = new ResidentCity();
residentCity.setLocation(mList.get(position).getLocation());//地區/城市名稱
residentCity.setParent_city(mList.get(position).getParent_city());//該地區/城市的上級城市
residentCity.setAdmin_area(mList.get(position).getAdmin_area());//該地區/城市所屬行政區域
residentCity.setCnty(mList.get(position).getCnty());//該地區/城市所屬國家名稱
residentCity.save();//保存數據到數據庫中
if (residentCity.save()) {//保存成功
//然後使用之前在搜索城市天氣中寫好的代碼
SPUtils.putString(Constant.LOCATION, mList.get(position).getLocation(), context);
//發送消息
EventBus.getDefault().post(new SearchCityEvent(mList.get(position).getLocation(),
mList.get(position).getParent_city()));
finish();
} else {//保存失敗
ToastUtils.showShortToast(context, "添加城市失敗");
}
}
初始化搜索要添加的城市列表
/**
* 初始化搜索要添加的城市列表
*/
private void initQueryAddList() {
mAdapterAdd = new CommonlyCityAddAdapter(R.layout.item_commonly_city_add_list, mList);
rvSearch.setLayoutManager(new LinearLayoutManager(context));
rvSearch.setAdapter(mAdapterAdd);
//點擊item時保存到數據庫中,同時傳遞數據到主頁面查詢出天氣
mAdapterAdd.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
QueryWeather(position);
}
});
}
初始化搜索輸入框 ,輸入後馬上查詢數據,不需要額外點擊,同時查詢到數據之後隱藏默認城市列表
/**
* 初始化搜索輸入框 ,輸入後馬上查詢數據,不需要額外點擊,同時查詢到數據之後隱藏默認城市列表
*/
private void initEdit() {
editQuery.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) {
}
@Override
public void afterTextChanged(Editable s) {
if (!s.toString().equals("")) {//輸入後,顯示清除按鈕
ivClearSearch.setVisibility(View.VISIBLE);
mAdapterAdd.changTxColor(s.toString());
mPresent.searchCity(context, s.toString());//開始搜索
} else {//隱藏和顯示控件
initHideOrShow();
}
}
});
}
然後修改返回數據的方法
/**
* 請求數據返回處理
* @param response
*/
@Override
public void getSearchCityResult(Response<SearchCityResponse> response) {
dismissLoadingDialog();
if (("ok").equals(response.body().getHeWeather6().get(0).getStatus())) {
if (response.body().getHeWeather6().get(0).getBasic().size() > 0) {
rvCommonlyUsed.setVisibility(View.GONE);//隱藏常用城市列表
mList.clear();
mList.addAll(response.body().getHeWeather6().get(0).getBasic());
mAdapterAdd.notifyDataSetChanged();
rvSearch.setVisibility(View.VISIBLE);//顯示搜索城市列表
layNormal.setVisibility(View.GONE);
} else {
ToastUtils.showShortToast(context, "很抱歉,未找到相應的城市");
}
} else {
ToastUtils.showShortToast(context, CodeToStringUtils.WeatherCode(response.body().getHeWeather6().get(0).getStatus()));
}
}
再修改initData
@Override
public void initData(Bundle savedInstanceState) {
StatusBarUtil.setStatusBarColor(context, R.color.white);//白色狀態欄
StatusBarUtil.StatusBarLightMode(context);//黑色字體
Back(toolbar);
initCityList();//初始化常用城市列表
initQueryAddList();//初始化搜索城市列表
initEdit();//初始化輸入框
}
OK,整合完畢,運行,效果圖如下:
OK,寫完收工,未完待續。。。
源碼地址 歡迎Star或者Fork
聯繫郵箱 [email protected]