liuzhibang 发布的文章

我们上次学习了百度定位以及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

 

继续昨天的学习。

昨天我们根据取得的天气数据新建了一个视图用来显示各项内容,那么今天我们就把数据显示出来吧!!!

这里我们要把数据和视图联系起来,那么就用到了适配器-Adapter,Android给我们提供了很多Adapter,这里我们用到了BaseAdapter。

 

BaseAdapter(1)

右键点击src/com.demo.weather,选择 New > Class,按照下图填写:

QQ截图20140921212540

选择[Finish]后,我们就新建了一个BaseAdapter的子类,打开 WeatherAdapter.java这个文件,来解释一下这些代码,

public class WeatherAdapter extends BaseAdapter
{

    @Override
    public int getCount()
    {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public Object getItem( int arg0 )
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public long getItemId( int arg0 )
    {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public View getView( int arg0, View arg1, ViewGroup arg2 )
    {
        // TODO Auto-generated method stub
        return null;
    }

}

 

public int getCount()

这个方法返回ListView有几条数据,

 

public Object getItem( int arg0 )

这个方法返回当前行的数据,

 

public long getItemId( int arg0 )

这个方法返回当前行的id,

 

public View getView( int arg0, View arg1, ViewGroup arg2 )

这个方法返回当前行的视图,

那么究竟怎样把数据和视图关联起来了。

 

首先,我们需要把取得的数据转换为代码可以方便操作的形式。把json数据转换为JavaBean,我们需要用到一个gson库,可以在这里下载。 http://code.google.com/p/google-gson/

把下载回来的gson-v2.jar放到libs文件夹,然后开始我们辛苦的工作吧,将是一大段代码。

 

Gson

新建一个package包,命名为com.demo.weather.bean,在这个包下面添加4个类,BaiduData、ResultsBean、IndexBean、WeatherDataBean,内容分别是:

public class BaiduData
{
    private int error;
    private String status;
    private String date;
    private List<ResultsBean> results;

    public int getError()
    {
        return error;
    }
    public void setError( int error )
    {
        this.error = error;
    }
    public String getStatus()
    {
        return status;
    }
    public void setStatus( String status )
    {
        this.status = status;
    }
    public String getDate()
    {
        return date;
    }
    public void setDate( String date )
    {
        this.date = date;
    }
    public List<ResultsBean> getResults()
    {
        return results;
    }
    public void setResults( List<ResultsBean> results )
    {
        this.results = results;
    }
}

 

public class ResultsBean
{
    private String currentCity;
    private String pm25;
    private List<IndexBean> index;
    private List<WeatherDataBean> weather_data;

    public String getCurrentCity()
    {
        return currentCity;
    }
    public void setCurrentCity( String currentCity )
    {
        this.currentCity = currentCity;
    }
    public String getPm25()
    {
        return pm25;
    }
    public void setPm25( String pm25 )
    {
        this.pm25 = pm25;
    }
    public List<IndexBean> getIndex()
    {
        return index;
    }
    public void setIndex( List<IndexBean> index )
    {
        this.index = index;
    }
    public List<WeatherDataBean> getWeather_data()
    {
        return weather_data;
    }
    public void setWeather_data( List<WeatherDataBean> weather_data )
    {
        this.weather_data = weather_data;
    }
}

 

public class IndexBean
{
    private String title;
    private String zs;
    private String tipt;
    private String des;

    public String getTitle()
    {
        return title;
    }
    public void setTitle( String title )
    {
        this.title = title;
    }
    public String getZs()
    {
        return zs;
    }
    public void setZs( String zs )
    {
        this.zs = zs;
    }
    public String getTipt()
    {
        return tipt;
    }
    public void setTipt( String tipt )
    {
        this.tipt = tipt;
    }
    public String getDes()
    {
        return des;
    }
    public void setDes( String des )
    {
        this.des = des;
    }
}

 

public class WeatherDataBean
{
    private String date;
    private String dayPictureUrl;
    private String nightPictureUrl;
    private String weather;
    private String wind;
    private String temperature;

    public String getDate()
    {
        return date;
    }
    public void setDate( String date )
    {
        this.date = date;
    }
    public String getDayPictureUrl()
    {
        return dayPictureUrl;
    }
    public void setDayPictureUrl( String dayPictureUrl )
    {
        this.dayPictureUrl = dayPictureUrl;
    }
    public String getNightPictureUrl()
    {
        return nightPictureUrl;
    }
    public void setNightPictureUrl( String nightPictureUrl )
    {
        this.nightPictureUrl = nightPictureUrl;
    }
    public String getWeather()
    {
        return weather;
    }
    public void setWeather( String weather )
    {
        this.weather = weather;
    }
    public String getWind()
    {
        return wind;
    }
    public void setWind( String wind )
    {
        this.wind = wind;
    }
    public String getTemperature()
    {
        return temperature;
    }
    public void setTemperature( String temperature )
    {
        this.temperature = temperature;
    }
}

 

这4个JavaBean承载了json格式的字符串转换后的对象。

 

然后,打开MainActivity.java文件,这个文件还有一个小红叉,那是我们之前删掉了一个文本后没有及时修改代码造成的后果。不用客气,把它删掉。

修改http.send方法为以下内容:

        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();
                BaiduData data = gson.fromJson( weather, BaiduData.class );

                Log.v( "onFailure", data.toString() );
            }

