接着昨天的任务,我们今天实现左右滑动可以切换城市的功能。

这里就需要引入新的控件了,Android给我们提供了ViewPager,我们就使用这个,同时,显示天气的界面我们也不再使用Activity,而改为Fragment。

Fragment

Fragment可以认为是可复用的UI组件,有自己的布局和完整的生命周期,可以处理本身的事件,但是必须依存于Activity,不能脱离Activity而存在。

08162501-0d1530c0e072468b8b0a68c1c8913bce

 

可以看出来,Fragment的生命周期跟Activity非常相似,并且会随着Activity的销毁而销毁。

 

下面,我们来战。

首先,新建一个Fragment的子类,取名为WeatherFragment。

public class WeatherFragment extends Fragment
{
    @Override
    public void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
    }

    @Override
    public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
    {
        return super.onCreateView( inflater, container, savedInstanceState );
    }
}

这是用来显示天气的界面,而我们之前是直接在Activity中显示的,需要把这部分代码给移植到Fragment中。

这是个麻烦的过程,不过不要紧,慢慢来。

新建一个Layout,取名为frag_weather.xml,然后把activity_main.xml中的代码给复制过来,

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"  >

    <ListView
        android:id="@+id/weather_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" >
    </ListView>

</RelativeLayout>

 

我们让WeatherFragment使用新的Layout,再把MainActivity中关于天气的代码移植到WeatherFragment中,

public class WeatherFragment extends Fragment
{
    @ViewInject( R.id.weather_list )
    private ListView lstWeather;

    private WeatherAdapter adapter;
    private BaiduData data;

    private List<WeatherDataBean> datas;
    private String city;

    public void setCity( String city )
    {
        this.city = city;
    }

    @Override
    public void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );

        datas = new ArrayList<WeatherDataBean>();
        adapter = new WeatherAdapter( getActivity(), datas );
    }

    @Override
    public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
    {
        View view = inflater.inflate( R.layout.frag_weather, null );
        ViewUtils.inject( this, view );

        lstWeather.setAdapter( adapter );

        getWeather();

        return view;
    }

    private void getWeather()
    {
        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 );
            }
        } );
    }
}

 

然后,修改主页面activity_main.xml为:

<?xml version="1.0"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:id="@+id/viewGroup"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="30dp"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >
    </LinearLayout>

</RelativeLayout>

这里我们引入了ViewPager,并且还有一个LinearLayout,其中ViewPager我们用来显示天气,Linearlayout用来作为指示器,表示我们当前所选城市的次序。

 

之后,修改我们主页面的代码,主界面现在的作用主要是两个:

1. 初次启动的时候,获取所在地城市

2. 处理切换Fragment的逻辑

3. 处理页面跳转/返回的逻辑

public class MainActivity extends FragmentActivity
{
    @ViewInject( R.id.viewPager )
    private ViewPager pager;

    @ViewInject( R.id.viewGroup )
    private LinearLayout layout;

    private MyAdapter mAdapter;
    private List<SelectCityBean> citys;

    private LocationClient mLocationClient;
    private BDLocationListener myListener;

    private List<ImageView> imageViews;

