10天学安卓-第八天
昨天郑州雨夹雪,还有冰雹,结果小区就断电了,真是悲剧。第八天的学习就挪到今天了。
经过前几天的学习,我们了解了一些Android的基础知识,并且做出了一个也算实用的天气预报APP,对Android也算得上是入门了,那么今天我们继续改进我们的APP。
这个APP现在只能查看所在城市的天气,那么万一妹子不跟我们一个城市,我们就不能关注到妹子所在城市的天气了,那还怎么嘘寒问暖呢,这个问题是一定要解决的。
如何解决?
那就是需要在一个界面上可以选择城市了,这就用到了数据库了。
我整理了一份所有城市的名单:
北京市,天津市,上海市,重庆市,邯郸市,石家庄市,保定市,张家口市,承德市,唐山市,廊坊市,沧州市,衡水市,邢台市,秦皇岛市,朔州市,忻州市,太原市,大同市,阳泉市,晋中市,长治市,晋城市,临汾市,吕梁市,运城市,沈阳市,铁岭市,大连市,鞍山市,抚顺市,本溪市,丹东市,锦州市,营口市,阜新市,辽阳市,朝阳市,盘锦市,葫芦岛市,长春市,吉林市,延边朝鲜族自治州,四平市,通化市,白城市,辽源市,松原市,白山市,哈尔滨市,齐齐哈尔市,鸡西市,牡丹江市,七台河市,佳木斯市,鹤岗市,双鸭山市,绥化市,黑河市,大兴安岭地区,伊春市,大庆市,南京市,无锡市,镇江市,苏州市,南通市,扬州市,盐城市,徐州市,淮安市,连云港市,常州市,泰州市,宿迁市,舟山市,衢州市,杭州市,湖州市,嘉兴市,宁波市,绍兴市,温州市,丽水市,金华市,台州市,合肥市,芜湖市,蚌埠市,淮南市,马鞍山市,淮北市,铜陵市,安庆市,黄山市,滁州市,阜阳市,宿州市,巢湖市,六安市,亳州市,池州市,宣城市,福州市,厦门市,宁德市,莆田市,泉州市,漳州市,龙岩市,三明市,南平市,鹰潭市,新余市,南昌市,九江市,上饶市,抚州市,宜春市,吉安市,赣州市,景德镇市,萍乡市,菏泽市,济南市,青岛市,淄博市,德州市,烟台市,潍坊市,济宁市,泰安市,临沂市,滨州市,东营市,威海市,枣庄市,日照市,莱芜市,聊城市,商丘市,郑州市,安阳市,新乡市,许昌市,平顶山市,信阳市,南阳市,开封市,洛阳市,济源市,焦作市,鹤壁市,濮阳市,周口市,漯河市,驻马店市,三门峡市,武汉市,襄樊市,鄂州市,孝感市,黄冈市,黄石市,咸宁市,荆州市,宜昌市,恩施土家族苗族自治州,神农架林区,十堰市,随州市,荆门市,仙桃市,天门市,潜江市,岳阳市,长沙市,湘潭市,株洲市,衡阳市,郴州市,常德市,益阳市,娄底市,邵阳市,湘西土家族苗族自治州,张家界市,怀化市,永州市,广州市,汕尾市,阳江市,揭阳市,茂名市,惠州市,江门市,韶关市,梅州市,汕头市,深圳市,珠海市,佛山市,肇庆市,湛江市,中山市,河源市,清远市,云浮市,潮州市,东莞市,兰州市,金昌市,白银市,天水市,嘉峪关市,武威市,张掖市,平凉市,酒泉市,庆阳市,定西市,陇南市,临夏回族自治州,甘南藏族自治州,成都市,攀枝花市,自贡市,绵阳市,南充市,达州市,遂宁市,广安市,巴中市,泸州市,宜宾市,资阳市,内江市,乐山市,眉山市,凉山彝族自治州,雅安市,甘孜藏族自治州,阿坝藏族羌族自治州,德阳市,广元市,贵阳市,遵义市,安顺市,黔南布依族苗族自治州,黔东南苗族侗族自治州,铜仁地区,毕节地区,六盘水市,黔西南布依族苗族自治州,海口市,三亚市,五指山市,琼海市,儋州市,文昌市,万宁市,东方市,澄迈县,定安县,屯昌县,临高县,白沙黎族自治县,昌江黎族自治县,乐东黎族自治县,陵水黎族自治县,保亭黎族苗族自治县,琼中黎族苗族自治县,西双版纳傣族自治州,德宏傣族景颇族自治州,昭通市,昆明市,大理白族自治州,红河哈尼族彝族自治州,曲靖市,保山市,文山壮族苗族自治州,玉溪市,楚雄彝族自治州,普洱市,临沧市,怒江傈傈族自治州,迪庆藏族自治州,丽江市,海北藏族自治州,西宁市,海东地区,黄南藏族自治州,海南藏族自治州,果洛藏族自治州,玉树藏族自治州,海西蒙古族藏族自治州,西安市,咸阳市,延安市,榆林市,渭南市,商洛市,安康市,汉中市,宝鸡市,铜川市,防城港市,南宁市,崇左市,来宾市,柳州市,桂林市,梧州市,贺州市,贵港市,玉林市,百色市,钦州市,河池市,北海市,拉萨市,日喀则地区,山南地区,林芝地区,昌都地区,那曲地区,阿里地区,银川市,石嘴山市,吴忠市,固原市,中卫市,塔城地区,哈密地区,和田地区,阿勒泰地区,克孜勒苏柯尔克孜自治州,博尔塔拉蒙古自治州,克拉玛依市,乌鲁木齐市,石河子市,昌吉回族自治州,五家渠市,吐鲁番地区,巴音郭楞蒙古自治州,阿克苏地区,阿拉尔市,喀什地区,图木舒克市,伊犁哈萨克自治州,呼伦贝尔市,呼和浩特市,包头市,乌海市,乌兰察布市,通辽市,赤峰市,鄂尔多斯市,巴彦淖尔市,锡林郭勒盟,兴安盟,阿拉善盟,台北市,高雄市,基隆市,台中市,台南市,新竹市,嘉义市,澳门特别行政区,香港特别行政区
这里基本囊括了国内大部分城市,我们做一个界面从这里选择就好了。
开工。
新建一个Activity,取名为ChooseCityActivity,并且修改AndroidManifest.xml,在application节点添加一个activity,如下:
<activity android:name="com.demo.weather.ChooseCityActivity"></activity>
这样就添加了一个新的Activity到APP中,不过现在还是空的,没有内容,不着急,我们过会儿会添加进去。
然后修改MainActivity,添加两个方法:
@Override public boolean onCreateOptionsMenu( Menu menu ) { super.onCreateOptionsMenu( menu ); menu.add( Menu.NONE, Menu.FIRST + 1, 0, "添加城市" ).setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS ); return true; } @Override public boolean onOptionsItemSelected( MenuItem item ) { if( item.getItemId() == Menu.FIRST + 1 ) { startActivityForResult( new Intent( getApplicationContext(), ChooseCityActivity.class ), 99 ); } return super.onOptionsItemSelected( item ); }
第一个方法的作用是在右上角添加了一个名为“添加城市”的按钮,第二个方法的作用就是点击了“添加城市”的按钮后,会跳转到我们新建的那个Activity。
ActionBar
我们这里用到了ActionBar,一个完整的ActionBar是这样子的:
一共分为四部分,
1. App Icon,也可替换为任意的图标
2. View Controller,这部分为下拉菜单或者Tab选项卡,或者是App Title
3. Action Item,这部分就是我们上面那两个方法进行设置的,每一个Action Item都会包含文字、图标
4. Action Overflow,如果第三部分的Action Item比较多显示不全的时候,就会在这里以列表的形式展示
每一个Action Item都可以通过调用setShowAsAction来设置它的展示方式,在前面我们添加了一个Action Item,并且设置为SHOW_AS_ACTION_ALWAYS,这就要求这个Item是必须显示在第三部分的。
了解了一些ActionBar的基础知识后,我们继续之前的学习。
现在的界面应该是这个样子的:
点击“添加城市”后,就跳转到了一个空白页面。
跳转+传值
Android中页面跳转提供了两种方法,startActivityForResult和startActivity,这两个方法区别在于能否将结果回传。
传值
假如MainActivity需要向ChooseCityActivity传值,可以这么做:
Intent intent = new Intent( getApplicationContext(), ChooseCityActivity.class ); intent.putExtra( "key", "value" ); startActivityForResult( intent, 99 );
并且在ChooseCityActivity的onCreate方法中进行接收:
protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); String value = getIntent().getStringExtra( "key" ); Log.v( "ChooseCityActivity", value ); }
回传
假如这个时候ChooseCityActivity需要把一些数据回传,可以这么做:
Intent intent = new Intent(); intent.putExtra( "key2", "value2" ); setResult( RESULT_OK, intent ); super.finish();
并且在MainActivity添加onActivityResult方法:
protected void onActivityResult( int requestCode, int resultCode, Intent data ) { super.onActivityResult( requestCode, resultCode, data ); Log.v( "onActivityResult", data.getStringExtra( "key2" ) ); }
了解了这些内容,Activity间的传值就没有问题了,可以传很多类型的数据,包括基本类型以及实现了Serializable或者Parcelable接口的类型,Serializable是Java标准的序列化接口,Parcelable是Google定义的效率更高的序列化接口,如果是复杂类型,推荐大家使用Parcelable接口。
希望大家掌握这些知识点,这些内容会在实际的应用中频繁使用。
我们继续之前的学习,这次的目标是在新的界面实现选择城市并且传回MainActivity,为了做到这一点,这个界面需要有一个可以输入关键字的文本框已经一个城市列表,知道要做什么了,就动起手来吧。
还记得怎么新建一个Layout吗?如果忘记了,请看这里:http://liuzhibang.cn/?p=380
这次我们新建的Layout可以起名为:activity_choose_city,并且我们这次尝试使用LinearLayout,
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <EditText android:id="@+id/choose_key" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" > </EditText> <ListView android:id="@+id/choose_list" android:layout_width="match_parent" android:layout_height="wrap_content" > </ListView> </LinearLayout>
这个界面有一个文本框和一个列表,并且是垂直排列,占满了整个屏幕。
界面已经好了,接下来我们需要把数据以某种方式存储起来,并且在文本框输入了关键字后进行筛选,把筛选的城市显示到列表中。
怎样把那么些个城市数据存储起来?什么时候进行这个操作呢?
Application
为什么我们的手机应用都叫APP呢?
那是因为我们所做出来的每一个应用都是一个Android Application,而APP就是Application的缩写,可见Application的重要性了。
实际上,Application是Android框架的系统组件,当应用启动的时候就会创建一个且只有一个Application对象,用来存储APP的一些信息,所有说Application是一个单例模式。既然是单例对象,那么我们就可以在这里保存一些全局的数据,例如我们的城市数据;或者进行一些必要的初始化操作,比如把我们的城市数据存储起来。
要做到这些,只需要继承Application这个类,实现我们自己的Application就可以了。
新建一个类WeatherApplication,让它继承Application,并且实现它的onCreate方法:
public class WeatherApplication extends Application { @Override public void onCreate() { super.onCreate(); } }
这个onCreate就是实际意义的整个APP的入口,每次启动都会执行这个方法,我们在这里把城市数据存储到数据库中。
然后修改AndroidManifest.xml,在application节点添加:
android:name="com.demo.weather.WeatherApplication"
这就指定了这个应用的Application就是WeatherApplication,然后修改WeatherApplication,进行我们的初始化数据的操作。
public class WeatherApplication extends Application { private String citys = "北京市,天津市,上海市,重庆市,邯郸市,石家庄市,保定市,张家口市,承德市,唐山市,廊坊市,沧州市,衡水市,邢台市,秦皇岛市,朔州市,忻州市,太原市,大同市,阳泉市,晋中市,长治市,晋城市,临汾市,吕梁市,运城市,沈阳市,铁岭市,大连市,鞍山市,抚顺市,本溪市,丹东市,锦州市,营口市,阜新市,辽阳市,朝阳市,盘锦市,葫芦岛市,长春市,吉林市,延边朝鲜族自治州,四平市,通化市,白城市,辽源市,松原市,白山市,哈尔滨市,齐齐哈尔市,鸡西市,牡丹江市,七台河市,佳木斯市,鹤岗市,双鸭山市,绥化市,黑河市,大兴安岭地区,伊春市,大庆市,南京市,无锡市,镇江市,苏州市,南通市,扬州市,盐城市,徐州市,淮安市,连云港市,常州市,泰州市,宿迁市,舟山市,衢州市,杭州市,湖州市,嘉兴市,宁波市,绍兴市,温州市,丽水市,金华市,台州市,合肥市,芜湖市,蚌埠市,淮南市,马鞍山市,淮北市,铜陵市,安庆市,黄山市,滁州市,阜阳市,宿州市,巢湖市,六安市,亳州市,池州市,宣城市,福州市,厦门市,宁德市,莆田市,泉州市,漳州市,龙岩市,三明市,南平市,鹰潭市,新余市,南昌市,九江市,上饶市,抚州市,宜春市,吉安市,赣州市,景德镇市,萍乡市,菏泽市,济南市,青岛市,淄博市,德州市,烟台市,潍坊市,济宁市,泰安市,临沂市,滨州市,东营市,威海市,枣庄市,日照市,莱芜市,聊城市,商丘市,郑州市,安阳市,新乡市,许昌市,平顶山市,信阳市,南阳市,开封市,洛阳市,济源市,焦作市,鹤壁市,濮阳市,周口市,漯河市,驻马店市,三门峡市,武汉市,襄樊市,鄂州市,孝感市,黄冈市,黄石市,咸宁市,荆州市,宜昌市,恩施土家族苗族自治州,神农架林区,十堰市,随州市,荆门市,仙桃市,天门市,潜江市,岳阳市,长沙市,湘潭市,株洲市,衡阳市,郴州市,常德市,益阳市,娄底市,邵阳市,湘西土家族苗族自治州,张家界市,怀化市,永州市,广州市,汕尾市,阳江市,揭阳市,茂名市,惠州市,江门市,韶关市,梅州市,汕头市,深圳市,珠海市,佛山市,肇庆市,湛江市,中山市,河源市,清远市,云浮市,潮州市,东莞市,兰州市,金昌市,白银市,天水市,嘉峪关市,武威市,张掖市,平凉市,酒泉市,庆阳市,定西市,陇南市,临夏回族自治州,甘南藏族自治州,成都市,攀枝花市,自贡市,绵阳市,南充市,达州市,遂宁市,广安市,巴中市,泸州市,宜宾市,资阳市,内江市,乐山市,眉山市,凉山彝族自治州,雅安市,甘孜藏族自治州,阿坝藏族羌族自治州,德阳市,广元市,贵阳市,遵义市,安顺市,黔南布依族苗族自治州,黔东南苗族侗族自治州,铜仁地区,毕节地区,六盘水市,黔西南布依族苗族自治州,海口市,三亚市,五指山市,琼海市,儋州市,文昌市,万宁市,东方市,澄迈县,定安县,屯昌县,临高县,白沙黎族自治县,昌江黎族自治县,乐东黎族自治县,陵水黎族自治县,保亭黎族苗族自治县,琼中黎族苗族自治县,西双版纳傣族自治州,德宏傣族景颇族自治州,昭通市,昆明市,大理白族自治州,红河哈尼族彝族自治州,曲靖市,保山市,文山壮族苗族自治州,玉溪市,楚雄彝族自治州,普洱市,临沧市,怒江傈傈族自治州,迪庆藏族自治州,丽江市,海北藏族自治州,西宁市,海东地区,黄南藏族自治州,海南藏族自治州,果洛藏族自治州,玉树藏族自治州,海西蒙古族藏族自治州,西安市,咸阳市,延安市,榆林市,渭南市,商洛市,安康市,汉中市,宝鸡市,铜川市,防城港市,南宁市,崇左市,来宾市,柳州市,桂林市,梧州市,贺州市,贵港市,玉林市,百色市,钦州市,河池市,北海市,拉萨市,日喀则地区,山南地区,林芝地区,昌都地区,那曲地区,阿里地区,银川市,石嘴山市,吴忠市,固原市,中卫市,塔城地区,哈密地区,和田地区,阿勒泰地区,克孜勒苏柯尔克孜自治州,博尔塔拉蒙古自治州,克拉玛依市,乌鲁木齐市,石河子市,昌吉回族自治州,五家渠市,吐鲁番地区,巴音郭楞蒙古自治州,阿克苏地区,阿拉尔市,喀什地区,图木舒克市,伊犁哈萨克自治州,呼伦贝尔市,呼和浩特市,包头市,乌海市,乌兰察布市,通辽市,赤峰市,鄂尔多斯市,巴彦淖尔市,锡林郭勒盟,兴安盟,阿拉善盟,台北市,高雄市,基隆市,台中市,台南市,新竹市,嘉义市,澳门特别行政区,香港特别行政区"; private DbUtils dbUtils; private static WeatherApplication instance; public static WeatherApplication getInstance() { return instance; } @Override public void onCreate() { super.onCreate(); instance = this; dbUtils = DbUtils.create( this ); if( !readInit() ) { initCity(); } } private void initCity() { boolean isSuccess = true; String[] cityArray = citys.split( "," ); for( String city : cityArray ) { CityBean cityBean = new CityBean(); cityBean.setCityName( city ); try { dbUtils.save( cityBean ); } catch( DbException e ) { isSuccess = false; } } saveInit( isSuccess ); } private void saveInit( boolean isInited ) { SharedPreferences sharedPreferences = getSharedPreferences( "weather", Context.MODE_PRIVATE ); Editor editor = sharedPreferences.edit(); editor.putBoolean( "isInited", isInited ); editor.commit(); } private boolean readInit() { SharedPreferences sharedPreferences = getSharedPreferences( "weather", Context.MODE_PRIVATE ); return sharedPreferences.getBoolean( "isInited", false ); } }
这里用到了CityBean这个类,
public class CityBean { private int id; private String cityName; public int getId() { return id; } public void setId( int id ) { this.id = id; } public String getCityName() { return cityName; } public void setCityName( String cityName ) { this.cityName = cityName; } }
这个类是进行数据库操作的类,而我们的数据库操作使用了第三方类库,根据类库的要求,我们这里有一个名为id的字段。
做好了这些后,运行程序。
有没有发现特别慢?那是因为我们在启动的时候进行了数据库操作,并且插入了300多条城市数据,这个过程是比较耗时的。
等一切现实正常后,我们退出程序,再重新启动,有没有发现变快了?那是因为我们只在第一次启动的时候进行了数据存储。
这里面用到了一个小技巧:
在数据存储操作后,我们另外在Preference中写入了一个名为isInited的Boolean类型的数据,如果成功,我们就不再进行这个操作了。
查看数据库文件
如果你的手机是Root过的,那么你还可以把数据库导出,放到电脑上用工具查看数据是否正确。另外,有一些手机APP,例如Root Explorer也提供了在手机上直接查看数据库的功能,但是毕竟不如在电脑上清晰直观。
每一个APP的数据库、缓存文件都存在/data/data/[package]下面,比如我们的APP,就存在/data/data/com.demo.weather这个目录下面。
需要注意,这个目录在APP被卸载之后会被清空。
稍微说明一下这几个目录,
cache:在这里可以存放临时缓存,
databases:就是数据库,我们的数据库文件就在这个目录
files:一般存放一些长时间保存的数据
lib:APP用到库文件
shared_prefs:这个就是SharedPreferences这个类保存的数据
跟我们这个APP相关的就只要两个目录:databases和shared_prefs,打开它们:
很清楚,一个数据库文件,另外一个是xml文件。大家可以通过各种办法把这两个文件导出到电脑来查看。
接下来的工作就是在ChooseCityActivity中选择城市了。
首先把这个Activity和我们之前新建的Layout联系起来,
public class ChooseCityActivity extends Activity { @ViewInject( R.id.choose_key ) private EditText edtKey; @ViewInject( R.id.choose_list ) private ListView lstCity; @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setContentView( R.layout.activity_choose_city ); ViewUtils.inject( this ); } }
再回想一下我们的需求:输入关键字后,在列表中显示相应的城市
这需要我们监听文本框的输入事件,可以这么做:
public class ChooseCityActivity extends Activity { @ViewInject( R.id.choose_key ) private EditText edtKey; @ViewInject( R.id.choose_list ) private ListView lstCity; @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setContentView( R.layout.activity_choose_city ); ViewUtils.inject( this ); edtKey.addTextChangedListener( new TextFilter() ); } private void search( String key ) { } class TextFilter implements TextWatcher { @Override public void beforeTextChanged( CharSequence s, int start, int count, int after ) { } @Override public void onTextChanged( CharSequence s, int start, int before, int count ) { String key = edtKey.getText().toString(); search( key ); } @Override public void afterTextChanged( Editable s ) { } } }
给EditText添加一个TextWatcher事件,就可以监听它的文本变化,当文本变化后,会调用search方法,这个方法将会查询数据库,并且把查询结果显示到ListView中。
将会用到数据库查询操作,联想到之前我们已经进行过存储操作了,我们可以把在Application里用到的DbUtils公开出来,在WeatherApplication添加方法:
public DbUtils getDbUtil() { if( dbUtils == null ) { dbUtils = DbUtils.create( this ); } return dbUtils; }
关于如何把数据显示到ListView中,相信大家还记得之前如何把天气显示出来的吧。
新建一个Layout,名为item_city:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/item_city" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_margin="15dp" android:textColor="#000000" android:textSize="16sp" /> </RelativeLayout>
然后新建一个Adapter,名为CityAdapter:
public class CityAdapter extends BaseAdapter { private List<CityBean> citys; private LayoutInflater inflater; public CityAdapter( Context context, List<CityBean> citys ) { this.citys = citys; inflater = LayoutInflater.from( context ); } @Override public int getCount() { return citys.size(); } @Override public Object getItem( int position ) { return citys.get( position ); } @Override public long getItemId( int position ) { return position; } @Override public View getView( int position, View convertView, ViewGroup parent ) { convertView = inflater.inflate( R.layout.item_city, null ); CityBean bean = (CityBean)getItem( position ); TextView txtCity = (TextView)convertView.findViewById( R.id.item_city ); txtCity.setText( bean.getCityName() ); return convertView; } }
最后修改ChooseCityActivity的search方法:
private void search( String key ) { DbUtils dbUtils = WeatherApplication.getInstance().getDbUtil(); try { List<CityBean> citys = dbUtils.findAll( Selector.from( CityBean.class ).where( "cityName", "like", "%" + key + "%" ) ); CityAdapter adapter = new CityAdapter( getApplicationContext(), citys ); lstCity.setAdapter( adapter ); } catch( DbException e ) { e.printStackTrace(); } }
这个时候的ChooseCityActivity是这样的:
public class ChooseCityActivity extends Activity { @ViewInject( R.id.choose_key ) private EditText edtKey; @ViewInject( R.id.choose_list ) private ListView lstCity; @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setContentView( R.layout.activity_choose_city ); ViewUtils.inject( this ); edtKey.addTextChangedListener( new TextFilter() ); } private void search( String key ) { DbUtils dbUtils = WeatherApplication.getInstance().getDbUtil(); try { List<CityBean> citys = dbUtils.findAll( Selector.from( CityBean.class ).where( "cityName", "like", "%" + key + "%" ) ); CityAdapter adapter = new CityAdapter( getApplicationContext(), citys ); lstCity.setAdapter( adapter ); } catch( DbException e ) { e.printStackTrace(); } } class TextFilter implements TextWatcher { @Override public void beforeTextChanged( CharSequence s, int start, int count, int after ) { } @Override public void onTextChanged( CharSequence s, int start, int before, int count ) { String key = edtKey.getText().toString(); search( key ); } @Override public void afterTextChanged( Editable s ) { } } }
来运行一下吧,点击“添加城市”,在输入框中输入文字后,列表是不是有了内容了?
我的是这样的:
到这里,我们的工作就完成了一多半了,剩下的就是把选择的城市回传给MainActivity,并且更新天气数据。
再接再厉,我们把最后这部分完成再休息。
关于如何把选择的城市回传,之前已经了解了,这次实践一下:
@OnItemClick( R.id.choose_list ) public void onItemClick( AdapterView<?> parent, View view, int position, long id ) { CityBean cityBean = (CityBean)parent.getAdapter().getItem( position ); Intent intent = new Intent(); intent.putExtra( "selectedCity", cityBean.getCityName() ); setResult( RESULT_OK, intent ); super.finish(); }
这个方法是ListView中的Item点击后触发,作用就是把选中的城市从Adapter中取出来,并且回传。
然后在MainActivity中修改onActivityResult方法:
protected void onActivityResult( int requestCode, int resultCode, Intent data ) { super.onActivityResult( requestCode, resultCode, data ); if( resultCode == RESULT_OK ) { String selectedCity = data.getStringExtra( "selectedCity" ); getWeather( selectedCity ); } }
在这里先判断了一下resultCode时候是正确返回,然后取出来城市并且调用了getWeather方法获取天气。
打完收工,运行一下吧。
有没有发现这个情况:
选择了别的城市后,再返回回来,天气好像是发生了变化,但是标题上还是所在地的名字,为什么会有这个情况呢?我们明明在getWeather方法中设置了标题啊。
别急,让我们回想一下Activity的生命周期中的第3条:
>> 3. Activity重新获得焦点的时候,会依次执行onRestart、onStart和onResume
当返回到MainActivity的时候,这个顺序有发生了一点变化,它会这样执行:
onActivityResult -> onRestart -> onStart -> onResume
看到了吧,onActivityResult是最先执行,这个时候我们调用getWeather会被onStart调用的getWeather给覆盖掉,怎么解决呢?
我这里有两种办法:
1. 我们不在onStart中获取天气,而在onCreate中获取
2. 修改一下getWeather,把城市作为类的成员变量,而不是方法参数,这样保证城市的一致
我们先采用第二种办法吧。
public class MainActivity extends Activity { @ViewInject( R.id.weather_list ) private ListView lstWeather; private WeatherAdapter adapter; private BaiduData data; private List<WeatherDataBean> datas; private LocationClient mLocationClient; private BDLocationListener myListener; private String city; @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setContentView( R.layout.activity_main ); Log.v( "WeatherAPP", "onCreate" ); ViewUtils.inject( this ); datas = new ArrayList<WeatherDataBean>(); adapter = new WeatherAdapter( getApplicationContext(), datas ); lstWeather.setAdapter( adapter ); initLocationClient(); mLocationClient.start(); } @Override public boolean onCreateOptionsMenu( Menu menu ) { super.onCreateOptionsMenu( menu ); menu.add( Menu.NONE, Menu.FIRST + 1, 0, "添加城市" ).setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS ); return true; } @Override public boolean onOptionsItemSelected( MenuItem item ) { if( item.getItemId() == Menu.FIRST + 1 ) { Intent intent = new Intent( getApplicationContext(), ChooseCityActivity.class ); intent.putExtra( "key", "value" ); startActivityForResult( intent, 99 ); } return super.onOptionsItemSelected( item ); } @Override protected void onActivityResult( int requestCode, int resultCode, Intent data ) { super.onActivityResult( requestCode, resultCode, data ); if( resultCode == RESULT_OK ) { city = data.getStringExtra( "selectedCity" ); } } private void getWeather() { setTitle( city + "天气" ); HttpUtils http = new HttpUtils(); RequestParams params = new RequestParams(); params.addQueryStringParameter( "location", city ); params.addQueryStringParameter( "output", "json" ); params.addQueryStringParameter( "ak", "YknGmxIoPugT7YrNrG955YLS" ); http.send( HttpMethod.GET, "http://api.map.baidu.com/telematics/v3/weather", params, new RequestCallBack<String>() { @Override public void onSuccess( ResponseInfo<String> responseInfo ) { String weather = responseInfo.result; Gson gson = new Gson(); data = gson.fromJson( weather, BaiduData.class ); datas.clear(); datas.addAll( data.getResults().get( 0 ).getWeather_data() ); adapter.notifyDataSetChanged(); Log.v( "onSuccess", data.toString() ); } @Override public void onFailure( HttpException arg0, String arg1 ) { Log.v( "onFailure", arg1 ); } } ); } private void initLocationClient() { mLocationClient = new LocationClient( getApplicationContext() ); // 声明LocationClient类 myListener = new MyLocationListener(); LocationClientOption option = new LocationClientOption(); option.setLocationMode( LocationMode.Hight_Accuracy ); option.setIsNeedAddress( true ); mLocationClient.setLocOption( option ); mLocationClient.registerLocationListener( myListener ); } @Override protected void onStop() { Log.v( "WeatherAPP", "onStop" ); super.onStop(); mLocationClient.stop(); } @Override protected void onPause() { Log.v( "WeatherAPP", "onPause" ); super.onPause(); } @Override protected void onRestart() { Log.v( "WeatherAPP", "onRestart" ); super.onRestart(); } @Override protected void onResume() { Log.v( "WeatherAPP", "onResume" ); super.onResume(); } @Override protected void onStart() { Log.v( "WeatherAPP", "onStart" ); super.onStart(); if( city == null || city.length() == 0 ) { city = readCity(); } if( city != null && city.length() > 0 ) { getWeather(); } } @Override protected void onDestroy() { Log.v( "WeatherAPP", "onDestroy" ); super.onDestroy(); } public class MyLocationListener implements BDLocationListener { @Override public void onReceiveLocation( BDLocation location ) { city = location.getCity(); String localCity = readCity(); if( !localCity.equals( city ) ) { saveCity( city ); getWeather(); } } } private void saveCity( String city ) { SharedPreferences sharedPreferences = getSharedPreferences( "weather", Context.MODE_PRIVATE ); Editor editor = sharedPreferences.edit(); editor.putString( "city", city ); editor.commit(); } private String readCity() { SharedPreferences sharedPreferences = getSharedPreferences( "weather", Context.MODE_PRIVATE ); return sharedPreferences.getString( "city", "" ); } }
这里修改了onStart方法:
protected void onStart() { Log.v( "WeatherAPP", "onStart" ); super.onStart(); if( city == null || city.length() == 0 ) { city = readCity(); } if( city != null && city.length() > 0 ) { getWeather(); } }
如果城市为空,才获取我们在Preference中保存的城市,这是因为onActivityResult执行在前,已经把city变量赋值了,然后在 执行onStart的时候确保这个值不被覆盖。
大功告成了。お疲れ様です。(辛苦了)
就这样,天气就变成了我们选择的城市的了。
等等,说好的“添加城市”呢?
我想同事查看两个城市,而不是一个一个看。
别慌,怎么同时查看两个城市,我们明天继续。
附件是本次的工程文件,点击下载 http://pan.baidu.com/s/1bntoAGR 。
此系列文章系本人原创,如需转载,请注明出处 www.liuzhibang.cn