Создаем приложение "Погода"
Главная » Создаем приложение "Погода"

 Мы все пользуемся на своих устройствах приложениями, показывающими прогноз погоды. На Play Market можно найти бесконечное множество таких программ, хотя далеко не все они нам нравятся. И сегодня мы научимся делать своей собственное приложение, которое будет радовать нас прогнозами погоды. Согласитесь, смотреть погоду в смартфоне через свое приложение куда приятнее, чем с всех остальных. У нас должно получиться примерно так:

Вид приложения погоды

Создаем новый проект, традиционно названия я оставлю стандартными. Выбираем минимальную версию для запуска Android 2.2, выбираем Blank Activity

Сразу открываем файл манифеста AndroidManifest.xml и добавим нашему приложению разрешение на использование Интернета:

<uses-permission android:name="android.permission.INTERNET"/>

Чтобы упростить работу над приложением, будет использовать только портретный вид для нашего Activity:

<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_main.xml. Здесь нас ждет не много работы. Создадим FrameLayout и зададим цвет его фона:

<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=".MainActivity"
 tools:ignore="MergeRootFrame"
 android:background="#FF0099CC" />

Теперь создадим 5 элементов TextView, в которых будет отображаться следующая информация:

     - город и страна;

     - текущая температура;

     - иконка, соответствующая текущей погоде;

     - временная отметка, указывающая, когда было сделано последнее обновление данных о погоде;

     - более детальная информация о погоде (влажность и т.п).

Для размещения этих элементов мы воспользуемся RelativeLayout. Для этого создаем в папке layout приложения файл по имени fragment_weather.xml и добавляем в него код:

<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=".MainActivity" >
 <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>

Теперь давайте отредактируем строковые ресурсы, которые будем использовать,  в файле strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <string name="app_name">Погода</string>
 <string name="change_city">Смена города</string>
 <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">Информация не найдена.</string>
</resources>

Теперь отредактируем меню нашего приложения. В нем будет пункт, позволяющий пользователю выбрать нужный город. Открываем файл menu_main.xml и добавляем следующий код:

<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=".MainActivity" >
 <item
 android:id="@+id/change_city"
 android:orderInCategory="1"
 android:title="@string/change_city"
 app:showAsAction="never"/>
</menu>

На этом вся работа в layout файлах завершена. Теперь нам предстоит настроить сам процесс получения и отображения информации о погоде.

Мы можем получить информацию о погоде любого города с помощью OpenWeatherMap API инструментов. Например, чтобы получить информацию о состоянии погоды в австралийском городе Канберра, мы делаем вот такой запрос. Ответ, который мы получаем, выглядит примерно так:

{
 "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 класс по имени Weather.java. Этот класс как раз и будет отвечать за отбор нужной информации из результата запроса к OpenWeatherMap API

Мы используем класс HttpURLConnection для того, чтобы сделать запрос. BufferedReader используется для чтения ответа на запрос. Когда ответ получен, мы преобразовываем его в объект JSONObject. Если запрос выполнен и результаты успешно получены, то JSON данные будут содержать  поле по имени cod со значением 200 (смотрим пример кода выше, вверху). Мы будем использовать это значение для проверки, получен ответ или нет. Чтобы воплотить все вышесказанное в программе, добавляем в файл Weather.java следующий код:

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 Weather {

 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());

 //Это значение будет равно 404 если ответ не получен
 if(data.getInt("cod") != 200){
 return null;
 }

 return data;
 }catch(Exception e){
 return null;
 }
 }
}

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

Создаем новый класс под названием City.java. Для сохранения и извлечения данных о нужном городе, будут использоваться два метода: setCity и getCity. Объект SharedPreferences объявляется в конструкторе. Класс City.java должен выглядеть примерно так:

import android.app.Activity;
import android.content.SharedPreferences;

public class City {

 SharedPreferences prefs;

