Maison >interface Web >js tutoriel >Créez une application météo à l'aide de TypeScript et NativeScript
Dans ce tutoriel, je vais vous montrer comment créer une application météo en NativeScript en utilisant le langage TypeScript.
Dans l'article précédent de cette série, nous avons créé une application de prise de notes utilisant du JavaScript pur. Cette fois, nous utiliserons TypeScript. Comprenons d’abord pourquoi TypeScript est un bon choix pour créer des applications NativeScript.
TypeScript est un citoyen de premier ordre dans NativeScript. Il est utilisé par l'équipe principale de NativeScript pour créer le framework NativeScript lui-même. Voici quelques raisons pour lesquelles vous pourriez vouloir développer des applications NativeScript à l'aide de TypeScript :
Si je ne vous ai pas convaincu ou si vous souhaitez en savoir plus sur les raisons pour lesquelles TypeScript est un bon choix pour développer avec NativeScript, vous pouvez consulter Créer de meilleures applications NativeScript avec TypeScript.
Pour profiter pleinement de ce que TypeScript a à offrir, je vous recommande d'utiliser l'éditeur de texte Visual Studio Code. Il dispose d'une fonctionnalité IntelliSense qui fournit une saisie semi-automatique intelligente lorsque vous écrivez du code TypeScript, il s'intègre à Git et dispose également de capacités de débogage.
Plus important encore, il existe également un plug-in NativeScript qui vous permet d'améliorer votre efficacité de travail lors du développement d'applications NativeScript. Une fonctionnalité que j’ai trouvée utile est l’intégration de l’émulateur. Cela vous permet d'exécuter l'émulateur directement depuis l'éditeur de texte et de déboguer l'application depuis l'éditeur de texte lui-même. Visual Studio Code est gratuit et disponible sur toutes les principales plateformes (Windows, Linux, OS X).
Mais si vous ne voulez pas quitter le confort de votre éditeur de texte, vous pouvez également installer des extensions pour améliorer votre codage avec TypeScript. Pour Atom, il existe le plugin atom-typescript. Pour Sublime Text, il existe le plugin TypeScript Sublime.
L'application que nous allons créer est une application météo. Il contiendra les pages suivantes :
La page d'accueil est la suivante :
Voici la page de prédiction :
Vous pouvez trouver le code source complet dans le référentiel GitHub de l'application.
Les données météorologiques proviendront de l'API OpenWeatherMap et comme toute autre API, vous devrez enregistrer une clé API pour l'utiliser. Allez-y et créez un compte, j'attendrai. Une fois connecté, accédez à la page Clés API, copiez la valeur du champ Clé et enregistrez-la dans un endroit sûr. Vous en aurez besoin plus tard lorsque vous commencerez à créer votre application.
Maintenant que vous savez à quoi ressemblera votre application, il est temps de commencer à la créer. Commencez par créer un nouveau projet NativeScript à l'aide d'un modèle TypeScript :
tns create weatherApp --template typescript
Une fois terminé, accédez au dossier app
文件夹并创建以下文件夹和文件。为了方便起见,您还可以下载或克隆 GitHub 存储库,然后从 app
et copiez les fichiers.
- common + constants.ts + navigation.ts + requestor.ts + utilities.ts - fonts - pages + forecast * forecast.css * forecast.ts * forecast.xml * forecast-view-model.ts + main * main.css * main.ts * main.xml * main-view-model.ts - stores + location.ts - app.css - app.ts
Nous ne le ferons que lorsque le répertoire app
目录中工作,因此每次我引用文件路径或文件夹时,都会假设 app
est le répertoire racine. p>
L'application nécessite plusieurs dépendances : module de géolocalisation NativeScript et Moment. Vous pouvez installer le module de géolocalisation à l'aide de la commande suivante :
tns plugin add nativescript-geolocation
Et installez Moment :
npm install moment
Le module de géolocalisation est utilisé pour déterminer l'emplacement actuel de l'utilisateur. Moment permet un formatage facile des horodatages Unix, que nous obtiendrons plus tard via l'API.
Avant de regarder le code de chaque page de l'application, jetons d'abord un œil aux modules personnalisés qui seront utilisés tout au long de l'application.
常量模块 (common/constants.ts
) 包含整个应用程序中使用的所有常量值:例如 OpenWeatherMap API 的基本 URL、您之前获得的 API 密钥、路径到我们将使用的端点、天气图标的字符代码和风向。
export const WEATHER_URL = 'http://api.openweathermap.org/data/2.5/'; export const WEATHER_APIKEY = 'YOUR OPENWEATHERMAP API KEY'; export const CURRENT_WEATHER_PATH = 'weather/'; export const WEATHER_FORECAST_PATH = 'forecast/daily/'; export const WEATHER_ICONS = { day: { 'clear': 0xf00d, 'clouds': 0xf002, 'drizzle': 0xf009, 'rain': 0xf008, 'thunderstorm': 0x010, 'snow': 0xf00a, 'mist': 0xf0b6 }, night: { 'clear': 0xf02e, 'clouds': 0xf086, 'drizzle': 0xf029, 'rain': 0xf028, 'thunderstorm': 0xf02d, 'snow': 0xf02a, 'mist': 0xf04a }, neutral: { 'temperature': 0xf055, 'wind': 0xf050, 'cloud': 0xf041, 'pressure': 0xf079, 'humidity': 0xf07a, 'rain': 0xf019, 'sunrise': 0xf046, 'sunset': 0xf052 } }; export const WIND_DIRECTIONS = [ "North", "North-northeast", "Northeast", "East-northeast", "East", "East-southeast", "Southeast", "South-southeast", "South", "South-southwest", "Southwest", "West-southwest", "West", "West-northwest", "Northwest", "North-northwest" ];
实用程序模块包括各种实用程序功能:例如将度数转换为方向、确定风速的描述性文本、将开尔文转换为摄氏度以及将字符代码转换为字符。您将在稍后的页面中看到如何使用所有这些函数。
import constants = require('./constants'); export function degreeToDirection(num) { var val= Math.floor((num / 22.5) + .5); return constants.WIND_DIRECTIONS[(val % 16)]; } export function describeWindSpeed(speed) { if(speed < 0.3) { return 'calm'; } else if(speed >= 0.3 && speed < 1.6) { return 'light air'; } else if (speed >= 1.6 && speed < 3.4) { return 'light breeze'; } else if (speed >= 3.4 && speed < 5.5) { return 'gentle breeze'; } else if (speed >= 5.5 && speed < 8) { return 'moderate breeze'; } else if(speed >= 8 && speed < 10.8) { return 'fresh breeze'; } else if(speed >= 10.8 && speed < 13.9) { return 'strong breeze'; } else if(speed >= 13.9 && speed < 17.2) { return 'moderate gale'; } else if (speed >= 17.2 && speed < 20.8) { return 'gale'; } else if (speed >= 20.8 && speed < 24.5) { return 'strong gale'; } else if (speed >= 24.5 && speed < 28.5) { return 'storm'; } else if (speed >= 28.5 && speed < 32.7) { return 'violent storm'; } else if (speed >= 32.7 && speed < 42) { return 'hurricane force'; } return 'super typhoon'; } export function describeHumidity(humidity) { if (humidity >= 0 && humidity <= 40) { return 'very dry'; } else if (humidity >= 40 && humidity <= 70) { return 'dry'; } else if (humidity >= 85 && humidity <= 95) { return 'humid'; } return 'very humid'; } export function describeTemperature(temp) { var celsius = convertKelvinToCelsius(temp); if (celsius >= 0 && celsius < 7) { return 'very cold'; } else if (celsius >= 8 && celsius < 13) { return 'cold'; } else if (celsius >= 13 && celsius < 18) { return 'cool'; } else if (celsius >= 18 && celsius < 23) { return 'mild'; } else if (celsius >= 23 && celsius < 28) { return 'warm'; } else if (celsius >= 28 && celsius < 32) { return 'hot'; } return 'very hot'; } export function convertKelvinToCelsius(celsius) { return celsius - 273.15; } export function getTimeOfDay() { var hour = (new Date()).getHours(); var time_of_day = 'night'; if(hour >= 5 && hour <= 18){ time_of_day = 'day'; } return time_of_day; } export function getIcons(icon_names) { var icons = icon_names.map((name) => { return { 'name': name, 'icon': String.fromCharCode(constants.WEATHER_ICONS.neutral[name]) }; }); return icons; }
导航模块是一个自定义帮助模块,它允许我们轻松地在应用程序的所有页面之间导航。打开 common/navigation.ts
文件并添加以下内容:
import frame = require('ui/frame'); export function getStartPage() { return 'pages/main/main'; } export function goToForecastPage() { frame.topmost().navigate('pages/forecast/forecast'); } export function goToMainPage() { frame.topmost().goBack(); }
这使用框架模块导航到应用的其他页面。 getStartPage()
方法仅返回应用程序主页面的位置。 goToForecastPage()
,顾名思义,允许用户导航到预测页面。
在 NativeScript 中导航时,您需要了解当前所在位置。这就是为什么您首先需要调用 topmost()
函数来获取当前或最上面的页面,然后调用 navigate()
函数转到另一个页面。此函数接受您想要访问的页面的路径。
Requestor
模块对 OpenWeatherMap API 执行实际请求。正如 NativeScript 简介一文中提到的,NativeScript 使用 JavaScript 虚拟机来运行 JavaScript 代码。这意味着我们还可以使用浏览器中提供的功能。
其中一个函数是 fetch,它允许我们向远程服务器发出 HTTP 请求。该参数是您要发出请求的网址。它返回一个承诺,因此我们使用 then()
来等待原始响应。注意“原始”一词的使用; fetch
函数返回带有标头和其他低级信息的响应,这就是为什么我们需要调用 json()
函数来获取实际的 JSON 数据。这将返回另一个承诺,因此我们再次使用 then()
来获取实际的对象。
export function get(url){ return fetch( url ).then(function(response){ return response.json(); }).then(function(json){ return json; }); }
或者,您可以使用 Http 模块,这是在 NativeScript 中发出 HTTP 请求的更可靠的方式。
位置存储用作位置信息的存储。这使我们能够从导入此模块的任何文件中更新并获取当前位置。
export var location; export function saveLocation(loc) { location = loc; } export function getLocation() { return location; }
现在是时候查看应用程序每个页面的代码了。但在此之前,首先打开入口点文件 (app.ts
)。这使用导航模块来获取应用程序的起始页面:
import application = require("application"); import navigation = require('./common/navigation'); application.mainModule = navigation.getStartPage(); application.start();
接下来,让我们分解 pages/main/main.xml
文件。
navigatingTo
事件用于在每次用户导航到此特定页面时执行 TypeScript 文件中类似命名的函数。 CSS 类也是从 TypeScript 文件动态确定的。
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="{{ background_class }}"> ... </Page>
ScrollView
组件用于包装所有内容,以便当内容超出屏幕尺寸可以显示时自动生成垂直滚动条。
由于我们要从远程服务器加载数据,因此使用 ActivityIndicator
组件来显示平台默认的加载动画。这需要您提供 busy
属性,该属性接受一个控制是否启动动画的布尔值。默认情况下,该值设置为 true
,并且仅在应用程序向服务器发出请求后更新为 false
。
visibility
属性还用于确保组件在不播放动画时不会占用任何空间。
<StackLayout> <ScrollView> <ActivityIndicator busy="{{ is_loading }}" visibility="{{ is_loading ? 'visible' : 'collapsed' }}" /> <StackLayout visibility="{{ !is_loading ? 'visible' : 'collapsed' }}"> ... </StackLayout> </ScrollView> </StackLayout>
对于主要内容,我们在顶部显示当前天气的总体概述,在下面显示详细信息。总体概述显示代表当前天气、当前温度、天气描述和地点的图标。
<Label text="{{ icon }}" class="icon" /> <Label text="{{ temperature }}" class="temperature" /> <Label text="{{ weather }}" class="weather" textWrap="true"/> <Label text="{{ place }}" class="place" textWrap="true"/>
有关详细信息,有大量有关当前天气的信息,您可以通过查看 text
属性来猜测。每一个还附有自己的图标。
在我之前向您展示的屏幕截图中,您看到它的两个页面都使用两列布局。这就是我们使用 GridLayout
的原因。但正如您从下面的代码中看到的,我们还在每行的第一列使用 GridLayout
。
您可能会问为什么我们要这样做,而不是仅仅创建一个三列布局,其中图标位于第一列,天气属性位于第二列,值位于第三列。这是完全可以理解的,它会让代码更加简洁。
但问题是 NativeScript 版本 2.1 目前不允许其 GridLayout
使用百分比单位。这意味着我们不能使用像 10%
这样的图标作为图标,而其他两列各消耗 45%。
我们下面使用的布局通过使用 GridLayout
来包装图标和天气属性来解决该问题,图标消耗 30 像素,天气属性消耗所需的空间量包含文本。请注意 row
和 col
属性在 GridLayout
上的使用。
<GridLayout columns="*,*" rows="auto,auto,auto,auto,auto,auto,auto" cssClass="details"> <GridLayout columns="30,auto" rows="auto" row="0" col="0"> <Label text="{{ wind_icon }}" class="small-icon" row="0" col="0" /> <Label text="Wind" textWrap="true" row="0" col="1" class="label" /> </GridLayout> <Label text="{{ wind }}" textWrap="true" row="0" col="1" /> <GridLayout columns="30,auto" rows="auto" row="1" col="0"> <Label text="{{ cloud_icon }}" class="small-icon" row="0" col="0" /> <Label text="Cloudiness" textWrap="true" row="1" col="1" class="label" /> </GridLayout> <Label text="{{ clouds }}" textWrap="true" row="1" col="1" /> <GridLayout columns="30,auto" rows="auto" row="2" col="0"> <Label text="{{ pressure_icon }}" class="small-icon" row="0" col="0" /> <Label text="Pressure" textWrap="true" row="2" col="1" class="label" /> </GridLayout> <Label text="{{ pressure }}" textWrap="true" row="2" col="1" /> <GridLayout columns="30,auto" rows="auto" row="3" col="0"> <Label text="{{ humidity_icon }}" class="small-icon" row="0" col="0" /> <Label text="Humidity" textWrap="true" row="3" col="1" class="label" /> </GridLayout> <Label text="{{ humidity }}" textWrap="true" row="3" col="1" /> <GridLayout columns="30,auto" rows="auto" row="4" col="0"> <Label text="{{ rain_icon }}" class="small-icon" row="0" col="0" /> <Label text="Rain" textWrap="true" row="4" col="1" class="label" /> </GridLayout> <Label text="{{ rain }}" textWrap="true" row="4" col="1" /> <GridLayout columns="30,auto" rows="auto" row="5" col="0"> <Label text="{{ sunrise_icon }}" class="small-icon" row="0" col="0" /> <Label text="Sunrise" textWrap="true" row="5" col="1" class="label" /> </GridLayout> <Label text="{{ sunrise }}" textWrap="true" row="5" col="1" /> <GridLayout columns="30,auto" rows="auto" row="6" col="0"> <Label text="{{ sunset_icon }}" class="small-icon" row="0" col="0" /> <Label text="Sunset" textWrap="true" row="6" col="1" class="label" /> </GridLayout> <Label text="{{ sunset }}" textWrap="true" row="6" col="1" /> </GridLayout>
主页的最后一个标记是通往预测页面的按钮:
<Button text="5 day Forecast" tap="goToForecastPage" />
打开 pages/main/main.ts
文件并添加以下代码:
import { EventData } from "data/observable"; import { Page } from "ui/page"; import { MainViewModel } from "./main-view-model"; import navigation = require('../../common/navigation'); export function navigatingTo(args: EventData) { var page = <Page>args.object; page.bindingContext = new MainViewModel(); } export function goToForecastPage () { navigation.goToForecastPage(); }
在上面的代码中,我们导入了几个内置的 NativeScript 模块、主视图模型和导航。
EventData
对象是使用对象解构来提取的,这是 TypeScript 提供的 ES6 功能。 EventData
是我们作为参数传递给 navigatingTo
函数的内容,以便它可以访问导航到此页面的任何页面传入的任何数据。
它有一个 object
属性,基本上是触发事件的任何组件。在本例中,我们知道它是在 Page
组件上触发的,这就是我们使用 <page></page>
作为类型断言的原因。之后,我们将主视图模型绑定到页面。这将允许我们在类中添加或更新属性,并且它将立即反映在页面上。
在主视图模型(pages/main/main-view-model.ts
)中,首先导入我们将使用的所有模块:
import observable = require("data/observable"); import requestor = require("../../common/requestor"); import constants = require("../../common/constants"); import geolocation = require("nativescript-geolocation"); import moment = require('moment'); import utilities = require('../../common/utilities'); import locationStore = require('../../stores/locationStore');
通过扩展Observable模块来创建视图模型,这使得该类中的所有属性都是可观察的。这意味着每次此类中发生更改时,UI 中每个属性的所有引用都会更新。
export class MainViewModel extends observable.Observable { constructor() { ... } }
在构造函数内,检查是否启用了地理定位。如果未启用,请尝试通过调用 enableLocationRequest()
函数来启用它。这会触发应用要求用户启用地理定位。
super(); //call the constructor method of the parent class //check if geolocation is not enabled if (!geolocation.isEnabled()) { geolocation.enableLocationRequest(); //try to enable geolocation }
接下来判断是白天还是晚上,并根据结果设置页面背景。然后在页面中设置图标。
var time_of_day = utilities.getTimeOfDay(); this.set('background_class', time_of_day); this.setIcons();
之后,尝试确定当前位置。请注意,如果用户不允许地理定位,则该应用根本无法运行,因为天气取决于用户的位置。该应用程序将尝试在 10 秒内确定用户的位置。如果失败,则会向用户显示一条错误消息。
var location = geolocation.getCurrentLocation({timeout: 10000}). then( (loc) => { if (loc) { ... } }, (e) => { //failed to get location alert(e.message); } );
如果位置请求成功,我们使用 locationStore
保存它。这允许我们稍后访问其他页面上的位置,而无需再次请求。
locationStore.saveLocation(loc);
下面是您在 NativeScript 中请求位置时可能收到的示例响应,供您参考。您可以查看 NativeScript 的位置文档,详细了解以下每个属性。
{ "latitude":51.50853, "longitude":-0.12574, "altitude":0, "horizontalAccuracy":37.5, "verticalAccuracy":37.5, "speed":0, "direction":0, "timestamp":"2016-08-08T02:25:45.252Z", "android":{ } }
我们可以使用模板文字构建完整的 API 请求 URL,并使用请求者模块发出请求。
this.set('is_loading', true); //show the loader animation var url = `${constants.WEATHER_URL}${constants.CURRENT_WEATHER_PATH}?lat=${loc.latitude}&lon=${loc.longitude}&apikey=${constants.WEATHER_APIKEY}`; requestor.get(url).then((res) => { ... });
响应返回后,将其提取并格式化,然后将结果值设置为类的属性。由于该类是可观察的,因此这将自动更新应用程序的 UI。
this.set('is_loading', false); //stop loader animation var weather = res.weather[0].main.toLowerCase(); var weather_description = res.weather[0].description; var temperature = res.main.temp; var icon = constants.WEATHER_ICONS[time_of_day][weather]; var rain = '0'; if(res.rain){ rain = res.rain['3h']; } this.set('icon', String.fromCharCode(icon)); this.set('temperature', `${utilities.describeTemperature(Math.floor(temperature))} (${utilities.convertKelvinToCelsius(temperature).toFixed(2)} °C)`); this.set('weather', weather_description); this.set('place', `${res.name}, ${res.sys.country}`); this.set('wind', `${utilities.describeWindSpeed(res.wind.speed)} ${res.wind.speed}m/s ${utilities.degreeToDirection(res.wind.deg)} (${res.wind.deg}°)`); this.set('clouds', `${res.clouds.all}%`); this.set('pressure', `${res.main.pressure} hpa`); this.set('humidity', `${utilities.describeHumidity(res.main.humidity)} (${res.main.humidity}%)`); this.set('rain', `${rain}%`); this.set('sunrise', moment.unix(res.sys.sunrise).format('hh:mm a')); this.set('sunset', moment.unix(res.sys.sunset).format('hh:mm a'));
下面是 API 可能返回的示例响应,供您参考:
{ "coord":{ "lon":-0.13, "lat":51.51 }, "weather":[ { "id":803, "main":"Clouds", "description":"broken clouds", "icon":"04d" } ], "base":"cmc stations", "main":{ "temp":291.44, "pressure":1031.7, "humidity":82, "temp_min":290.37, "temp_max":292.25 }, "wind":{ "speed":0.3, "deg":45, "gust":1 }, "rain":{ "3h":0.075 }, "clouds":{ "all":68 }, "dt":1470545747, "sys":{ "type":3, "id":1462694692, "message":0.0043, "country":"GB", "sunrise":1470544455, "sunset":1470598626 }, "id":2643743, "name":"London", "cod":200 }
您可以在当前天气数据的文档中找到有关每个属性的详细信息。
最后。有setIcons()
函数,它设置页面中使用的所有图标:
setIcons() { var icons = utilities.getIcons([ 'temperature', 'wind', 'cloud', 'pressure', 'humidity', 'rain', 'sunrise', 'sunset' ]); icons.forEach((item) => { this.set(`${item.name}_icon`, item.icon); }); }
以下是主页的样式:
.temperature { font-size: 40px; font-weight: bold; text-align: center; } .weather { font-size: 30px; text-align: center; } .place { font-size: 20px; text-align: center; } .icon { font-family: 'weathericons'; font-size: 100px; text-align: center; } .small-icon { font-family: 'weathericons'; font-size: 18px; margin-right: 5px; } .details { margin-top: 20px; padding: 30px; font-size: 18px; } .label { font-weight: bold; } .details Label { padding: 5px 0; } Button { margin: 20px; }
请注意,使用 weathericons
作为 font-family
用于 icon
和 small-icon
类。这就是我们在 NativeScript 中使用图标字体的方式。如果您喜欢网页上的 Font Awesome 等图标字体,则可以在 NativeScript 应用中以相同的方式使用它们。
首先,下载您要使用的图标字体。此应用程序使用天气图标字体。解压 zip 存档并在解压的文件夹中转到 font
目录。将 .ttf
文件复制到应用中的 fonts
目录,并将其重命名为 weathericons.ttf
。文件名是您每次想要使用特定字体图标时用作 font-family
的值的文件名。除此之外,您还必须添加 font-size
来控制图标的大小。
现在让我们看一下预测页面的标记(pages/forecast/forecast.xml
)。在标题中,有一个后退按钮,允许用户返回主页。请注意,我们使用的是 Button
组件,而不是通用的 NavigationButton
,它是 iOS 后退按钮和 Android 导航按钮的 NativeScript 等效项。
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="{{ background_class }}"> <Page.actionBar> <ActionBar title="5-day Forecast" class="header"> <NavigationButton text="Back" android.systemIcon="ic_menu_back" tap="goToMainPage" /> </ActionBar> </Page.actionBar> ... </Page>
对于主要内容,使用 Repeater
组件,而不是通常的 ListView
。这两个组件都可以用来生成列表,但 ListView
带有更多功能。例如,当列表超出屏幕尺寸时,它会自动生成垂直滚动条。还内置无限滚动功能。
本例中使用 Repeater
组件,因为实际上并不需要我刚才提到的功能。我们所需要的只是一个简单的列表。
<StackLayout> <ScrollView> <ActivityIndicator busy="{{ is_loading }}" visibility="{{ is_loading ? 'visible' : 'collapsed' }}" /> <Repeater items="{{ forecast }}"> <Repeater.itemTemplate> ... </Repeater.itemTemplate> </Repeater> </ScrollView> </StackLayout>
每个 Repeater.itemTemplate
内部都是一个 GridLayout
,其中包含两列,一列用于一般天气信息,一列用于详细信息。
第一列是 StackLayout
包含日期、天气图标和天气描述。第二列也是一个 StackLayout
,包含四个 GridLayouts
,其中包含四个天气属性(温度、风速、云量和气压)。
第一个 GridLayout
有三列,用于包含图标、白天温度和夜间温度。其他三行只有两列 - 用于图标和天气属性的值。
<GridLayout class="item" columns="*,*" rows="auto"> <StackLayout class="day-weather" row="0" col="0"> <Label text="{{ day }}" class="date" /> <Label text="{{ icon }}" class="icon" /> <Label text="{{ description }}" textWrap="true" /> </StackLayout> <StackLayout class="details" row="0" col="1"> <GridLayout columns="30,auto,auto" rows="auto" row="0" col="0"> <Label text="{{ $parents['Page'].temperature_icon }}" class="small-icon" row="0" col="0" /> <Label text="{{ temperature.day }}" class="temp day-temp" row="0" col="1" /> <Label text="{{ temperature.night }}" class="temp night-temp" row="0" col="2" /> </GridLayout> <GridLayout columns="30,auto" rows="auto" row="1" col="0"> <Label text="{{ $parents['Page'].wind_icon }}" class="small-icon" row="0" col="0" /> <Label text="{{ wind }}" row="0" col="1" /> </GridLayout> <GridLayout columns="30,auto" rows="auto" row="2" col="0"> <Label text="{{ $parents['Page'].cloud_icon }}" class="small-icon" row="0" col="0" /> <Label text="{{ clouds }}" row="0" col="1" /> </GridLayout> <GridLayout columns="30,auto" rows="auto" row="3" col="0"> <Label text="{{ $parents['Page'].pressure_icon }}" class="small-icon" row="0" col="0" /> <Label text="{{ pressure }}" row="0" col="1" /> </GridLayout> </StackLayout> </GridLayout>
注意 $parents['Page']
的使用。使用 Repeater
或 ListView
组件时,您无法访问为要使用的列表指定的数组之外的数据 — 除非您显式指定父组件数据可用的地方。这就是 $parents['Page']
的用武之地。 $parents
是 NativeScript 中的一个特殊变量,允许您访问特定组件上的可用数据。在本例中,我们指定 Page
来访问每个天气属性的图标。
<GridLayout columns="30,auto" rows="auto" row="1" col="0"> <Label text="{{ $parents['Page'].wind_icon }}" class="small-icon" row="0" col="0" /> <Label text="{{ wind }}" row="0" col="1" /> </GridLayout>
预测页面的代码与主页的代码几乎相同。唯一的区别是导航功能用于返回主页,而我们使用 ForecastViewModel
作为视图模型。
import { EventData } from "data/observable"; import { Page } from "ui/page"; import { ForecastViewModel } from "./forecast-view-model"; import navigation = require('../../common/navigation'); export function navigatingTo(args: EventData) { var page = <Page>args.object; page.bindingContext = new ForecastViewModel(); } export function goToMainPage() { navigation.goToMainPage(); }
这是视图模型的代码(pages/forecast/forecast-view-model.ts
):
import observable = require("data/observable"); import requestor = require("../../common/requestor"); import constants = require("../../common/constants"); import moment = require('moment'); import utilities = require('../../common/utilities'); import locationStore = require('../../stores/locationStore'); export class ForecastViewModel extends observable.Observable { constructor() { super(); var location = locationStore.getLocation(); var url = `${constants.WEATHER_URL}${constants.WEATHER_FORECAST_PATH}?cnt=6&lat=${location.latitude}&lon=${location.longitude}&apikey=${constants.WEATHER_APIKEY}`; var time_of_day = utilities.getTimeOfDay(); this.set('is_loading', true); this.set('background_class', time_of_day); this.setIcons(); requestor.get(url).then((response) => { this.set('is_loading', false); var forecast = this.getForecast(response); this.set('forecast', forecast); }); } private getForecast(response) { var forecast = []; var list = response.list.splice(1); //remove first item from array of forecasts //format and push all the necessary data into a new array list.forEach((item) => { forecast.push({ day: moment.unix(item.dt).format('MMM DD (ddd)'), icon: String.fromCharCode(constants.WEATHER_ICONS['day'][item.weather[0].main.toLowerCase()]), temperature: { day: `${utilities.describeTemperature(item.temp.day)}`, night: `${utilities.describeTemperature(item.temp.night)}` }, wind: `${item.speed}m/s`, clouds: `${item.clouds}%`, pressure: `${item.pressure} hpa`, description: item.weather[0].description }) }); return forecast; } private setIcons() { var icons = utilities.getIcons(['temperature', 'wind', 'cloud', 'pressure']); icons.forEach((item) => { this.set(`${item.name}_icon`, item.icon); }); } }
在构造函数内,我们从位置存储中获取当前位置,并构造 16 天天气预报的 URL 端点。但我们只需要 5 天,而不是 16 天,因此我们指定 6 作为计数 (cnt
)。我们使用 6 是因为时区取决于服务器而不是指定的位置。这意味着 API 有可能返回前一天或当天的天气。这就是为什么有额外的 1 天作为填充。
var location = locationStore.getLocation(); var url = `${constants.WEATHER_URL}${constants.WEATHER_FORECAST_PATH}?cnt=6&lat=${location.latitude}&lon=${location.longitude}&apikey=${constants.WEATHER_APIKEY}`;
接下来,通过调用 getForecast()
函数发出请求并使用 API 响应更新 UI:
requestor.get(url).then((response) => { this.set('is_loading', false); var forecast = this.getForecast(response); this.set('forecast', forecast); });
以下是 16 天预测端点返回的示例响应。请注意,为了使示例更加简洁,我将计数设置为 1,这就是 list
属性仅包含一个对象的原因。
{ "city":{ "id":2643743, "name":"London", "coord":{ "lon":-0.12574, "lat":51.50853 }, "country":"GB", "population":0 }, "cod":"200", "message":0.0347, "cnt":1, "list":[ { "dt":1470571200, "temp":{ "day":24.69, "min":17.37, "max":24.69, "night":17.37, "eve":23.29, "morn":19.02 }, "pressure":1029.77, "humidity":0, "weather":[ { "id":500, "main":"Rain", "description":"light rain", "icon":"10d" } ], "speed":8.27, "deg":253, "clouds":0 } ] }
以下是预测页面的样式(pages/forecast/forecast.css
):
Page { font-size: 15px; } .item { padding: 20px 10px; } .day-weather { text-align: center; } .details { horizontal-align: left; } .date { font-size: 20px; } .icon { font-family: 'weathericons'; font-size: 30px; } .temp { padding: 3px; text-align: center; font-size: 15px; } .day-temp { background-color: #d0c110; } .night-temp { background-color: #505050; } .small-icon { font-family: 'weathericons'; margin-right: 5px; text-align: center; }
打开 app.css
文件并添加以下样式:
.header { background-color: #333; color: #fff; } .day { background-color: #f48024; color: #fff; } .night { background-color: #924da3; color: #fff; }
您可以通过转到 App_Resources
文件夹来更改默认应用程序图标。在那里您可以看到 Android 和 iOS 文件夹。对于Android,您可以替换以下每个文件夹中的图像文件来更改图标:
drawable-hdpi
drawable-ldpi
drawable-mdpi
对于 iOS,您要替换的是 Assets.xcassets/AppIcon.appiconset
文件夹内的图像。
如果您想轻松创建适用于 Android 和 iOS 的图标,请查看 MakeAppIcon。只需选择一个图像文件用作图标,它就会自动为 Android 和 iOS 生成不同的尺寸。然后您可以将它们移动到上面提到的文件夹中。只需确保您获得正确的尺寸,并且名称与它们替换的图像相同。
您可以通过执行以下 tns
命令照常在设备或模拟器上运行该应用:
tns run {platform} tns livesync {platform} --watch
现在我们使用 TypeScript 的唯一区别是,在每个任务开始时都有一个额外的步骤,即将 TypeScript 文件编译为 JavaScript。 TypeScript 强大的类型检查充当了安全网,可以在 NativeScript 编译应用之前捕获一些错误。
在本教程中,您学习了如何使用 TypeScript 语言通过 NativeScript 构建应用程序。具体来说,您学习了以下概念:
我将为您提供一些资源,以继续您使用 NativeScript 开发出色应用程序的旅程:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!