            @Override
            public void onFailure( HttpException arg0, String arg1 )
            {
                Log.v( "onFailure", arg1 );
            }
        } );

 

到这里,先搞一段落,来解释一下这么多的代码。

虽然代码很多,但是都很简单,BaiduData、ResultsBean、IndexBean、WeatherDataBean这4个类是根据gson的要求,结合百度天气API的返回数据做成的标准JavaBean。

                Gson gson = new Gson();
                BaiduData data = gson.fromJson( weather, BaiduData.class );

这两行是核心功能,把Json格式的字符串转换为了我们做成的BaiduData这个JavaBean。

 

BaseAdapter(2)

既然已经把数据格式化为JavaBean了,剩下的工作就简单多了。

 

修改WeatherAdapter使得数据和视图关联,

public class WeatherAdapter extends BaseAdapter
{
    private List<WeatherDataBean> weathers;
    private LayoutInflater inflater;
    private BitmapUtils bitmapUtils;

    public WeatherAdapter( Context context, List<WeatherDataBean> weathers )
    {
        this.weathers = weathers;
        inflater = LayoutInflater.from( context );
        bitmapUtils = new BitmapUtils( context );
    }

    @Override
    public int getCount()
    {
        return weathers.size();
    }

    @Override
    public Object getItem( int position )
    {
        return weathers.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_weather, null );

        TextView txtDate = (TextView)convertView.findViewById( R.id.item_date );
        TextView txtWeather = (TextView)convertView.findViewById( R.id.item_weather );
        TextView txtWind = (TextView)convertView.findViewById( R.id.item_wind );
        TextView txtTemperature = (TextView)convertView.findViewById( R.id.item_temperature );
        ImageView imgTemperature = (ImageView)convertView.findViewById( R.id.item_picture );

        WeatherDataBean bean = (WeatherDataBean)getItem( position );

        txtDate.setText( bean.getDate() );
        txtWeather.setText( bean.getWeather() );
        txtWind.setText( bean.getWind() );
        txtTemperature.setText( bean.getTemperature() );

        bitmapUtils.display( imgTemperature, bean.getDayPictureUrl() );

        return convertView;
    }

}

 

这里我们使用了LayoutInflater来载入昨天做成的item_weather界面,使用了BitmapUtils来加载网络图片。虽然是大段代码,但是道理很简单,把对应的数据显示到对应的项目。

 

然后,在MainActivity.java里添加对ListView以及我们之前建好的WeatherAdapter类的引用,并且将ListView、WeatherAdapter、BaiduData三个对象关联起来,

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

    private WeatherAdapter adapter;
    private BaiduData data;

    private List<WeatherDataBean> datas;

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

        ViewUtils.inject( this );

        HttpUtils http = new HttpUtils();

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

        RequestParams params = new RequestParams();
        params.addQueryStringParameter( "location", "北京" );
        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 );
            }
        } );
    }
}

打完收工。

 

最终的效果如下:

device-2015-01-20-171134

 

同志们,很有成就感了吧!!!

 

不要得意,这只是把数据简单的显示出来而已,并且只能是帝都北京的数据,如果你不在北京,这根本对你一点用处都没有啊。