    @Override
    protected void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );

        Log.v( "WeatherAPP", "onCreate" );

        ViewUtils.inject( this );

        imageViews = new ArrayList<ImageView>();

        citys = readCity();
        mAdapter = new MyAdapter( getSupportFragmentManager() );
        pager.setAdapter( mAdapter );
        pager.setOnPageChangeListener( new OnPageChangeListener()
        {
            @Override
            public void onPageSelected( int arg0 )
            {
                setTitle( citys.get( arg0 ).getCityName() + "天气" );
                setImageBackground( arg0 );
            }

            @Override
            public void onPageScrolled( int arg0, float arg1, int arg2 )
            {
            }

            @Override
            public void onPageScrollStateChanged( int arg0 )
            {
            }
        } );

        if( citys == null || citys.size() == 0 )
        {
            citys = new ArrayList<SelectCityBean>();
            initLocationClient();
            mLocationClient.start();
        }

        showIndicator( 0 );
    }

    private void showIndicator( int position )
    {
        layout.removeAllViews();
        imageViews = new ArrayList<ImageView>();

        pager.setCurrentItem( position );

        for( int i = 0; i < citys.size(); i++ )
        {
            ImageView imageView = new ImageView( this );
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 20, 20 );
            lp.leftMargin = 5;
            imageView.setLayoutParams( lp );
            imageViews.add( imageView );
            if( i == position )
            {
                setTitle( citys.get( position ).getCityName() + "天气" );
                imageView.setBackgroundResource( R.drawable.page_indicator_focused );
            }
            else
            {
                imageView.setBackgroundResource( R.drawable.page_indicator_unfocused );
            }

            layout.addView( imageView );
        }
    }

    private void setImageBackground( int selectItems )
    {
        for( int i = 0; i < imageViews.size(); i++ )
        {
            if( i == selectItems )
            {
                imageViews.get( i ).setBackgroundResource( R.drawable.page_indicator_focused );
            }
            else
            {
                imageViews.get( i ).setBackgroundResource( R.drawable.page_indicator_unfocused );
            }
        }
    }

    @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 )
        {
            String city = data.getStringExtra( "selectedCity" );
            addCity( city );
        }
    }

    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();
        if( mLocationClient != null ) 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();
    }

    @Override
    protected void onDestroy()
    {
        Log.v( "WeatherAPP", "onDestroy" );
        super.onDestroy();
    }

    public class MyLocationListener implements BDLocationListener
    {
        @Override
        public void onReceiveLocation( BDLocation location )
        {
            String city = location.getCity();
            addCity( city );
        }
    }

    private void addCity( String city )
    {
        SelectCityBean cityBean = new SelectCityBean();
        cityBean.setCityName( city );
        saveCity( cityBean );

        if( citys == null ) citys = new ArrayList<SelectCityBean>();
        citys.add( cityBean );
        mAdapter.notifyDataSetChanged();
        showIndicator( citys.size() - 1 );
    }

    private void saveCity( SelectCityBean city )
    {
        DbUtils dbUtils = WeatherApplication.getInstance().getDbUtil();
        try
        {
            dbUtils.save( city );
        }
        catch( DbException e )
        {
        }
    }

    private List<SelectCityBean> readCity()
    {
        DbUtils dbUtils = WeatherApplication.getInstance().getDbUtil();
        try
        {
            return dbUtils.findAll( SelectCityBean.class );
        }
        catch( DbException e )
        {
            return null;
        }
    }

    public class MyAdapter extends FragmentStatePagerAdapter
    {
        public MyAdapter( FragmentManager fm )
        {
            super( fm );
        }

        @Override
        public Fragment getItem( int arg0 )
        {
            WeatherFragment fragment = new WeatherFragment();
            fragment.setCity( citys.get( arg0 ).getCityName() );
            return fragment;
        }

        @Override
        public int getItemPosition( Object object )
        {
            return POSITION_NONE;
        }

        @Override
        public int getCount()
        {
            if( citys == null ) return 0;
            return citys.size();
        }
    }
}

 

基本上面目全非了,跟之前的代码完全不一样了,这里面的主要变动为:

1. saveCity、readCity不再从Preference中获取数据了,而改为从数据库获取

2. 增加了MyAdapter以及相关的ViewPager的逻辑

 

这里还用到了一个新的SelectCityBean以及两个图片资源,

public class SelectCityBean
{
    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;
    }
}

 

两个图片资源分别代表了当前城市以及其他城市,

page_indicator_focusedpage_indicator_unfocused

 

完成之后,运行来看看,我这边的效果是这样的:

device-2015-01-27-084239

 

 

你可以试着添加城市看看,是不是这样的效果。

 

