首页 > 代码库 > 6 使用Ionic开发天气应用

6 使用Ionic开发天气应用

  简介:本节课我们会制作一款天气应用,这款应用允许用户查看当前的天气情况、天气预报以及地点收藏,在模态框内显示日出和日落的数据,使用分页滚动面板显示天气信息,使用侧滑菜单实现导航。

  6.1 项目配置

  环境配置,我们在第二节课的时候讲过了呦~下面我们直接创建项目啦~

  打开终端,在我们选择的目录下执行命令:

    $ ionic start weatherApp blank

  进入文件夹:

    $ cd weatherApp

  启动服务:

    $ ionic serve

  应用初始页面如下:

技术分享

  6.2 设置侧滑菜单和视图

  本应用我们将会采用侧滑菜单作为主要的导航组件。侧滑菜单既可以在左边也可以在右边滑入展开,本应用将从左边滑入。使用ionSideMenus组件可以很容易实现这个功能,它允许右划显示侧滑菜单,或者使用在左上角已配置好的切换按钮。

  现在我们打开index.html文件,配置侧滑菜单组件:

<body ng-app="starter">
    <!-- 声明ionSideMenus容器以包裹侧边栏菜单和内容区域 -->
    <ion-side-menus>
        <!-- 使用ionSideMenuContent组件包裹主体内容 -->
        <ion-side-menu-content>
            <!-- 在侧滑菜单内容区域中使用带切换图标的导航组件切换侧滑菜单的状态 -->
            <ion-nav-bar class="bar-positive">
                <ion-nav-buttons side="left">
                    <button class="button button-clear" menu-toggle="left">
                        <span class="icon ion-navicon"></span>
                    </button>
                </ion-nav-buttons>
            </ion-nav-bar>
            <ion-nav-view></ion-nav-view>
        </ion-side-menu-content>
        <!-- 声明一个侧滑菜单并设置其位置为左侧边缘 -->
        <ion-side-menu side="left">
            <!-- 为侧滑菜单设置一个头部 -->
            <ion-header-bar class="bar-dark">
                <h1 class="title">天气</h1>
            </ion-header-bar>
            <!-- 使用ionContent组件包裹链接列表为侧滑导航菜单设置内容 -->
            <ion-content>
                <ion-list>
                    <ion-item class="item-icon-left" ui-sref="search" menu-close>
                        <span class="icon ion-search"></span> 查询城市
                    </ion-item>
                    <ion-item class="item-icon-left" ui-sref="settings" menu-close>
                        <span class="icon ion-ios-cog"></span> 设置
                    </ion-item>
                </ion-list>
            </ion-content>
        </ion-side-menu>
    </ion-side-menus>
</body>

  侧滑菜单的定义十分简单,只要在代码中包含并使用ionSideMenus、ionSideMenuContent和ionSideMenu指令即可。首先我们需要用ionSideMenus包裹其他功能指令,否则菜单无法生效。在ionSideMenus内部需要增加ionSideMenuContent和ionSideMenu这两个元素,并使用side属性设置菜单位置,注意每个侧滑菜单组件中只能定义一个ionSideMenuContent元素,可以定义最多两个ionSideMenu元素。

  ionNavButtons元素,设置menu-toggle属性来控制单击菜单时菜单的开启和显示,而menuclose属性,则控制菜单的关闭与隐藏。

  下面我们来预览一下我们设置的侧滑菜单的样子~

技术分享

  6.3 制作搜索视图-->地理位置搜索

  当用户第一次启动时,用户需要设置想要查看天气的地点。我们将创建一个新的视图用来允许用户搜索并查看结果列表。

  要实现这个功能,需要使用state provider创建一个新的路由并定义模板和控制器,现在我们先来添加一个路由状态,打开app.js文件:

angular.module(‘starter‘, [‘ionic‘])
    //添加config()方法定义路由状态
    .config(function($stateProvider, $urlRouterProvider) {
        $stateProvider
        //定义搜索路由的状态
        .state(‘search‘, {
          url:‘/search‘,
          controller:‘SearchController‘,
          templateUrl:‘views/search/search.html‘
        });
        //使用搜索页面作为默认视图
        $urlRouterProvider.otherwise(‘/search‘);
    })

  搜索视图的路由状态我们已经设置好了,现在来建搜索视图模板,新建文件www/views/search/search.html:

<ion-view view-title="查询城市">
    <ion-content>
        <div class="list">
            <!-- 带有ngModel指令的搜索框和一个可单击按钮制作的搜索列表 -->
            <div class="item item-input-inset">
                <label class="item-input-wrapper">
                    <input type="search" ng-model="model.term" placeholder="搜索城市">
                </label>
                <button class="button button-small button-positive" ng-click="search()">查询</button>
            </div>
            <!-- 当搜索结果存在时遍历搜索结果显示地址和天气视图连接 -->
            <div class="item" ng-repeat="result in results" ui-sref="weather({city:result.formatted_address, lat:result.geometry.location.lat,lng:result.geometry.location.lng})">
                {{result.formatted_address}}
            </div>
        </div>
    </ion-content>
</ion-view>

  然后我们来设置搜索视图的控制器,新建文件www/views/search/search.js文件:

angular.module(‘starter‘)
    //新建控制器SearchController并注入服务
    .controller(‘SearchController‘, function($scope, $http) {
        //定义一个搜索数据模板
        $scope.model = { term: ‘‘ };
        $scope.search = function() {
            //从googleapis中搜索数据并存储结果到scope中
            $http.get(‘https://maps.googleapis.com/maps/api/geocode/json‘, { params: { address: $scope.model.term } })
                .success(function(response) {
                    $scope.results = response.results;
                });
        };
    });

  现在将搜索视图的控制器引入到index.html文件中:

    <script src="http://www.mamicode.com/views/search/search.js"></script>

  预览一下应用~

技术分享

  6.4 完成设置视图的制作

  现如今,我们用过的应用,基本都会有设置的功能,提供给用户一些应用配置的选项。我们这款天气应用也添加了设置的功能,现在我们就来逐一的完成它。

  我们需要为视图界面增加一个新的模板和控制器,然后为了管理应用,需要使用另外两个服务来在视图之间共享数据和方法,最后当编辑成功时同时更新侧滑菜单中的内容,它包含一个快速进入收藏地点的入口。

  第一步我们需要先创建两个服务,一个用来追踪收藏地点操作,另一个用来操作设置视图。使用Angular的工厂函数创建服务可以方便的注入任意的控制器,settings服务是一个带有属性的简单对象,locations服务包含一些方法帮助管理收藏的地点。

  在主应用的JS文件中,同时增加这两个服务可以保证应用更流畅,但也可以使用两个独立的模块。打开app.js文件,增加以下内容:

//使用工厂函数定义Settings服务
    .factory(‘Settings‘, function() {
        var Settings = {
            units: ‘si‘,
            days: 8
        };
        //配置默认设置后返回一个JS对象
        return Settings;
    })
    //使用工厂函数定义Locations服务
    .factory(‘Locations‘, function() {
        var Locations = {
            //创建地点对象并存储在数组中,默认数据为芝加哥
            data: [{
                city: ‘北京,中国‘,
                lat: 39.904211,
                lng: 116.407395
            }],
            //确定一个地点在搜索结果列表中的索引
            getIndex: function(item) {
                var index = -1;
                angular.forEach(Locations.data, function(location, i) {
                    if (item.lat == location.lat && item.lng == location.lng) {
                        index = i;
                    }
                });
                return index;
            },
            //从收藏地点中增加或者删除元素
            toggle: function(item) {
                var index = Locations.getIndex(item);
                if (index >= 0) {
                    Locations.data.splice(index, 1);
                } else {
                    Locations.data.push(item);
                }
            },
            //如果新增数据,将其移到顶层或者将它增加到顶层
            primary: function(item) {
                var index = Locations.getIndex(item);
                if (index >= 0) {
                    Locations.data.splice(index, 1);
                    Locations.data.splice(0, 0, item);
                } else {
                    Locations.data.unshift(item);
                }
            }
        };
        //返回带有数据和方法的Locations对象
        return Locations;
    })

  上面个我们使用Angular服务工厂函数定义了一个可以在不同控制器中共享使用的服务,将这个服务添加到不同的视图中后,随便某个视图中的该服务有变化,其他视图也会显示出相应的变化。我们用Locations.data这个数组来存储地点列表,并为Locations服务建立了三个方法,如果收藏的地点存在,getIndex()方法会返回收藏地点在Locations.data数组中的索引,toggle()方法会检查Locations.data数组中是否存在收藏地点,如果存在就删除如果不存在就添加,primary()方法则用来将收藏地点置顶,或者移除一个已经置顶的地点。

  现在,我们配置好了Locations服务后,可以实现在侧滑菜单中显示收藏城市的列表了。首先,我们需要为侧滑菜单添加一个控制器来向应用作用域中注入Locations服务,因为这个控制器属于侧滑菜单视图,不是一个独立的视图页面,所以我们可以把控制器代码直接放在app.js文件中,打开app.js文件:

    //创建一个控制器并注入服务
    .controller(‘LeftMenuController‘, function($scope, Locations) {
        //将地点列表数据映射到作用域中
        $scope.locations = Locations.data;
    })

  然后我们把控制器添加到侧滑菜单模板中去,打开index.html文件,做如下修改:

        <!-- 声明一个侧滑菜单并设置其位置为左侧边缘 添加控制器-->
        <ion-side-menu side="left" ng-controller="LeftMenuController">

  下面我们来更新一下侧滑菜单,给它添加一个收藏列表用以显示已经收藏的地点,还是打开index.html文件:

            <ion-content>
                <ion-list>
                    <ion-item class="item-icon-left" ui-sref="search" menu-close>
                        <span class="icon ion-search"></span> 查询城市
                    </ion-item>
                    <ion-item class="item-icon-left" ui-sref="settings" menu-close>
                        <span class="icon ion-ios-cog"></span> 设置
                    </ion-item>
                    <!-- .item-divider样式用来给文字添加背景以做区别显示 -->
                    <ion-item class="item-divider">收藏城市</ion-item>
                    <!-- 循环显示地点列表,显示城市的名称,增加到其具体天气视图的链接,使用menu-close指令来确保单击后隐藏侧滑菜单 -->
                    <ion-item class="item-icon-left" ui-sref="weather({city: location.city,lat:location.lat,lng:location.lng})" menu-close ng-repeat="location in locations">
                        <span class="icon ion-ios-location"></span>{{location.city}}
                    </ion-item>
                </ion-list>
            </ion-content>

  侧滑菜单的收藏列表现在我们预览,就能够看到啦~

技术分享

 

 

  现在,我们来创建设置视图的模板,新建文件www/views/settings/settings.html:

<ion-view view-title="设置">
    <ion-content>
        <ion-list>
            <ion-item class="item-divider">单位</ion-item>
            <!-- 使用ionRadio组件来切换温度显示的单位 -->
            <ion-radio ng-model="settings.units" ng-value="‘si‘">摄氏度</ion-radio>
            <ion-radio ng-model="settings.units" ng-value="‘us‘">华氏度</ion-radio>
            <div class="item item-divider">
                预报天数<span class="badge badge-dark">{{settings.days-1}}</span>
            </div>
            <!-- 使用范围选择器来控制显示天数 -->
            <div class="item range range-positive">
                1
                <input type="range" name="days" ng-model="settings.days" min="2" max="8" value="8"> 7
            </div>
            <!-- 创建一个带有可切换canDelete变量状态的分割线 -->
            <div class="item item-button-right">
                收藏城市
                <button class="button button-small" ng-click="canDelete = !canDelete">{{canDelete ? ‘完成‘ : ‘修改‘}}</button>
            </div>
        </ion-list>
        <!-- 创建一个地点列表并根据canDelete值显示删除按钮 -->
        <ion-list show-delete="canDelete">
            <!-- 循环遍历显示所有的地点 -->
            <ion-item ng-repeat="location in locations">
                <!-- 当列表的删除状态为真时显示删除按钮 -->
                <ion-delete-button class="ion-minus-circled" ng-click="remove($index)">
                </ion-delete-button>
                {{location.city}}
            </ion-item>
        </ion-list>
        <p class="padding">天气数据由<a href="">Forecast.io</a>提供,地理位置信息由<a href="">谷歌提供。</a></p>
    </ion-content>
</ion-view>

  设置视图模板已经完成了,现在我们来给他添加一个控制器,可以快速访问之前写好的服务,同时增加删除按钮的逻辑,新建文件www/views/settings/settings.js:

angular.module(‘starter‘)
    //添加控制器并注入服务
    .controller(‘SettingsController‘, function($scope, Settings, Locations) {
        //在scope中添加设置的收藏地点数据
        $scope.settings = Settings;
        $scope.locations = Locations.data;
        //为删除操作设置默认状态
        $scope.canDelete = false;
        //定义从收藏地点中删除元素的方法
        $scope.remove = function(index){
            Locations.toggle(Locations.data[index]);
        };
    });

  把设置视图的控制器文件引入index.html文件中:

    <script src="http://www.mamicode.com/views/settings/settings.js"></script>

  然后我们为设置页面添加一个路由,打开app.js文件:

            .state(‘settings‘, {
                url: ‘/settings‘,
                controller: ‘SettingsController‘,
                templateUrl: ‘views/settings/settings.html‘
            });

  现在,我们来看一下设置视图的预览效果吧~

技术分享

  6.5 设置天气视图

  现在我们来制作天气视图页面。天气视图页面用来显示某地点的当前天气和天气预报。

  我们会使用Forecast.io服务发送请求来获取天气数据。所以要先创建一个账号,登录https://darksky.net/dev/地址,创建账号,然后使用申请的免费账号登录获取的token。

  Forecast.io服务是不提供跨域资源共享功能的,也就是说,默认情况下,我们不能在浏览器中载入它们的API数据。Ionic命令行组件提供了一些特性,包括允许通过使用代理来解除浏览器的这个限制。

  在应用中,需要使用ionic.config.json文件,这个文件包含一个json对象用来配置Ionic项目,在配置中可以定义一个需要被代理的地址列表,在项目根目录下打开ionic.config.json文件:

{
    "name":"weatherApp",
    "app_id":"",
    "proxies":[
        {
        "path":"/api/forecast",
        "proxyUrl":"https://api.darksky.net/forecast/YOUR_KEY/"
        }
    ]
}

  上面的这段代码中,我们增加了一个proxies的代理属性,它的值为一个数组或者对象,在这个对象里,我们增加了一个path属性,用来定义最终会被应用访问的代理地址,还增加了一个proxyUrl属性,用来定义初始会被访问的地址,your_key即为之前注册时给你的key值,lat和lng为你希望应用初始访问的地点的地理编码。

  当ionic serve命令运行成功时,我们的代理也就成功运行了。不过这种方法只能用于使用Forecast.io服务进行本地开发,因为当应用在移动设备中运行时,它是没有Ionic命令行组件来代理API请求的。此时,我们只能通过升级API接口让其支持跨域资源共享(cors),或者增加cors代理服务来解决跨域的问题。

  现在,我们来增加天气视图模板,基本需求是在头部标题栏显示地点的名称并在视图中展示当前温度,创建文件www/views/weather/weather.html:

<ion-view view-titile="{{params.city}}">
    <ion-content>
        <h3>实时天气</h3>
        <p>{{forecast.currently.temperature | number:0}}&deg;</p>
    </ion-content>
</ion-view>

  然后我们来创建天气视图的控制器,新建文件www/views/weather/weather.js:

angular.module(‘starter‘)
    //创建WeatherController控制器,并注入服务
    .controller(‘WeatherController‘, function($scope, $http, $stateParams, Settings) {
        //在scope中添加服务数据
        $scope.params = $stateParams;
        $scope.settings = Settings;
        //添加HTTP请求载入forecast的天气数据
        $http.get(‘/api/forecast/‘ + $stateParams.lat + ‘,‘ + $stateParams.lng, { params: { units: Settings.units } }).success(function(forecast) {
            $scope.forecast = forecast;
        });
    });

  然后,我们将天气视图的控制器引入到index.html文件中:

    <script src="views/weather/weather.js"></script>

  最后我们给天气视图增加新路由,打开app.js文件:

            //定义天气视图路由状态
            .state(‘weather‘, {
                url: ‘/weather/:city/:lat/:lng‘,
                controller: ‘WeatherController‘,
                templateUrl: ‘views/weather/weather.html‘ 
            });

  现在,我们来看一下天气视图的预览效果:

技术分享

  如果能够看到上面的预览效果,就证明,我们跨域获取数据没有问题,现在我们需要美化天气视图的样式,再给天气视图增加一个滚动的功能,以方便展示更多天气信息。

  我们先来给天气视图模板添加一个ionScroll滚动组件,来创建一个垂直滚动分页的效果。对于垂直滚动,通常我们会使用ionContent组件,这个组件默认就是垂直滚动的并且内容是自带填充的,但是ionScroll组件更加可配置化,可以设置滚动内容区域函数,以达到我们想要的滚动效果。ionScroll指令需要指定宽度和高度,ionContent则是自适应的。因为应用可能在不同的设备和分辨率上使用,所以,我们不得不基于屏幕大小来计算ionScroll的大小。

  下面,我们先来给天气视图模板添加滚动组件,打开weather.html文件:

<ion-view view-title="{{params.city}}">
    <!-- 使用ionContent指令包裹ionScroll组件 -->
    <ion-content>
        <!-- 使用ionScroll指令,锁定垂直方向滚动,并设置容器的宽高 -->
        <ion-scroll direction="y" paging="true" ng-style="{width:getWidth(), height:getHeight()}">
            <!-- ionScroll内部创建div标签,并设置所有的div元素大小等于所有的页面高度之和 -->
            <div ng-style="{height:getTotalHeight()}">
                <!-- 定义具体的每一页,并设置美一页的宽高等于ionScroll组件区域大小 -->
                <div class="scroll-page page1" ng-style="{width:getWidth(), height:getHeight()}">
                    page1
                </div>
                <div class="scroll-page page2" ng-style="{width:getWidth(), height:getHeight()}">
                    page2
                </div>
                <div class="scroll-page page3" ng-style="{width:getWidth(), height:getHeight()}">
                    page3
                </div>    
            </div>
        </ion-scroll>
    </ion-content>
</ion-view>

  滚动组件已经按需求布局好了,里面使用的方法,我们在天气视图的控制器里进行定义,打开weather.js文件:

        //获得标题栏的高度
        var barHeight = document.getElementsByTagName(‘ion-header-bar‘)[0].clientHeight;
        //返回应用的宽度
        $scope.getWidth = function() {
            return window.innerWidth + ‘px‘;
        };
        //返回去除标题栏后的应用的高度
        $scope.getHeight = function() {
            return parseInt(window.innerHeight - barHeight) + ‘px‘;
        };
        //返回滚动页面的高度总和
        $scope.getTotalHeight = function() {
            return parseInt(parseInt($scope.getHeight()) * 3) + ‘px‘;
        };

  现在我们已经完成了滚动的部署,下面我们来给每一个滚动页面添加内容,打开weather.html文件:

技术分享
<ion-view view-title="{{params.city}}">
    <!-- 使用ionContent指令包裹ionScroll组件 -->
    <ion-content>
        <!-- 使用ionScroll指令,锁定垂直方向滚动,并设置容器的宽高 -->
        <ion-scroll direction="y" paging="true" ng-style="{width:getWidth(), height:getHeight()}">
            <!-- ionScroll内部创建div标签,并设置所有的div元素大小等于所有的页面高度之和 -->
            <div ng-style="{height:getTotalHeight()}">
                <!-- 定义具体的每一页,并设置美一页的宽高等于ionScroll组件区域大小 -->
                <div class="scroll-page center" ng-style="{width:getWidth(), height:getHeight()}">
                    <!-- 使用一个标题栏样式作为二级标题 -->
                    <div class="bar bar-dark">
                        <h1 class="title">实时天气状态</h1>
                    </div>
                    <!-- 使用has-header来定位页面内容 -->
                    <div class="has-header">
                        <h2 class="primary">{{forecast.currently.temperature | number:0}}&deg;</h2>
                        <!-- 基于天气情况使用icons过滤器获取当前天气的图标 -->
                        <h2 class="secondary icon" ng-class="forecast.currently.icon | icons"></h2>
                        <p>{{forecast.currently.summary}}</p>
                        <p>
                            最高:{{forecast.daily.data[0].temperatureMax | number:0}}&deg; 最低:{{forecast.daily.data[0].temperatureMin | number:0}}&deg; 体感:{{forecast.currently.apparentTemperature | number:0}}&deg;
                        </p>
                        <p>
                            风速:{{forecast.currently.windSpeed | number:0}}
                            <!-- 根据温度中的风向旋转屏幕上的箭头 -->
                            <span class="icon wind-icon ion-ios7-arrow-thin-up" ng-style="{transform: ‘rotate(‘ + forecast.currently.windBearing + ‘deg)‘}"></span>
                        </p>
                    </div>
                </div>
                <div class="scroll-page" ng-style="{width:getWidth(), height:getHeight()}">
                    <div class="bar bar-dark">
                        <h1 class="title">周天气预报</h1>
                    </div>
                    <div class="has-header">
                        <p class="padding">{{forecast.daily.summary}}</p>
                        <!-- 使用limitiTo过滤器来设置值,显示页面中设置的天数 -->
                        <div class="row" ng-repeat="day in forecast.daily.data | limitTo:settings.days">
                            <!-- 使用date过滤器转换时间戳为星期 -->
                            <div class="col col-50">{{day.time + ‘000‘ | date:‘EEEE‘}}</div>
                            <div class="col">
                                <span class="icon" ng-class="day.icon | icons"></span>
                                <!-- 使用chance过滤器对百分数取整 -->
                                <sup>{{day.precipProbability | chance}}</sup>
                            </div>
                            <div class="col">{{day.temperatureMax | number:0}}&deg;</div>
                            <div class="col">{{day.temperatureMin | number:0}}&deg;</div>
                        </div>
                    </div>
                </div>
                <div class="scroll-page" ng-style="{width:getWidth(), height:getHeight()}">
                    <div class="bar bar-dark">
                        <h1 class="title">天气概况</h1>
                    </div>
                    <div class="list has-header">
                        <!-- 获取已经转换成当地时区的日出日落时间 -->
                        <div class="item">
                            日出时间:{{forecast.daily.data[0].sunriseTime | timezone:forecast.timezone}}
                        </div>
                        <div class="item">
                            日落时间:{{forecast.daily.data[0].sunsetTime | timezone:forecast.timezone}}
                        </div>
                        <div class="item">
                            能见度:{{forecast.currently.visibility}}
                        </div>
                        <div class="item">
                            空气湿度:{{forecast.currently.humidity*100}}%
                        </div>
                    </div>
                </div>
            </div>
        </ion-scroll>
    </ion-content>
</ion-view>
View Code

  上面这段代码中,载入数据的时候使用了很多过滤器,而这些过滤器我们还没有定义,致使页面数据显示会有些问题,所以我们先来定义一下过滤器。因为我们从服务获取的天气数据大都不是我们先想要的格式,所以,我们建立一个过滤器来转换服务器给我的数据格式。首先,我们需要安装moment.js类库,执行命令:ionic add moment-timezone(我当时执行没有成功,按照错误提示,又执行了这条命令:bower install --save-dev moment-timezone),安装完成后,将类库文件引入到index.html文件中:

    <script src="http://www.mamicode.com/lib/moment/moment.js"></script>
    <script src="http://www.mamicode.com/lib/moment-timezone/builds/moment-timezone-with-data.js"></script>

  现在我们已经配置好了moment.js类库,可以开始创建过滤器了,打开文件app.js,添加如下内容:

//创建时区过滤器,用来根据天气地点时区转换时间
    .filter(‘timezone‘, function(){
        return function(input,timezone){
            //过滤器只有在时间戳和时区都指定时才开始工作
            if(input && timezone){
                var time = moment.tz(input*1000, timezone);
                return time.format(‘LT‘);
            }
            return ‘‘;
        };
    })
    //创建chance过滤器,用于将小数转化为百分比形式数据
    .filter(‘chance‘, function(){
        return function(chance){
            //如果给定某值,将其转化为百分比后取整输出
            if(chance){
                var value = http://www.mamicode.com/Math.round(chance / 10);
                return value*10;
            }
            return 0;
        };
    })
    //创建icons过滤器,根据具体的天气条件返回相应的图标
    .filter(‘icons‘,function(){
        //设置天气图标对象,返回在这个对象中找到的对应的值
        var map = {
            ‘clear-day‘:‘ion-ios-sunny‘,
            ‘clear-night‘:‘ion-ios-moon‘,
            rain:‘ion-ios-rainy‘,
            snow:‘ion-ios-snowy‘,
            sleet:‘ion-ios-rainy‘,
            wind:‘ion-ios-flag‘,
            fog:‘ion-ios-cloud‘,
            cloudy:‘ion-ios-cloudy‘,
            ‘partly-cloudy-day‘:‘ion-ios-partlysunny‘,
            ‘partly-cloudy-night‘:‘ion-ios-cloudy-night‘
        };
        return function(icon){
            return map[icon] || ‘‘;
        }
    })

  此时,我们查看应用时,数据都按需求显示了,但是样式不美观,打开style.css文件,我们来给天气视图增加一些样式:

.scroll-page .icon:before { padding-right: 5px; }
.scroll-page .row + .row { margin-top: 0; padding-top: 5px; }
.scroll-page .row:nth-of-type(odd) { background: #fafafa; }
.scroll-page .row:nth-of-type(even) { background: #f3f3f3; }
.scroll-page .wind-icon { display: inline-block; }
.scroll-page.center { text-align: center; }
.scroll-page .primary { margin: 0; font-size: 100px; font-weight: lighter; padding-left: 30px; }
.scroll-page .secondary { margin: 0; font-size: 150px; font-weight: lighter; }
.scroll-page .has-header { position: relative; }

  现在我们来预览一下应用~

技术分享

 

  下面,我们来个天气视图导航栏右侧添加一个菜单列表的按钮,以便向用户显示一些可操作选项。这个按钮是可以通过下方上滑唤醒的组件。里面会有一个"取消"选型,单击组件外部可以关闭组件列表。

  这个组件的所有内容都写在$ionicActionSheet服务中,它没有模板内容,我们需要定义一系列按钮,告诉每个按钮的作用。首先,我们先在天气视图导航栏添加一个"更多"的图标按钮来唤醒活动菜单列表组件,打开weather.html文件:

<ion-view view-title="{{params.city}}">
    <!-- 定义导航栏左侧的切换主导航按钮 -->
    <ion-nav-buttons side="left">
        <button class="button button-clear" menu-toggle="left"><span class="icon ion-navicon"></span></button>
    </ion-nav-buttons>
    <!-- 定义导航栏右侧的更多图标按钮,并调用showOptions()方法 -->
    <ion-nav-buttons side="right">
        <button class="button button-icon" ng-click="showOptions()"><span class="icon ion-more"></span></button>
    </ion-nav-buttons>

  预览一下页面效果:

技术分享

  现在,打开weather.js文件,向控制器注入$ionicActionSheet服务和Locations服务:

.controller(‘WeatherController‘, function($scope, $http, $stateParams, $ionicActionSheet, Settings, Locations) {

  然后还是在weather.js文件中,定义showOptions()方法:

//定义showOptions()方法
        $scope.showOptions = function(){
            //使用show方法设置并显示活动菜单列表组件,前提是必须注入$ionicActionSheet服务
            var sheet = $ionicActionSheet.show({
                //按钮对象数组
                buttons:[
                    {text:‘收藏城市‘},
                    {text:‘收藏置顶‘},
                    {text:‘日出日落‘}
                ],
                //显示取消按钮并设置显示文字
                cancelText:‘取消‘,
                //处理按钮点击事件方法,参数为点击按钮的顺序
                buttonClicked:function(index){
                    //Locations服务的toggle方法切换收藏状态
                    if(index === 0){
                        Locations.toggle($stateParams);
                    }
                    //使用Locations服务的primary方法指定收藏
                    if(index === 1){
                        Locations.primary($stateParams);
                    }
                    //日出日落弹窗功能区域
                    if(index === 2){
                        $scope.showModal();
                    }
                    //返回TRUE会关闭活动菜单列表组件,否则它会保存打开状态
                    return true;
                }
            });
        };

  现在我们已经实现了收藏切换和置顶的功能,预览下应用~
技术分享

  上面那段代码中,活动菜单列表第三个按钮日出日落的弹窗方法,我们还没有定义,现在来完成弹窗功能,继续在weather.js文件中编辑:

 

        //定义开启弹窗的方法
        $scope.showModal = function(){
            //如果弹窗已经存在,直接显示弹窗
            if($scope.modal){
                $scope.modal.show();
            }
            //如果弹窗不存在,则导入弹窗模板,并注入作用域以备使用
            else{
                $ionicModal.fromTemplateUrl(‘views/weather/modal-chart.html‘, {
                    scope:$scope
                })
                //当模板加载完毕后,将弹窗实例存储到作用域中
                .then(function(modal){
                    $scope.modal = modal;
                    //显示弹窗
                    $scope.modal.show();
                });
            }
        };

  弹窗配置中已经指向了弹窗模板页面,新建www/views/weather/modal-chart.html文件:

<ion-modal-view>
    <!-- 增加一个带有关闭按钮的标题栏 -->
    <ion-header-bar class="bar-dark">
        <h1 class="title">日出日落时间</h1>
        <button class="button button-clear" ng-click="hideModal()">关闭</button>
    </ion-header-bar>
    <ion-content>
    </ion-content>
</ion-modal-view>

  预览下页面效果:

技术分享

  现在弹窗加载正常,但是没有数据,我们使用SunCalc库来帮助我们计算全年的日出日落时间。执行命令$ ionic add suncalc,安装完成后,将SunCalc引入到index.html文件中:

    <script src="lib/suncalc/suncalc.js"></script>

  然后我们来更新一下showModal()方法,打开weather.js文件:

        //定义开启弹窗的方法
        $scope.showModal = function(){
            //如果弹窗已经存在,直接显示弹窗
            if($scope.modal){
                $scope.modal.show();
            }
            //如果弹窗不存在,则导入弹窗模板,并注入作用域以备使用
            else{
                $ionicModal.fromTemplateUrl(‘views/weather/modal-chart.html‘, {
                    scope:$scope
                })
                //当模板加载完毕后,将弹窗实例存储到作用域中
                .then(function(modal){
                    $scope.modal = modal;
                    //创建变量
                    var days = [];
                    var day = Date.now();
                    //为每一天增加对应数据
                    for(var i=0;i<365;i++){
                        day += 1000*60*60*24;
                        //使用SunCalc基于经纬度和时间获取日出和日落的时间
                        days.push(SunCalc.getTimes(day, $scope.params.lat, $scope.params.lng));
                    };
                    //将最后生成的列表数据注入到作用域中
                    $scope.chart = days;
                    //显示弹窗
                    $scope.modal.show();
                });
            }
        };

  之后,我们来更新一下弹窗模板,使用collection repeat来渲染列表(对于collection-repeat和ng-repeat的区别,大家可以自己查一下哟),打开modal-chart.html文件:

    <ion-content>
        <!-- 使用.list类的div嵌套元素 -->
        <div class="list">
            <!-- 使用collectionRepeat指令 -->
            <div class="item" collection-repeat="day in chart">
                <!-- 绑定日期,日出时间,日落时间到列表元素中 -->
                {{day.sunrise | date:‘MMM d‘}}:{{day.sunrise | date:‘shortTime‘}},{{day.sunset | date:‘shortTime‘}}
            </div>
        </div>
    </ion-content>

  现在我们再预览一下弹窗,看看效果~

技术分享

  天气视图最后一个功能-->弹框提示并确认收藏地点修改功能。现在,我们点击活动菜单列表里的收藏按钮时,应用只会默默的执行,不会给我们提示信息,这样不友好,用户在操作后没有得到反馈。

  我们需要在Locations服务的切换收藏方法中增加弹框方法。首先需要确认用户是否想要移除/添加收藏地点,并且当收藏地点移除/添加成功后提示用户,打开app.js文件:

            //从收藏地点中增加或者删除元素
            toggle: function(item) {
                var index = Locations.getIndex(item);
                if (index >= 0) {
                    //创建确认弹框,参数为预先定义好的对象;默认确认弹框带有确认和取消按钮
                    $ionicPopup.confirm({
                        title:‘确定吗?‘,
                        template:‘不再收藏‘ + Locations.data[index].city
                    })
                    //当单击弹框中的某个按钮时,触发该函数,当用户点击确认的时候执行删除收藏地点功能
                    .then(function(res){
                        if(res){
                            Locations.data.splice(index, 1);
                        }
                    });
                } else {
                    Locations.data.push(item);
                    //创建一个带有标题的提示弹窗,告知用户收藏成功,默认只有确认按钮
                    $ionicPopup.alert({
                        title:‘收藏成功‘
                    });
                }
            },

  现在我们来预览一下应用:

技术分享

  到现在,我们这款天气应用已经开发完了哦~

 

 

  

 

 

6 使用Ionic开发天气应用