嗯嗯,今天就到这儿吧,已经足够多了,好学的你肯定已经在查找更多关于Adapter、ListView的知识了,这里我就不做介绍了,建议大家经常百度。

 

休闲一下,说点废话。

本人从事IT行业10余年,从最初级的菜鸟程序员开始,一步一步成长,见过形形色色的程序员,从个人的角度把程序员分成这么几个级别:

1. 无脑

这个指的是一遇到问题就问别人的同志,自己也不思考,也不求上进,混一天算一天。这种类型是极少数的,我也只是听说过,都是传说中的人物。

2. 菜鸟

这种类型的程序员非常多,通常是接触技术时间不长的朋友。这部分通常是没有网络就不能工作了,经常在论坛里查找资料,下载别人代码拿来使用,键盘上的Ctrl、C、V键都磨光了。

3. 小牛

这种类型多见于技术负责人、技术总监这样的职位,要想做到小牛这一步,需要对技术有比较深的了解,能够从无到有进行设计的。在各种开源社区,这部分人通常会比较活跃,菜鸟们大都使用小牛们的劳动成果。

4. 大牛

这种类型的人已经非常少了,我只见过那么几位,都是各大公司CTO之类的。说说其中一位,他从无到有设计了公司的云计算方案,从硬件的基础设施到软件层面,再到用户层面,这位大牛都有深入的掌握。

5. 传说

鄙人只能在各种新闻、书籍中才能见到了。个人认为这部分人的技术已经不是我们可以评判的了,他们的思想、影响力根本不是我等可以企及的。

 

各位亲,你是哪个级别的?

 

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

 

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

经过第二天的学习,我们正确的调用了百度天气API,将天气信息显示到了界面上,做到这一步,我们的工作就算是完成1%了,剩下99%的工作就需要不断的润色这个未成形的APP了。

最首要的就是,我们要把那么一大堆字符转换为普通用户可以轻松理解的界面,那么我们来学习一下Android里面的界面布局。

打开res/layout/activity_main.xml文件,切换到Layouts选项卡,可以看到里面有许多项目,GridLayout、LinearLayout、RelativeLayout等,这些都代表什么类型的布局呢?

QQ截图20140921162822

理论知识

总体来说,Android界面布局分为5中,分别为LinearLayout(线性布局)、RelativeLayout(相对布局)、FrameLayout(框架布局)、TableLayout(表格布局)、GridLayout(网格布局),下面逐一简单介绍一下。

LinearLayout(线性布局)

LinearLayout使得布局内部的元素以水平(horizontal)或者垂直(vertical)的方式排成一行,

<LinearLayout 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:orientation="horizontal" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="第一个文本" />
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="第二个文本" />

</LinearLayout>

 

其中,android:orientation指定布局内部元素的排列方式,如果没有此项设置,默认为水平排列。

设置好排列方式之后的效果分别如下:

QQ截图20140921164824QQ截图20140921165036

 

RelativeLayout(相对布局)

RelativeLayout使得布局内部的元素以相对于容器、其他元素的位置排列,

<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" >

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="第一个文本" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/text1"
        android:layout_toRightOf="@id/text1"
        android:text="第二个文本" />

</RelativeLayout>

 

这个布局定义了第一个文本位于布局的左上角,第二个文本在第一个文本的右下。

QQ截图20140921165837

FrameLayout(框架布局)

FrameLayout使得布局内部的元素按照层次堆叠在左上角,后添加的元素会覆盖前面的元素。

<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" >

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:background="#00ff00"
        android:text="第一个文本" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:textSize="40sp"
        android:layout_height="wrap_content"
        android:background="#ff00ff"
        android:text="第二个文本" />

</FrameLayout>

 

这样第二个文本会覆盖在第一个文本的上面,

QQ截图20140921170506

TableLayout(表格布局)

TableLayout使得布局内部的元素以行和列的形式排列,每一行都是一个TableRow或者一个普通的View,

<TableLayout 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:stretchColumns="1">

    <TableRow>
        <TextView
            android:layout_column="1"
            android:text="Open..."
            android:padding="3dip" />
        <TextView
            android:text="Ctrl-O"
            android:gravity="right"
            android:padding="3dip" />
    </TableRow>

    <TableRow>
        <TextView
            android:layout_column="1"
            android:text="Save..."
            android:padding="3dip" />
        <TextView
            android:text="Ctrl-S"
            android:gravity="right"
            android:padding="3dip" />
    </TableRow>

    <View
        android:layout_height="2dip"
        android:background="#FF909090" />

    <TableRow>
        <TextView
            android:layout_column="1"
            android:text="Quit"
            android:padding="3dip" />
    </TableRow>