今天的内容比较少,代码比较多,大家多多练习一下。

 

附件是本次的工程文件,点击下载 http://pan.baidu.com/s/1sj2V5fB 。

此系列文章系本人原创,如需转载,请注明出处 www.liuzhibang.cn

 

昨天郑州雨夹雪,还有冰雹,结果小区就断电了,真是悲剧。第八天的学习就挪到今天了。

经过前几天的学习,我们了解了一些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是这样子的:

2224215266067739940

一共分为四部分,

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的基础知识后,我们继续之前的学习。

现在的界面应该是这个样子的:

device-2015-01-25-143330

 

点击“添加城市”后,就跳转到了一个空白页面。

 

跳转+传值

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被卸载之后会被清空。

device-2015-01-25-143934

 

稍微说明一下这几个目录,

cache:在这里可以存放临时缓存,

databases:就是数据库,我们的数据库文件就在这个目录

files:一般存放一些长时间保存的数据

lib:APP用到库文件

shared_prefs:这个就是SharedPreferences这个类保存的数据

 

跟我们这个APP相关的就只要两个目录:databases和shared_prefs,打开它们:

device-2015-01-25-145619 device-2015-01-25-145633

很清楚,一个数据库文件,另外一个是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 )
        {
        }
    }
}

 

来运行一下吧,点击“添加城市”,在输入框中输入文字后,列表是不是有了内容了?

我的是这样的:

device-2015-01-25-145816

 

到这里,我们的工作就完成了一多半了,剩下的就是把选择的城市回传给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

我们上次学习了百度定位以及SharedPreferences的使用,不知道大家有没有注意到我们新加了一个方法:

    protected void onStop()
    {
        super.onStop();
        mLocationClient.stop();
    }

 

这个方法的作用是在界面停止的时候,同时停止百度定位功能。

 

联想到我们还有onCreate,那么这两个方法是做什么用的?是什么原理呢?

这就需要我们来了解一下Activity的生命周期。

Activity生命周期

2012120122450787

Activity的整个生命周期有七个重要方法,分别是onCreate、onStart、onResume、onPause、onStop、onRestart、onDestroy,而且这七个方法在不同的状态下会有不同的触发时机,

1. Activity启动的时候,会依次执行onCreate、onStart和onResume

2. Activity失去焦点的时候,会依次执行onPause和onStop

3. Activity重新获得焦点的时候,会依次执行onRestart、onStart和onResume

4. Activity关闭的时候,会依次执行onPause、onStop和onDestroy

举个例子:

在APP已经启动的时候,如果按下Back键,那么就是第4种-关闭的情况,会依次执行onPause、onStop和onDestroy,

按下Home键,那么就是第2种-失去焦点的情况,会依次执行onPause和onStop,这个时候如果再次启动APP,就会是按照第3种-重新获得焦点的情况,会依次执行onRestart、onStart和onResume。

为了验证这一点,我们修改一下MainActivity.java,添加以下方法:

    @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();
    }

    @Override
    protected void onDestroy()
    {
        Log.v( "WeatherAPP", "onDestroy" );
        super.onDestroy();
    }

 

 

并且在onCreate和onStop方法内加入分别加入:

        Log.v( "WeatherAPP", "onCreate" );
	Log.v( "WeatherAPP", "onStop" );

 

 

然后运行程序,试着重复以上操作,并且在Logcat View中查看相应的Log。

这里说一点小技巧,点击Logcat View左上角的绿色加号,可以添加自定义的日志过滤器,我这里就是用了这样的功能。

QQ截图20140927194650

QQ截图20140927194550

 

在明白了Activity的生命周期后,它们在实际的APP中具体怎么应用?

那么,举一些例子。

1. 如果我们的预报天气APP需要在用户每次重新可见的时候都获取最新天气,那么调用获取天气相关的代码就需要放置到onResume方法中

