6 使用Ionic开发天气应用

Posted 姜腾腾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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="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="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>
            

以上是关于6 使用Ionic开发天气应用的主要内容,如果未能解决你的问题,请参考以下文章

使用离子框架将屏幕分成两个部分?

4 Ionic导航和核心组件--旅游应用

技术干货前端开发之IONIC移动端开发

使用 Phaser.js 和 Ionic 开发游戏应用程序(缓慢/不稳定的渲染)

Ionic 6 的生产网址是啥

混合式移动应用开发浅析之Ionic/Cordova vs React Native