> 웹 프론트엔드 > JS 튜토리얼 > TypeScript 및 NativeScript를 사용하여 날씨 앱 만들기

TypeScript 및 NativeScript를 사용하여 날씨 앱 만들기

王林
풀어 주다: 2023-08-27 22:17:11
원래의
722명이 탐색했습니다.

이 튜토리얼에서는 TypeScript 언어를 사용하여 NativeScript로 날씨 앱을 구축하는 방법을 보여 드리겠습니다.

이 시리즈의 이전 기사에서는 순수 JavaScript를 사용하여 메모 작성 애플리케이션을 만들었습니다. 이번에는 TypeScript를 사용해 보겠습니다. 먼저 TypeScript가 NativeScript 애플리케이션을 구축하는 데 좋은 선택인 이유를 이해해 보겠습니다.

1. TypeScript를 선택하는 이유는 무엇입니까?

TypeScript는 NativeScript의 일급 시민입니다. NativeScript 핵심 팀이 NativeScript 프레임워크 자체를 구축하는 데 사용됩니다. TypeScript를 사용하여 NativeScript 앱을 개발하려는 몇 가지 이유는 다음과 같습니다.

  • TypeScript는 JavaScript로 컴파일됩니다. 컴파일러가 실행되면 코드에 존재할 수 있는 모든 오류를 포착하므로 NativeScript 컴파일러가 완료될 때까지 기다리지 않고 즉시 조치를 취할 수 있습니다. 이는 개발자로서 생산성이 더 높아질 수 있음을 의미합니다.
  • TypeScript를 사용하면 클래스, 모듈, 화살표 함수, 템플릿 리터럴 등과 같은 ES6 기능을 사용할 수 있습니다. 이는 더 나은 코드를 구성하고 작성하기 위해 더 많은 도구를 사용할 수 있다는 것을 의미합니다.

납득할 수 없거나 TypeScript가 NativeScript 개발에 적합한 이유에 대해 더 알고 싶다면 TypeScript를 사용하여 더 나은 NativeScript 앱 구축을 확인하세요.

2. 도구

TypeScript가 제공하는 기능을 최대한 활용하려면 Visual Studio Code 텍스트 편집기를 사용하는 것이 좋습니다. TypeScript 코드를 작성할 때 스마트 자동 완성을 제공하는 IntelliSense 기능이 있고 Git과 통합되며 디버깅 기능도 있습니다.

가장 중요한 점은 NativeScript 애플리케이션을 개발할 때 작업 효율성을 향상시킬 수 있는 NativeScript 플러그인도 있다는 것입니다. 제가 찾은 유용한 기능 중 하나는 에뮬레이터 통합입니다. 이를 통해 텍스트 편집기에서 직접 에뮬레이터를 실행하고 텍스트 편집기 자체에서 애플리케이션을 디버그할 수 있습니다. Visual Studio Code는 무료이며 모든 주요 플랫폼(Windows, Linux, OS X)에서 사용할 수 있습니다.

하지만 텍스트 편집기의 편안함을 벗어나고 싶지 않다면 확장 프로그램을 설치하여 TypeScript로 코딩을 더 잘할 수도 있습니다. Atom의 경우atom-typescript 플러그인이 있습니다. Sublime Text의 경우 TypeScript Sublime 플러그인이 있습니다.

3.앱 개요

우리가 만들 앱은 날씨 앱입니다. 여기에는 다음 페이지가 포함됩니다:

  • 홈 페이지에는 현재 날씨와 온도, 기압, 습도 등 일부 관련 정보가 표시됩니다.
  • 날씨 예보 페이지는 향후 5일 동안의 기상 조건에 대한 5일 예보를 표시합니다.

홈페이지는 다음과 같습니다

TypeScript 및 NativeScript를 사용하여 날씨 앱 만들기

예측 페이지입니다:

TypeScript 및 NativeScript를 사용하여 날씨 앱 만들기

앱의 GitHub 저장소에서 전체 소스 코드를 찾을 수 있습니다.

4. OpenWeatherMap

날씨 데이터는 OpenWeatherMap API에서 제공되며 다른 API와 마찬가지로 이를 사용하려면 API 키를 등록해야 합니다. 가서 계정을 등록하세요. 기다리겠습니다. 로그인한 후 API Keys 페이지로 이동하여 Key 필드의 값을 복사하여 안전한 곳에 저장하세요. 나중에 애플리케이션 생성을 시작할 때 필요합니다.

TypeScript 및 NativeScript를 사용하여 날씨 앱 만들기

5. 앱 만들기