2. 另外一种情况,我们的APP已经在前台可见了,这时候来了电话,就变得不可见了,之后挂了电话,又重新可见了,这个时候通常是不需要重新获取数据的,那么调用获取天气相关的代码就需要放置到onStart方法中

3. 最简单的情况,我们只需要在启动的时候获取天气,那么就只需要在onCreate方法中执行

所以呢,技术不是问题,一切都需要根据实际的需要来决定我们的程序应该如何实现。

 

以上是简单介绍了一下Activity的生命周期,大家可以在实践中慢慢摸索,并且以上介绍的都只是基础内容,高阶一点的内容我们将在之后的学习中慢慢接触。

 

在了解了生命周期后,是不是要对我们的APP做些修改呢,嗯,就按照第2种情况修改吧。

所要做的只是把下面这段代码从onCreate方法移到onStart中。

        String city = readCity();
        if( city != null && city.length() > 0 )
        {
            getWeather( city );
        }

 

看看结果是不是想要的呢。

 

想必练习到这里,大家也都累了,理论知识真是恼人,不过确实是我们所必须的,只有经过理论武装的知识才能发挥出最大作用。

今天就到这里吧,各位辛苦了。

明天是周末,会有很多的内容,请大家期待!

 

此系列文章系本人原创,如需转载,请注明出处 www.liuzhibang.cn

 

经过前几天的学习,我们的天气预报程序已经可以把天气正常的呈现出来了,正如之前说的,现在的APP只能显示固定地区的天气,那么我们要怎样才能显示我们本身所在地的天气呢?

Android定位

Android系统本身提供了三种定位方式,分别是网络、基站和GPS,主要利用的是LocationManager、TelephonyManager相关的类库,但是因为一些原因,Google的API在国内访问经常出现问题,所以在这里我就不对这些API做介绍了,有想了解的可以自行查询相关资料。

百度地图定位

除了Android本身提供的定位功能外,在国内也有很多供我们使用的定位系统,比如百度地图、高德地图都提供了相应的功能,我这里主要介绍一下百度地图的使用方式。

需要到 http://lbsyun.baidu.com/sdk/download?selected=location 下载定位功能的开发包,然后把开发包的内容放到libs文件夹,这样我们就引入了百度地图定位功能的API。

然后,开工吧。

 

配置Manifest

首先添加定位功能所需要的权限,还记得添加到哪吧。

    <!-- 这个权限用于进行网络定位 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!-- 这个权限用于访问GPS定位 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位 -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <!-- 获取运营商信息,用于支持提供运营商信息相关的接口 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- 这个权限用于获取wifi的获取权限,wifi信息会用来进行网络定位 -->
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <!-- 用于读取手机当前的状态 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <!-- 写入扩展存储,向扩展卡写入数据,用于写入离线定位数据 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- SD卡读取权限,用户写入离线定位数据 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <!-- 允许应用读取低级别的系统日志文件 -->
    <uses-permission android:name="android.permission.READ_LOGS" />

 

然后在application节点内,添加一个service,这个service是运行于后台的获取定位的服务,

        <service
            android:name="com.baidu.location.f"
            android:enabled="true"
            android:process=":remote" >
            <intent-filter>
                <action android:name="com.baidu.location.service_v2.2" >
                </action>
            </intent-filter>
        </service>

 

最后,在application节点内,添加我们申请的百度地图API的Accesskey。

        <meta-data
            android:name="com.baidu.lbsapi.API_KEY"
            android:value="YknGmxIoPugT7YrNrG955YLS" />

 

就这样,百度地图的引入就算是完成了。那么如何在代码中使用?

 

打开MainActivity.java,我们需要简单重构一下代码。

你可以使用Eclipse的重构工具,也可以手动修改代码,修改为如下:

    @Override
    protected void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );

        ViewUtils.inject( this );

        datas = new ArrayList<WeatherDataBean>();
        adapter = new WeatherAdapter( getApplicationContext(), datas );
        lstWeather.setAdapter( adapter );

        getWeather( "北京" );
    }

    private void getWeather( String 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 );
            }
        } );
    }

 

 