 public City(Activity activity){
 prefs = activity.getPreferences(Activity.MODE_PRIVATE);
 }

 // Если пользователь не выбрал город, на умолчанию 
 // будем показывать погоду на Сидней
 String getCity(){
 return prefs.getString("city", "Sydney, AU");
 }

 void setCity(String city){
 prefs.edit().putString("city", city).commit();
 }
}

Создаем еще один java класс по имени WeatherFragment.java. Как свой интерфейс, этот класс будет использовать файл fragment_weather.xml. В этом классе мы объявляем 5 объектов TextView и инициализируем их в методе onCreateView, также будет использоваться объект Thread для того, чтобы позволить приложению получать данные от OpenWeatherMap API асинхронно. Также нас нужен объект Handler. Добавляем в WeatherFragment.java следующий код:

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.logging.Handler;
import java.util.logging.LogRecord;

public class WeatherFragment extends Fragment {

 TextView cityField;
 TextView updatedField;
 TextView detailsField;
 TextView currentTemperatureField;
 TextView weatherIcon;

 Handler handler;

 public WeatherFragment(){
 handler=new Handler() {
 @Override
 public void close() {
 }
 @Override
 public void flush() {
 }
 @Override
 public void publish(LogRecord record) {
 }
 };
 }

 @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);

 return rootView;
 }
}

Также в методе onCreate мы вызываем метод updateWeatherData:

@Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 updateWeatherData(new CityPreference(getActivity()).getCity());
 }

Пока что все светит красным, но не отчаиваемся:). В методе updateWeatherData мы делаем запрос, вызываем getJSON. Добавляем описание этого метода:

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. Ответ от сервиса погоды получается как массив данных. Мы будем использовать только первый элемент этого массива. Создаем метод renderWeather:

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");
 }
}

Под конец указанного выше метода мы вызвали setWeatherIcon, указав ID текущей погоды. Это нужно настроить для того, чтобы пользователь видел при соответствующей погоде соответствующее ей изображение. Например:

     - значения в области 200 относятся к грозе, а значит мы будем использовать строку R.string.weather_thunder;

     - значения в области 300 относятся к морозу, здесь мы используем R.string.weather_drizzle;

     - значение а области 500 соответствует дождю, мы используем R.string.weather_rain;

     - и т.д. и т.п.

Добавляем выполнение метода setWeatherIcon:

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);
}

И под конец, добавим в этот класс еще один метод - метод, позволяющий пользователю менять  город:

public void changeCity(String city){
 updateWeatherData(city);
 }

Осталось немного поработать в файле MainActivity.java. Заменим в нем метод onCreate на этот код:

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 if (savedInstanceState == null) {
 getSupportFragmentManager().beginTransaction()
 .add(R.id.container, new WeatherFragment())
 .commit();
 }
}

Далее нужно отредактировать метод onOptionsItemSelected, который будет вызывать меню с опцией выбора нужного пользователю города. Для реализации функции ввода названия нужного города мы будем использовать Alert Dialog, в котором предложим пользователю ввести название необходимого города:

@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("Сменить город");
 final EditText input = new EditText(this);
 input.setInputType(InputType.TYPE_CLASS_TEXT);
 builder.setView(input);
 builder.setPositiveButton("Показать", 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);
}

На этом работа на приложением завершена. Можете переходить в тестированию своего приложения на эмуляторе либо устройстве. 

Надеюсь  урок был вам чем то полезен. Удачи!

Оригинал урока.

Категория: Уроки программирования | Просмотров: 2676 | Добавил: Oleg | Теги: Android приложение, погода, Программирование, weather | Рейтинг: 5.0/2
Всего комментариев: 3
avatar
1 Dr-Parlament • 12:14, 09.09.2015
Ну почему ничего не работает?!
avatar
2 Verteletsky • 16:06, 08.10.2015
Потому что пример на рабочий!
avatar
3 tonyfirehead • 23:38, 21.02.2016
Так и не починили?
avatar