Статьи

Создать приложение погоды на Android

Конечный продукт
Что вы будете создавать

Многие популярные погодные приложения в Google Play либо полны рекламы, требуют слишком много разрешений, либо содержат функции, которые большинство из нас никогда не используют. Не было бы замечательно, если бы вы могли создать собственное приложение погоды с нуля?

В этом уроке я собираюсь показать вам, как. Наше приложение будет иметь простой и минималистичный пользовательский интерфейс, показывающий пользователю именно то, что ему нужно знать о текущих погодных условиях. Давайте начнем.

Из этого туториала вы узнаете, как создать приложение погоды с нуля, но одной из альтернатив является использование одного из шаблонов приложения погоды Android на Envato Market.

Например, Weminder предоставляет простой, понятный пользовательский интерфейс и все основные функции погодного приложения, так что вы можете настроить его для своих собственных целей.

Шаблон приложения погоды Weminder на Envato Market
Шаблон приложения погоды Weminder на Envato Market

Или, если вы хотите что-то уникальное и специально разработанное, отправляйтесь в Envato Studio, чтобы проверить ассортимент предлагаемых здесь мобильных услуг и услуг по разработке приложений .

Прежде чем продолжить, дважды проверьте, что у вас есть следующие настройки:

  • Пакет Eclipse ADT : вы можете скачать его на сайте разработчика Android .
  • Ключ API OpenWeatherMap : Это не обязательно для завершения учебника, но это бесплатно. Вы можете получить его, зарегистрировавшись на сайте OpenWeatherMap .
  • Иконки : я рекомендую скачать шрифт иконок погоды, созданный Эриком Флауэрсом . Вам нужно скачать файл TTF , потому что мы будем использовать его в собственном приложении. Мы будем использовать шрифт для отображения различных значков в зависимости от погодных условий.

Я собираюсь назвать это приложение SimpleWeather , но не стесняйтесь давать ему любое имя. Введите уникальное имя пакета, установите минимальный требуемый SDK для Android 2.2 и установите целевой SDK для Android 4.4 . Вы можете оставить тему на Holo Dark .

Создать новое приложение для Android

Это приложение будет иметь только одну Activity и оно будет основано на шаблоне пустых действий, как показано ниже.

Создать новый вид деятельности

Назовите Activity WeatherActivity . Мы будем использовать Fragment внутри этого Activity . Макет, связанный с Activity представляет собой activity_weather.xml . Макет, связанный с Fragment является фрагментом_weather.xml .

Экран Сведения о деятельности

Скопируйте weathericons-регулярно-webfont.ttf в каталог assets / fonts вашего проекта и переименуйте его в weather.ttf .

Единственное разрешение, в котором нуждается это приложение — это 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>

В файле 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» />

Отредактируйте 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>

Этот файл содержит строки, используемые в нашем приложении, а также коды символов 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»>&#xf00d;</string>
    <string name=»weather_clear_night»>&#xf02e;</string>
     
    <string name=»weather_foggy»>&#xf014;</string>
    <string name=»weather_cloudy»>&#xf013;</string>
    <string name=»weather_rainy»>&#xf019;</string>
    <string name=»weather_snowy»>&#xf01b;</string>
    <string name=»weather_thunder»>&#xf01e;</string>
    <string name=»weather_drizzle»>&#xf01c;</string>
     
    <string name=»place_not_found»>Sorry, no weather data found.</string>
     
</resources>

Пользователь должен иметь возможность выбрать город, погода которого он хочет видеть. Отредактируйте 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 и запросим данные о погоде.

Мы можем получить текущую информацию о погоде для любого города в формате 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;
        }
    }
}

Пользователь не должен указывать название города каждый раз, когда он хочет использовать приложение. Приложение должно помнить последний город, в котором был заинтересован пользователь. Мы делаем это, используя 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();
    }
     
}

Создайте новый класс 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);
}

Во время настройки проекта 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 для дальнейшего улучшения вашего приложения. Возможно, вы также захотите использовать больше значков погоды , потому что в настоящее время мы используем только небольшую их часть.