这里新增加了一个以城市为参数方法getWeather,做好了重构之后,我们加入百度的定位功能。

 

声明两个变量:

    private LocationClient mLocationClient;
    private BDLocationListener myListener;

 

添加初始化这两个变量的方法,

    private void initLocationClient()
    {
        mLocationClient = new LocationClient( getApplicationContext() ); 
        myListener = new MyLocationListener();
        LocationClientOption option = new LocationClientOption();
        option.setLocationMode( LocationMode.Hight_Accuracy );
        option.setIsNeedAddress( true );
        mLocationClient.setLocOption( option );
        mLocationClient.registerLocationListener( myListener );
    }

 

我们是用到了MyLocationListener,这个类需要自定义,作为MainActivity的内部类存在,

    public class MyLocationListener implements BDLocationListener
    {
        @Override
        public void onReceiveLocation( BDLocation location )
        {
            String city = location.getCity();
            getWeather( city );
            setTitle( city + "天气" );
        }
    }

 

然后,修改onCreate方法,

    protected void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );

        ViewUtils.inject( this );

        datas = new ArrayList<WeatherDataBean>();
        adapter = new WeatherAdapter( getApplicationContext(), datas );
        lstWeather.setAdapter( adapter );

        initLocationClient();
        mLocationClient.start();
    }

 

最后,添加onStop方法,

    @Override
    protected void onStop()
    {
        super.onStop();
        mLocationClient.stop();
    }

 

打完收工。

 

运行吧,看看是怎样的效果,反正我的是这样:

device-2015-01-22-161410

 

是不是你所在的城市呢?

 

既然看到效果了,那么稍微解释一下上面那些代码。

首先在界面加载的时候,会调用initLocationClient方法,这个方法初始化了LocationClient和BDLocationListener,LocationClient的作用是异步获取定位,MyLocationListener则是在LocationClient获取定位后的回调方法,我们在这个回调方法中调用了获取天气的方法getWeather,并且调用setTitle方法修改了APP页面的标题为“城市名+天气”。

整个流程堪称完美,只差一点点。

 

这一点点是什么地方呢?

 

就在于我们每一次获取天气之前都需要定位,而你是不会每天都换一个城市的,为此我们的程序需要优化一下。优化后的流程为:

1. 从本地读取城市,并且同时调用百度定位

2. 如果1中的本地城市不为空,则获取天气

3. 如果1中百度定位的城市跟本地城市一致,就不再次获取天气;若不一致,则覆盖本地城市,并且重新获取天气

这样做的好处有两个:

1. 只有第一次启动APP的时候,本地城市为空,那么就是获取天气会比较快

2. 即使更换了城市,也能准确应对

 

既然整理好思路了,那么就开工吧。

 

Android本地存储数据有多种方式,主要有Preference、Sqlite、File这三种,我们今天使用的是Preference这种方式。

Preference

Preference适合存储轻量数据,如String、Int、Boolean等类型的数据,我们所需要保存的城市数据就是一个简单的字符串,非常适合这种方式。

在MainActivity.java 内添加两个方法,分别为存储、读取数据的方法。

    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", "" );
    }

 

有些经验的程序员一眼就能看明白,Preference中是以Key/Value的形式存储数据的。

 

然后修改onCreate方法,

    protected void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );

        ViewUtils.inject( this );

        datas = new ArrayList<WeatherDataBean>();
        adapter = new WeatherAdapter( getApplicationContext(), datas );
        lstWeather.setAdapter( adapter );

        initLocationClient();
        mLocationClient.start();

        String city = readCity();
        if( city != null && city.length() > 0 )
        {
            getWeather( city );
        }
    }

 

这里加入了读取本地城市,并且获取天气的逻辑。

 