</TableLayout>

 

这个布局有四行,1、2、4行是TableRow,各有数目不同的文本,第3行是一个普通的View,

QQ截图20140921172116

GridLayout(网格布局)

GridLayout使得布局内部的元素以行、列、单元格的形式排列,

<?xml version="1.0" encoding="utf-8"?>  
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:orientation="horizontal"  
    android:rowCount="5"  
    android:columnCount="4" >  
  <Button  
        android:text="1"/>  
  <Button  
        android:text="2"/>  
  <Button  
        android:text="3"/>  
  <Button  
        android:text="/"/>  
  <Button  
        android:text="4"/>  
  <Button  
        android:text="5"/>  
  <Button  
        android:text="6"/>  
  <Button  
        android:text="×"/>  
  <Button  
        android:text="7"/>  
  <Button  
        android:text="8"/>  
  <Button  
        android:text="9"/>  
   <Button  
        android:text="-"/>  
   <Button  
        android:layout_columnSpan="2"  
        android:layout_gravity="fill"  
        android:text="0"/>  
   <Button  
        android:text="."/>  
   <Button  
        android:layout_rowSpan="2"  
        android:layout_gravity="fill"  
        android:text="+"/>  
   <Button  
        android:layout_columnSpan="3"  
        android:layout_gravity="fill"  
        android:text="="/>   
</GridLayout>

 

这里定义了一个简单的计算器布局,

QQ截图20140921172904

 

其他布局

除了以上5种主要的布局,还有一些第三方的布局,可以让你方便的实现一些比较酷炫的效果,例如DrawerLayout(左滑弹出菜单)、SwipeBackLayout(左滑返回)等,这些知识需要有一定的开发经验之后慢慢摸索。

小结

以上是简单介绍了一下各种布局的使用方式,具体各个布局还有很多的属性,活用这些属性才会制作出实用的界面,这些属性要靠大家在不断的学习中慢慢掌握。

在所有的布局里面,我这里推荐大家使用RelativeLayout。如果使用其他布局,可能需要嵌套比较多的层级才可以实现的界面,通常使用RelativeLayout会比较方便,而如果层级较多,对于界面的展示会需要耗费比较多的内存,所有我这里推荐使用RelativeLayout。

 

好了,大家辛苦了,先休息一下吧,下面我们就要使用RelativeLayout来做我们的第一个界面了。

 

天气界面

让我们回到我们的项目,打开res/layout/activity_main.xml文件,删掉TextView,添加一个新的控件-ListView。

QQ截图20140921185030

 

然后切换到代码模式,确认一下ListView的属性,

    <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>

 

这里的属性说明的是这个ListView宽度撑满界面,高度自适应,水平居中并且垂直居中,它的id是weather_list,大家还记得id的作用吧,它使得这个控件在代码中可以被找到并使用。

话说这个ListView是干什么用的?

当然是显示天气了。还以北京的天气为例,http://api.map.baidu.com/telematics/v3/weather?location=%E5%8C%97%E4%BA%AC&output=json&ak=YknGmxIoPugT7YrNrG955YLS, 这个链接返回的数据是:

{
error: 0,
status: "success",
date: "2015-01-19",
results: [
{
currentCity: "北京",
pm25: "80",
index: [
{
title: "穿衣",
zs: "冷",
tipt: "穿衣指数",
des: "天气冷,建议着棉服、羽绒服、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣、冬大衣或厚羽绒服。"
},
{
title: "洗车",
zs: "较适宜",
tipt: "洗车指数",
des: "较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。"
},
{
title: "旅游",
zs: "适宜",
tipt: "旅游指数",
des: "天气较好,同时又有微风伴您一路同行。虽会让人感觉有点凉,但仍适宜旅游,可不要错过机会呦!"
},
{
title: "感冒",
zs: "少发",
tipt: "感冒指数",
des: "各项气象条件适宜,无明显降温过程,发生感冒机率较低。"
},
{
title: "运动",
zs: "较适宜",
tipt: "运动指数",
des: "天气较好,但考虑气温较低,推荐您进行室内运动,若户外适当增减衣物并注意防晒。"
},
{
title: "紫外线强度",
zs: "最弱",
tipt: "紫外线强度指数",
des: "属弱紫外线辐射天气,无需特别防护。若长期在户外,建议涂擦SPF在8-12之间的防晒护肤品。"
}
],
weather_data: [
{
date: "周一 01月19日 (实时:3℃)",
dayPictureUrl: "http://api.map.baidu.com/images/weather/day/qing.png",
nightPictureUrl: "http://api.map.baidu.com/images/weather/night/qing.png",
weather: "晴",
wind: "微风",
temperature: "-5℃"
},
{
date: "周二",
dayPictureUrl: "http://api.map.baidu.com/images/weather/day/duoyun.png",
nightPictureUrl: "http://api.map.baidu.com/images/weather/night/duoyun.png",
weather: "多云",
wind: "微风",
temperature: "5 ~ -2℃"
},
{
date: "周三",
dayPictureUrl: "http://api.map.baidu.com/images/weather/day/qing.png",
nightPictureUrl: "http://api.map.baidu.com/images/weather/night/qing.png",
weather: "晴",
wind: "北风3-4级",
temperature: "5 ~ -6℃"
},
{
date: "周四",
dayPictureUrl: "http://api.map.baidu.com/images/weather/day/qing.png",
nightPictureUrl: "http://api.map.baidu.com/images/weather/night/qing.png",
weather: "晴",
wind: "微风",
temperature: "6 ~ -5℃"
}
]
}
]
}

这是json格式的数据,其中[weather_data]这个节点就是当前以及接下来4天的天气,一共是5条数据,这样标准的列表数据当然是使用ListView控件来显示了。

那么,如何进行呢?

赵本山老师教过我们,总共分三步,

1. 取得数据

2. 做成界面

3. 把数据显示到界面上

那么,数据已经取得了,下面就进行第二步,做成界面。

ListView

如何使用ListView?在Android的设计中,这里就是一个标准的代码分离的方案,ListView作为一个容器使用,而其中的每一项单独使用另外的View来实现,那么,具体来说我们需要做些什么?

不要着急,慢慢来。

邮件点击res/layout目录,选择New > Android XML File,在弹出框中按照下图所示进行填写,

QQ截图20140921203413

然后点击Finish,就切换到了我们新建的这个视图了,这个视图将定义ListView中每一项都显示什么内容。根据百度的数据,我们的这个天气预报APP可以显示日期、天气、风、温度以及两个分别代表白天晚上的天气图片,那么我们就可以开始行动了,先添加4个TextView和1个ImageView,位置可以随便放置。需要注意的是在添加ImageView的时候,会弹出选择图片资源的对话框,我们就选中默认的ic_launcher就好了。

这些做好之后,看看我们的界面是什么样子。不管怎么说,我的是这样子的:

QQ截图20140921204537

很那看是不是,还是那句话,不着急,我们来调整一下。

选中左上角的TextView,然后注意图中红框部分,我们将在这里设置它的属性,包括宽度、位置、字体等,大家练习一下吧,试着设置一下各个项目,看看都会有什么反应。

 

嗯,先来看看我调整后的界面吧。

QQ截图20140921205841

说不上好看,也算是比较标准的列表项目了。分享一下我的代码吧:

<?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="wrap_content"
    android:padding="10dp" >

    <TextView
        android:id="@+id/item_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="日期"
        android:textColor="#000000"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/item_weather"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/item_date"
        android:layout_marginTop="5dp"
        android:text="天气"
        android:textColor="#000000"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/item_wind"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/item_date"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="5dp"
        android:layout_toRightOf="@id/item_weather"
        android:text="风"
        android:textColor="#000000"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/item_temperature"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/item_date"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="5dp"
        android:layout_toRightOf="@id/item_wind"
        android:text="温度"
        android:textColor="#000000"
        android:textSize="14sp" />

    <ImageView
        android:id="@+id/item_picture"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />

</RelativeLayout>

 

你可以直接使用我的设置,这样很方便。   ^_^

嗯,到这里,三步里面的第二步也完成了,好辛苦啊,休息休息。

通过今天的折腾,我们的界面又变成不显示任何内容的了,不要担心,我们明天继续。

 

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

 

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