Многие популярные погодные приложения в Google Play либо полны рекламы, требуют слишком много разрешений, либо содержат функции, которые большинство из нас никогда не используют. Не было бы замечательно, если бы вы могли создать собственное приложение погоды с нуля?
В этом уроке я собираюсь показать вам, как. Наше приложение будет иметь простой и минималистичный пользовательский интерфейс, показывающий пользователю именно то, что ему нужно знать о текущих погодных условиях. Давайте начнем.
Ищете ярлык?
Из этого туториала вы узнаете, как создать приложение погоды с нуля, но одной из альтернатив является использование одного из шаблонов приложения погоды Android на Envato Market.
Например, Weminder предоставляет простой, понятный пользовательский интерфейс и все основные функции погодного приложения, так что вы можете настроить его для своих собственных целей.
Или, если вы хотите что-то уникальное и специально разработанное, отправляйтесь в Envato Studio, чтобы проверить ассортимент предлагаемых здесь мобильных услуг и услуг по разработке приложений .
1. Предпосылки
Прежде чем продолжить, дважды проверьте, что у вас есть следующие настройки:
- Пакет Eclipse ADT : вы можете скачать его на сайте разработчика Android .
- Ключ API OpenWeatherMap : Это не обязательно для завершения учебника, но это бесплатно. Вы можете получить его, зарегистрировавшись на сайте OpenWeatherMap .
- Иконки : я рекомендую скачать шрифт иконок погоды, созданный Эриком Флауэрсом . Вам нужно скачать файл TTF , потому что мы будем использовать его в собственном приложении. Мы будем использовать шрифт для отображения различных значков в зависимости от погодных условий.
2. Создать новый проект
Я собираюсь назвать это приложение SimpleWeather , но не стесняйтесь давать ему любое имя. Введите уникальное имя пакета, установите минимальный требуемый SDK для Android 2.2 и установите целевой SDK для Android 4.4 . Вы можете оставить тему на Holo Dark .
Это приложение будет иметь только одну Activity
и оно будет основано на шаблоне пустых действий, как показано ниже.
Назовите Activity
WeatherActivity . Мы будем использовать Fragment
внутри этого Activity
. Макет, связанный с Activity
представляет собой activity_weather.xml . Макет, связанный с Fragment
является фрагментом_weather.xml .
3. Добавьте пользовательский шрифт
Скопируйте weathericons-регулярно-webfont.ttf в каталог assets / fonts вашего проекта и переименуйте его в weather.ttf .
4. Отредактируйте Манифест
Единственное разрешение, в котором нуждается это приложение — это android.permission.INTERNET
.
1
|
<uses-permission android:name=»android.permission.INTERNET»/>
|
Чтобы этот урок был простым, мы будем поддерживать только портретный режим. Узел activity
манифеста должен выглядеть следующим образом:
01
02
03
04
05
06
07
08
09
10
|
<activity
android:name=»ah.hathi.simpleweather.WeatherActivity»
android:label=»@string/app_name»
android:screenOrientation=»portrait»
>
<intent-filter>
<action android:name=»android.intent.action.MAIN» />
<category android:name=»android.intent.category.LAUNCHER» />
</intent-filter>
</activity>
|
5. Отредактируйте макет упражнения
В файле activity_weather.xml нечего менять. У него уже должен быть FrameLayout
. Добавьте дополнительное свойство, чтобы изменить цвет background
на #FF0099CC
.
1
2
3
4
5
6
7
8
|
<FrameLayout xmlns:android=»http://schemas.android.com/apk/res/android»
xmlns:tools=»http://schemas.android.com/tools»
android:id=»@+id/container»
android:layout_width=»match_parent»
android:layout_height=»match_parent»
tools:context=»ah.hathi.simpleweather.WeatherActivity»
tools:ignore=»MergeRootFrame»
android:background=»#FF0099CC» />
|
6. Отредактируйте макет фрагмента
Отредактируйте frag_weather.xml , добавив пять тегов TextView
чтобы показать следующую информацию:
- город и страна
- текущая температура
- значок, показывающий текущее состояние погоды
- отметка времени, сообщающая пользователю, когда информация о погоде последний раз обновлялась
- более подробная информация о текущей погоде, например, описание и влажность
Используйте RelativeLayout
для упорядочивания текстовых представлений. Вы можете настроить textSize
для различных устройств.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
<RelativeLayout xmlns:android=»http://schemas.android.com/apk/res/android»
xmlns:tools=»http://schemas.android.com/tools»
android:layout_width=»match_parent»
android:layout_height=»match_parent»
android:paddingBottom=»@dimen/activity_vertical_margin»
android:paddingLeft=»@dimen/activity_horizontal_margin»
android:paddingRight=»@dimen/activity_horizontal_margin»
android:paddingTop=»@dimen/activity_vertical_margin»
tools:context=»ah.hathi.simpleweather.WeatherActivity$PlaceholderFragment» >
<TextView
android:id=»@+id/city_field»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_alignParentTop=»true»
android:layout_centerHorizontal=»true»
android:textAppearance=»?android:attr/textAppearanceLarge» />
<TextView
android:id=»@+id/updated_field»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_below=»@+id/city_field»
android:layout_centerHorizontal=»true»
android:textAppearance=»?android:attr/textAppearanceMedium»
android:textSize=»13sp» />
<TextView
android:id=»@+id/weather_icon»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_centerVertical=»true»
android:layout_centerHorizontal=»true»
android:textAppearance=»?android:attr/textAppearanceLarge»
android:textSize=»70sp»
/>
<TextView
android:id=»@+id/current_temperature_field»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_alignParentBottom=»true»
android:layout_centerHorizontal=»true»
android:textAppearance=»?android:attr/textAppearanceLarge»
android:textSize=»40sp» />
<TextView
android:id=»@+id/details_field»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:layout_below=»@+id/weather_icon»
android:layout_centerHorizontal=»true»
android:textAppearance=»?android:attr/textAppearanceMedium»
/>
</RelativeLayout>
|
7. Отредактируйте strings.xml
Этот файл содержит строки, используемые в нашем приложении, а также коды символов Unicode, которые мы будем использовать для отображения значков погоды. Приложение сможет отображать восемь различных типов погодных условий. Если вы хотите справиться больше, то обратитесь к этой шпаргалке . Добавьте следующее в values / strings.xml :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?xml version=»1.0″ encoding=»utf-8″?>
<resources>
<string name=»app_name»>Simple Weather</string>
<string name=»change_city»>Change city</string>
<!— Put your own APP ID here —>
<string name=»open_weather_maps_app_id»>11111</string>
<string name=»weather_sunny»></string>
<string name=»weather_clear_night»></string>
<string name=»weather_foggy»></string>
<string name=»weather_cloudy»></string>
<string name=»weather_rainy»></string>
<string name=»weather_snowy»></string>
<string name=»weather_thunder»></string>
<string name=»weather_drizzle»></string>
<string name=»place_not_found»>Sorry, no weather data found.</string>
</resources>
|
8. Добавить пункт меню
Пользователь должен иметь возможность выбрать город, погода которого он хочет видеть. Отредактируйте menu / weather.xml и добавьте элемент для этой опции.
01
02
03
04
05
06
07
08
09
10
11
12
|
<menu xmlns:android=»http://schemas.android.com/apk/res/android»
xmlns:app=»http://schemas.android.com/apk/res-auto»
xmlns:tools=»http://schemas.android.com/tools»
tools:context=»ah.hathi.simpleweather.WeatherActivity» >
<item
android:id=»@+id/change_city»
android:orderInCategory=»1″
android:title=»@string/change_city»
app:showAsAction=»never»/>
</menu>
|
Теперь, когда все XML-файлы готовы к использованию, давайте перейдем к API OpenWeatherMap и запросим данные о погоде.
9. Получить данные из OpenWeatherMap
Мы можем получить текущую информацию о погоде для любого города в формате JSON с помощью API OpenWeatherMap. В строке запроса мы передаем название города и метрическую систему, в которой должны быть результаты.
Например, чтобы получить текущую информацию о погоде для Канберры, используя метрическую систему, мы отправляем запрос по адресу http://api.openweathermap.org/data/2.5/weather?q=Canberra&units=metric.
Ответ, который мы получаем от API, выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
{
«base»: «cmc stations»,
«clouds»: {
«all»: 90
},
«cod»: 200,
«coord»: {
«lat»: -35.28,
«lon»: 149.13
},
«dt»: 1404390600,
«id»: 2172517,
«main»: {
«humidity»: 100,
«pressure»: 1023,
«temp»: -1,
«temp_max»: -1,
«temp_min»: -1
},
«name»: «Canberra»,
«sys»: {
«country»: «AU»,
«message»: 0.313,
«sunrise»: 1404335563,
«sunset»: 1404370965
},
«weather»: [
{
«description»: «overcast clouds»,
«icon»: «04n»,
«id»: 804,
«main»: «Clouds»
}
],
«wind»: {
«deg»: 305.004,
«speed»: 1.07
}
}
|
Создайте новый класс Java и назовите его RemoteFetch.java . Этот класс отвечает за получение данных о погоде из API OpenWeatherMap.
Мы используем класс HttpURLConnection
чтобы сделать удаленный запрос. OpenWeatherMap API ожидает ключ API в заголовке HTTP с именем x-api-key
. Это указано в нашем запросе с использованием метода setRequestProperty
.
Мы используем BufferedReader
для чтения ответа API в StringBuffer
. Когда у нас будет полный ответ, мы преобразуем его в объект JSONObject
.
Как видно из приведенного выше ответа, данные JSON содержат поле с именем cod
. Его значение равно 200
если запрос был успешным. Мы используем это значение, чтобы проверить, содержит ли ответ JSON текущую информацию о погоде или нет.
Класс RemoteFetch.java должен выглядеть следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package ah.hathi.simpleweather;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONObject;
import android.content.Context;
import android.util.Log;
public class RemoteFetch {
private static final String OPEN_WEATHER_MAP_API =
«http://api.openweathermap.org/data/2.5/weather?q=%s&units=metric»;
public static JSONObject getJSON(Context context, String city){
try {
URL url = new URL(String.format(OPEN_WEATHER_MAP_API, city));
HttpURLConnection connection =
(HttpURLConnection)url.openConnection();
connection.addRequestProperty(«x-api-key»,
context.getString(R.string.open_weather_maps_app_id));
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
StringBuffer json = new StringBuffer(1024);
String tmp=»»;
while((tmp=reader.readLine())!=null)
json.append(tmp).append(«\n»);
reader.close();
JSONObject data = new JSONObject(json.toString());
// This value will be 404 if the request was not
// successful
if(data.getInt(«cod») != 200){
return null;
}
return data;
}catch(Exception e){
return null;
}
}
}
|
10. Храните город в качестве предпочтения
Пользователь не должен указывать название города каждый раз, когда он хочет использовать приложение. Приложение должно помнить последний город, в котором был заинтересован пользователь. Мы делаем это, используя SharedPreferences
. Однако вместо прямого доступа к этим предпочтениям из нашего класса Activity
, для этой цели лучше создать отдельный класс.
Создайте новый класс Java и назовите его CityPreference.java . Чтобы сохранить и получить название города, создайте два метода setCity
и getCity
. Объект SharedPreferences
инициализируется в конструкторе. Класс CityPreference.java должен выглядеть следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package ah.hathi.simpleweather;
import android.app.Activity;
import android.content.SharedPreferences;
public class CityPreference {
SharedPreferences prefs;
public CityPreference(Activity activity){
prefs = activity.getPreferences(Activity.MODE_PRIVATE);
}
// If the user has not chosen a city yet, return
// Sydney as the default city
String getCity(){
return prefs.getString(«city», «Sydney, AU»);
}
void setCity(String city){
prefs.edit().putString(«city», city).commit();
}
}
|
11. Создайте фрагмент
Создайте новый класс Java и назовите его WeatherFragment.java . Этот фрагмент использует frag_weather.xml в качестве макета. Объявите пять объектов TextView
и инициализируйте их в методе onCreateView
. Объявите новый объект Typeface
именем weatherFont
. Объект TypeFace
будет указывать на веб-шрифт, который вы скачали и сохранили в папке assets / fonts .
Мы будем использовать отдельный Thread
для асинхронной выборки данных из API OpenWeatherMap. Мы не можем обновить пользовательский интерфейс из такого фонового потока. Поэтому нам нужен объект Handler
, который мы инициализируем в конструкторе класса WeatherFragment
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public class WeatherFragment extends Fragment {
Typeface weatherFont;
TextView cityField;
TextView updatedField;
TextView detailsField;
TextView currentTemperatureField;
TextView weatherIcon;
Handler handler;
public WeatherFragment(){
handler = new Handler();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_weather, container, false);
cityField = (TextView)rootView.findViewById(R.id.city_field);
updatedField = (TextView)rootView.findViewById(R.id.updated_field);
detailsField = (TextView)rootView.findViewById(R.id.details_field);
currentTemperatureField = (TextView)rootView.findViewById(R.id.current_temperature_field);
weatherIcon = (TextView)rootView.findViewById(R.id.weather_icon);
weatherIcon.setTypeface(weatherFont);
return rootView;
}
}
|
Инициализируйте объект weatherFont
, вызвав createFromAsset
класса Typeface
. Мы также updateWeatherData
метод onCreate
в onCreate
.
1
2
3
4
5
6
|
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
weatherFont = Typeface.createFromAsset(getActivity().getAssets(), «fonts/weather.ttf»);
updateWeatherData(new CityPreference(getActivity()).getCity());
}
|
В updateWeatherData
мы запускаем новый поток и вызываем getJSON
RemoteFetch
класса RemoteFetch
. Если значение, возвращаемое getJSON
равно null
, мы выводим сообщение об ошибке пользователю. Если это не так, мы renderWeather
метод renderWeather
.
Только основной Thread
может обновлять пользовательский интерфейс приложения Android. Вызов Toast
или renderWeather
непосредственно из фонового потока может привести к ошибке во время выполнения. Вот почему мы вызываем эти методы, используя метод post
handler
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private void updateWeatherData(final String city){
new Thread(){
public void run(){
final JSONObject json = RemoteFetch.getJSON(getActivity(), city);
if(json == null){
handler.post(new Runnable(){
public void run(){
Toast.makeText(getActivity(),
getActivity().getString(R.string.place_not_found),
Toast.LENGTH_LONG).show();
}
});
} else {
handler.post(new Runnable(){
public void run(){
renderWeather(json);
}
});
}
}
}.start();
}
|
Метод renderWeather
использует данные JSON для обновления объектов TextView
. weather
узел ответа JSON представляет собой массив данных. В этом уроке мы будем использовать только первый элемент массива данных о погоде.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private void renderWeather(JSONObject json){
try {
cityField.setText(json.getString(«name»).toUpperCase(Locale.US) +
«, » +
json.getJSONObject(«sys»).getString(«country»));
JSONObject details = json.getJSONArray(«weather»).getJSONObject(0);
JSONObject main = json.getJSONObject(«main»);
detailsField.setText(
details.getString(«description»).toUpperCase(Locale.US) +
«\n» + «Humidity: » + main.getString(«humidity») + «%» +
«\n» + «Pressure: » + main.getString(«pressure») + » hPa»);
currentTemperatureField.setText(
String.format(«%.2f», main.getDouble(«temp»))+ » ℃»);
DateFormat df = DateFormat.getDateTimeInstance();
String updatedOn = df.format(new Date(json.getLong(«dt»)*1000));
updatedField.setText(«Last update: » + updatedOn);
setWeatherIcon(details.getInt(«id»),
json.getJSONObject(«sys»).getLong(«sunrise») * 1000,
json.getJSONObject(«sys»).getLong(«sunset») * 1000);
}catch(Exception e){
Log.e(«SimpleWeather», «One or more fields not found in the JSON data»);
}
}
|
В конце метода renderWeather
мы вызываем setWeatherIcon
с id
текущей погоды, а также времени восхода и захода солнца. Установить значок погоды немного сложно, потому что API OpenWeatherMap поддерживает больше погодных условий, чем мы можем использовать с веб-шрифтом, который мы используем. К счастью, погодные идентификаторы следуют шаблону, о котором вы можете прочитать больше на веб-сайте OpenWeatherMap .
Вот как мы отображаем идентификатор погоды на иконку:
- коды погоды в диапазоне 200 связаны с грозами, что означает, что мы можем использовать
R.string.weather_thunder
для этих - коды погоды в диапазоне 300 относятся к моросящим дождям, и мы используем
R.string.weather_drizzle
для этих - коды погоды в диапазоне 500 означают дождь, и мы используем
R.string.weather_rain
для них - и так далее …
Мы используем время восхода и захода солнца для отображения солнца или луны, в зависимости от текущего времени дня и только при ясной погоде.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private void setWeatherIcon(int actualId, long sunrise, long sunset){
int id = actualId / 100;
String icon = «»;
if(actualId == 800){
long currentTime = new Date().getTime();
if(currentTime>=sunrise && currentTime<sunset) {
icon = getActivity().getString(R.string.weather_sunny);
} else {
icon = getActivity().getString(R.string.weather_clear_night);
}
} else {
switch(id) {
case 2 : icon = getActivity().getString(R.string.weather_thunder);
break;
case 3 : icon = getActivity().getString(R.string.weather_drizzle);
break;
case 7 : icon = getActivity().getString(R.string.weather_foggy);
break;
case 8 : icon = getActivity().getString(R.string.weather_cloudy);
break;
case 6 : icon = getActivity().getString(R.string.weather_snowy);
break;
case 5 : icon = getActivity().getString(R.string.weather_rainy);
break;
}
}
weatherIcon.setText(icon);
}
|
Конечно, вы можете обрабатывать больше погодных условий, добавляя больше операторов case
в оператор setWeatherIcon
метода setWeatherIcon
.
Наконец, добавьте метод changeCity
к фрагменту, чтобы позволить пользователю обновить текущий город. Метод changeCity
будет вызываться только из основного класса Activity
.
1
2
3
|
public void changeCity(String city){
updateWeatherData(city);
}
|
12. Изменить действие
Во время настройки проекта Eclipse заполнил WeatherActivity.java некоторым стандартным кодом. Замените реализацию по onCreate
метода onCreate
на приведенную ниже, в которой мы используем WeatherFragment
. Метод onCreate
должен выглядеть следующим образом:
01
02
03
04
05
06
07
08
09
10
11
|
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new WeatherFragment())
.commit();
}
}
|
Затем отредактируйте метод onOptionsItemSelected
и обработайте единственный onOptionsItemSelected
у нас пункт меню. Все, что вам нужно сделать, это вызвать метод showInputDialog
.
В методе showInputDialog
мы используем AlertDialog.Builder
для создания объекта Dialog
который предлагает пользователю ввести название города. Эта информация передается в метод changeCity
, который сохраняет название города с CityPreference
класса CityPreference
и вызывает метод changeCity
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.change_city){
showInputDialog();
}
return false;
}
private void showInputDialog(){
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(«Change city»);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(input);
builder.setPositiveButton(«Go», new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
changeCity(input.getText().toString());
}
});
builder.show();
}
public void changeCity(String city){
WeatherFragment wf = (WeatherFragment)getSupportFragmentManager()
.findFragmentById(R.id.container);
wf.changeCity(city);
new CityPreference(this).setCity(city);
}
|
Ваше приложение погоды теперь готово. Создайте проект и разверните его на устройстве Android для тестирования.
Вывод
Теперь у вас есть полнофункциональное приложение погоды. Не стесняйтесь исследовать API OpenWeatherMap для дальнейшего улучшения вашего приложения. Возможно, вы также захотите использовать больше значков погоды , потому что в настоящее время мы используем только небольшую их часть.