接下来修改MyLocationListener,

    public class MyLocationListener implements BDLocationListener
    {
        @Override
        public void onReceiveLocation( BDLocation location )
        {
            String city = location.getCity();

            String localCity = readCity();
            if( !localCity.equals( city ) )
            {
                saveCity( city );
                getWeather( city );
            }
        }
    }

 

这里加入了定位的城市和本地城市判断的逻辑,并且删掉了对setTitle 方法的调用.

 

最后,修改getWeather方法,在方法第一行加入对setTitle的调用。

setTitle( city + "天气" );

 

打完收工,最后MainActivity.java是这个样子的:

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;

    @Override
    protected void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );

        ViewUtils.inject( this );

        datas = new ArrayList<WeatherDataBean>();
        adapter = new WeatherAdapter( getApplicationContext(), datas );
        lstWeather.setAdapter( adapter );

        initLocationClient();
        mLocationClient.start();

        String city = readCity();
        if( city != null && city.length() > 0 )
        {
            getWeather( city );
        }
    }

    private void getWeather( String city )
    {
        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()
    {
        super.onStop();
        mLocationClient.stop();
    }

    public class MyLocationListener implements BDLocationListener
    {
        @Override
        public void onReceiveLocation( BDLocation location )
        {
            String city = location.getCity();

            String localCity = readCity();
            if( !localCity.equals( city ) )
            {
                saveCity( city );
                getWeather( city );
            }
        }
    }

    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", "" );
    }
}

 

今天的主要任务就是嵌入了百度地图,重构了代码,优化了流程,最最重要的是我们学习了SharedPreferences的使用,这种保存数据方式在做APP的时候是经常会用到的,希望大家能熟练掌握。

 

请注意,本文用到的key是我个人使用的,请勿将其用于任何商业用途。如果有商业需要,请联系我或者自行在百度官网申请Accesskey。

 

附件是本次的工程文件,点击下载 http://pan.baidu.com/s/1jG9puYU 。

此系列文章系本人原创,如需转载,请注明出处 www.liuzhibang.cn

 

 

经过前几天的练习,相信大家已经对如何做出一个简单的界面有了初步的了解,并且已经做出来一个还不错的天气列表了。

今天大家稍事休息,要练习的内容比较少,着重学习一些理论知识,先理清几个概念。

Android系统架构

Android系统本质上是Linux系统,但相对于Linux系统,主要在驱动、性能、内存管理、设备管理等一些部分做了比较多的改动,以更适用于移动设备。

Android系统架构图

从上图可以看到,Android系统架构为四层,分别是Linux内核、系统运行库、应用程序框架以及应用层,每一层的作用分别为:

Linux内核

提供了作为一个操作系统所应有的基础功能,包括各种硬件驱动、内存管理、电源管理等,该层作为硬件和软件之间的抽象层而存在。

系统运行库

系统运行库分为两部分,系统库和Android运行时,系统库提供了底层的Sqlite、SSL、OpenGL、Surface Manager等类库,从数据库到网络再到界面绘制,都在这一层进行。

Android运行时,这一部分在Android4.4以前是Dalvik,也就是Google实现的Java虚拟机,所有的Android APP都运行在虚拟机里面,每个Dalvik虚拟机都是一个独立进程。简单的说就是每个Android APP编译之后会打包为APK文件,APK文件内部包含资源文件、代码文件(Dex)、AndroidManifest.xml以及别的配置文件,而Dalvik虚拟机则执行的是Dex文件。

Android4.4以后,启用了Dalvik虚拟机,而改用Android Runtime,根据Google的说明,新的Android Runtime会有更好的效率,执行速度更快、耗电更少、占用内存更低。

应用程序框架

这一层就是我们代码可以直接调用的了,通常所说的Android API都会在这一层提供,主要包括Activity Manager、Window Manager、Content Provider等,等我们对系统有了更多的学习之后,会慢慢用到其中的一部分内容。