이제 앱이 어떤 모습일지 알았으니 실제로 앱 제작을 시작할 차례입니다. TypeScript 템플릿을 사용하여 새 NativeScript 프로젝트를 만드는 것부터 시작하세요.

으아악

완료되면 app 文件夹并创建以下文件夹和文件。为了方便起见,您还可以下载或克隆 GitHub 存储库,然后从 app 폴더로 이동하여 파일을 복사하세요.

으아악

이 작업은 app 目录中工作,因此每次我引用文件路径或文件夹时,都会假设 app 디렉터리가 루트 디렉터리인 경우에만 수행됩니다. p>

종속성 설치

이 앱에는 NativeScript 위치정보 모듈 및 Moment 등 여러 가지 종속성이 필요합니다. 다음 명령을 사용하여 위치정보 모듈을 설치할 수 있습니다:

으아악

그리고 설치 순간:

으아악

Geolocation 모듈은 사용자의 현재 위치를 결정하는 데 사용됩니다. Moment를 사용하면 나중에 API를 통해 얻을 수 있는 Unix 타임스탬프의 형식을 쉽게 지정할 수 있습니다.

자주 사용되는 모듈

애플리케이션의 각 페이지에 대한 코드를 살펴보기에 앞서, 먼저 애플리케이션 전반에 걸쳐 사용될 사용자 정의 모듈을 살펴보겠습니다.

상시

常量模块 (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 组件用于包装所有内容,以便当内容超出屏幕尺寸可以显示时自动生成垂直滚动条。

由于我们要从远程服务器加载数据,因此使用 ActivityIndi​​cator 组件来显示平台默认的加载动画。这需要您提供 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 像素,天气属性消耗所需的空间量包含文本。请注意 rowcol 属性在 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" />
로그인 후 복사

主页 JavaScript

打开 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> 作为类型断言的原因。之后,我们将主视图模型绑定到页面。这将允许我们在类中添加或更新属性,并且它将立即反映在页面上。

在主视图模型(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 用于 iconsmall-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'] 的使用。使用 RepeaterListView 组件时,您无法访问为要使用的列表指定的数组之外的数据 — 除非您显式指定父组件数据可用的地方。这就是 $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>
로그인 후 복사

预测页面 JavaScript

预测页面的代码与主页的代码几乎相同。唯一的区别是导航功能用于返回主页,而我们使用 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;
}
로그인 후 복사

6.更改默认应用程序图标

您可以通过转到 App_Resources 文件夹来更改默认应用程序图标。在那里您可以看到 Android 和 iOS 文件夹。对于Android,您可以替换以下每个文件夹中的图像文件来更改图标:

  • drawable-hdpi
  • drawable-ldpi
  • drawable-mdpi

对于 iOS,您要替换的是 Assets.xcassets/AppIcon.appiconset 文件夹内的图像。

如果您想轻松创建适用于 Android 和 iOS 的图标,请查看 MakeAppIcon。只需选择一个图像文件用作图标,它就会自动为 Android 和 iOS 生成不同的尺寸。然后您可以将它们移动到上面提到的文件夹中。只需确保您获得正确的尺寸,并且名称与它们替换的图像相同。

7.运行应用程序

您可以通过执行以下 tns 命令照常在设备或模拟器上运行该应用:

tns run {platform}
tns livesync {platform} --watch
로그인 후 복사

现在我们使用 TypeScript 的唯一区别是,在每个任务开始时都有一个额外的步骤,即将 TypeScript 文件编译为 JavaScript。 TypeScript 强大的类型检查充当了安全网,可以在 NativeScript 编译应用之前捕获一些错误。

结论

在本教程中,您学习了如何使用 TypeScript 语言通过 NativeScript 构建应用程序。具体来说,您学习了以下概念:

  • 通过将相关文件放入各自的文件夹中来构建您的应用。
  • 使用模块重用代码。
  • 使用视图模型为应用页面提供数据和功能。
  • 使用地理定位插件确定位置。
  • 发出 HTTP 请求。
  • 使用字体图标。
  • 在页面之间导航。

我将为您提供一些资源,以继续您使用 NativeScript 开发出色应用程序的旅程:

  • 阅读有关如何在 Google Play 或 App Store 上发布 NativeScript 应用的指南。
  • 订阅 NativeScript 简讯并了解 NativeScript 社区的最新动态。
  • 查看 Awesome NativeScript,这是一系列出色的 NativeScript 库、工具、资源和应用程序。
  • 观看我们的 Envato Tuts+ TypeScript 入门课程。

위 내용은 TypeScript 및 NativeScript를 사용하여 날씨 앱 만들기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