应用层

这一层就是我们平时可以操作的APP了。Android本身既是个操作系统,又提供了基础的APP,如SMS、Phone、Browser等,当然了,更多的APP是需要我们程序员来实现的。

 

了解了Android系统的基础架构后,再结合我们正在开发的天气预报APP来具体的学习一下Android APP项目的一个核心的文件——AndroidManifest.xml。

AndroidManifest

每一个Android项目都包含一个清单(Manifest)文件--AndroidManifest.xml,它存储在项目层次中的最底层,它是Android程序的全局配置文件,可以定义应用程序及其组件的结构和元数据。它包含了组成应用程序的每一个组件(活动、服务、内容提供器和广播接收器)的节点,并使用Intent过滤器和权限来确定这些组件之间以及这些组件和其他应用程序是如何交互的。它还提供了各种属性来详细地说明应用程序的元数据(如它的图标或者主题)的以及额外的可用来进行安全设置和单元测试顶级节点,我们先来看一下天气预报APP的这个文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.demo.weather"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.demo.weather.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

我们逐次说明一下,

根节点

根节点定义了包名、版本号,其中package一定是唯一的,android:versionCode是供程序内部使用的版本号,android:versionName则会显示给用户。

uses-sdk

这个节点指定Android应用中所需要使用的SDK的版本,android:minSdkVersion指定可运行的最低版本,android:maxSdkVersion指定可运行的最高版本,android:targetSdkVersion这个指定了目标版本。

指定的版本均为数字,大家可以对照下表参考:

 Android版本  API Level
 5.0  21
 4.4w  20
 4.4  19
 4.3  18
 4.2.2  17
 4.1.2  16
 4.0.3  15
 4.0  14
 3.2  13
 3.1  12
 3.0  11
 2.3.3  10
 2.2  8
 2.1  7
 1.6  4
 1.5  3

 

关于android:targetSdkVersion这个目标版本很多初学者会疑惑,不明白到底有什么用。我个人的理解是比如你的APP指定了最低版本为8,目标版本为19,你开发过程中做出来的程序在Android4.4的设备上是没有任何问题的,其他的版本Android系统就会进行相应的兼容适配。比如有些API在低版本是不提供的,就需要我们在程序中进行版本判断,进行不同的代码处理。注意,这个时候你指定了最低版本是8,你最低可以使用8的API,在程序编译的时候,你用到了19的API是不会出现编译错误的。

android:maxSdkVersion这个配置一般是不需要设置的,因为系统本身是向下兼容的。

uses-permission

这个节点指定了APP拥有的权限。Android系统对于权限管理是比较严格的,一个APP必须先声明权限才能使用相应的功能。例如需要访问网络,则需要在这里声明一个android.permission.INTERNET权限,相应的如GPS、Camera、VIBRATE等都需要先声明权限才可以使用。

application

这个节点指定了APP的组件、属性,如icon、label、name、theme等。

activity

这个节点是application的子节点,定义了整个APP的所有页面。

其它

除了以上我们用到的几个外,还有receiver、provider、service等,这些我们在以后慢慢熟悉。

 

一点点实践

了解了Android本身的架构后,我们稍微修改一下manifest文件。

应用图标

icon

把上面的图标放到resdrawable-hdpi文件夹下,然后修改application节点下的android:icon="@drawable/ic_launcher" 为  android:icon="@drawable/icon"

应用名称

打开resvaluesstrings.xml文件,修改<string name="app_name">Weather</string>为<string name="app_name">天气预报</string>

 

打完收工,运行程序。

可以发现应用左上角的图标和名称都变成了我们想要的。

device-2015-01-21-165433

 

今天的内容比较少,主要是一些基础理论知识,也希望大家多多查阅资料,对Android本身的结构有更多更深入的了解。

 

附件是本次的工程文件,点击下载

 

此系列文章系本人原创,如需转载,请注明出处 www.liuzhibang.cn