Вступ
У цій статті, я покажу вам один з можливих практичних способів використання знань, які ви здобули в попередній статті про бібліотеку Volley. Ми будемо створювати додаток для відображення погоди на Марсі, використовуючии інформацію зібрану ровером Curiosity, яка тепер доступна кожному завдяки NASA через {MAAS} API.Спочатку, ми налаштує проект в Android Studio та спроектуємо користувацький інтерфейс. Далі ми будемо конструювати ядро додатку за допомогою Volley. Зважаючи на те, що кожен додаток, так чи інакше, оперує зображеннями, я покажу, як стягнути випадкове зображення використовуючи Flickr API. Ми будемо завантажувати зображення за допомогою Volley, в основному через її чудову систему кешування. І в кінці, ми додамо декілька прикрашальних функцій, щоб надати нашому додатку ефектності.
1. Налаштування проекту
Створимо новий проект в Android Studio. Бібліотека Volley зворотньо-сумісна, а отже ви можете вибрати будь-який рівень API, який вам заманеться. Я вибрав API 21, але ви можете обрати API починаючи з 8 (Froyo) чи вищих.Крок 1: Користувацький інтерфейс
Наш додаток має єдину актівіті. Ви можете назвати MainActivity.java, як рекомендує Android Studio. Відкрийте редактор шару розмітки activity_main.xml.Зважаючи на те що у нас зображення буде займати приблизно 70% екрану, а те, що залишилось буде займати інформація про погоду, ми маємо використати атрибут XML такий як
layout_weight
. Звичайно, ми можемо вказати абсолютні значення також, але але вони не будуть однаковими для всіх пристроїв. Нажаль, у світі Android не все так просто з розмірами. Атрибут layout_weight
допоможе нам вирішити проблему.
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
| android:layout_width = "match_parent" android:layout_height = "match_parent" android:orientation = "vertical" > < RelativeLayout android:layout_width = "match_parent" android:layout_height = "0dp" android:layout_weight = "0.68" android:background = "#FF5722" > <!-- image --> </ RelativeLayout > < RelativeLayout android:layout_weight = "0.33" android:layout_height = "0dp" android:layout_width = "match_parent" android:paddingTop = "@dimen/activity_horizontal_margin" android:background = "#212121" > <!-- TextViews --> </ RelativeLayout > </ LinearLayout > |
ImageView
:
1
2
3
4
5
| < ImageView android:id = "@+id/main_bg" android:layout_width = "match_parent" android:layout_height = "match_parent" android:scaleType = "centerCrop" /> |
RelativeLayout
, ми додамо список з TextView
елементів. Двоє з них будуть показувати середню температуру атмосфери. Третій для повідомлень про помилку.
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
| < TextView android:id = "@+id/error" android:layout_centerInParent = "true" android:visibility = "gone" android:layout_height = "wrap_content" android:layout_width = "match_parent" android:textSize = "20sp" android:textColor = "#FF5722" android:layout_margin = "@dimen/activity_horizontal_margin" android:gravity = "center" android:text = "I'm sorry.\nI wasn't able to retrieve real time data." /> < TextView android:id = "@+id/degrees" android:layout_height = "wrap_content" android:layout_width = "wrap_content" android:layout_centerHorizontal = "true" android:textSize = "90sp" android:textColor = "#FF5722" android:text = "-36°" /> < TextView android:id = "@+id/weather" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_centerHorizontal = "true" android:layout_below = "@id/degrees" android:textSize = "30sp" android:gravity = "center" android:textColor = "#FF5722" android:text = "Sunny" /> |
Крок 2: Тема та Дозволи
Є ще дві речі, про які ми мали б попіклуватися перш ніж поринути в ядро додатку. Змінимо тему додатку наandroid:Theme.Material.Light.NoActionBar
. Це означає, що ми не будемо приховувати action bar при запуску.
1
| < style name = "AppTheme" parent = "android:Theme.Material.Light.NoActionBar" /> |
1
| < uses-permission android:name = "android.permission.INTERNET" /> |
2. Ядро додатку
Крок 1: Імпортування бібліотеки Volley
Як ми вже обговорили в попередній статті, найпростіший спосіб та в той же час найнадійніший використовувати бібліотеку Volley це імпортувати бібліотеку як модуль. Завантажте вихідний код бібліотеки та імпортуйте його через File > New > Module, та в файі build.gradle додайте рядок.
1
| compile project( ":volley" ) |
Крок 2: Реалізація допоміжного класу
Як вже було вказано pаніше, якщо вам потрібно запускати безліч проектів, краще використовути загальну чергу запитів (shared request queue). Вам краще уникати від створення черги запитів кожен раз, коли робите запит за допомогоюVolley.newRequestQueue
, через те що нікому не потрібні витоки пам'яті (memory leaks) та інші не потрібні проблеми.Задля цього, створимо клас, який буде працювати за шаблоном проектування одинак. Клас працює з статичною, глобально видимою змінною, яка управляє об'єктом
RequestQueue
. Таким чином, ви покінчили з RequestQueue
. Далі, наслідуємось від класу Application
, ви маєте сказати операційній системі згенерувати об'єкт при старті програми, перед стартом першої актівіті.Трихи підправимо структуру нашого одинака (singleton). Потрібно створити новий екземпляр класу в методі
Application.onCreate
, а не getInstance
коли він рівний null
.Щоб досягти цього, створимо новий клас і назвемо його MarsWeather.java. Далі, додамо йому предка
Application
, перевизначимо метот
onCreate,
та ініціалізуємо об'єкт статичного екземпляру RequestQueue
.У цьому класі, ми конструюємо об'єкт, використовуючи модифікатори
public
та synchronized
функцію getInstance
. Всередині методу, ми повертаємо mInstance
змінну. Метод onCreate
виконується при старті додатку отже змінна mInstance
вже стоврена методом getInstance
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
| public class MarsWeather extends Application { private RequestQueue mRequestQueue; private static MarsWeather mInstance; @Override public void onCreate() { super .onCreate(); mInstance = this ; mRequestQueue = Volley.newRequestQueue(getApplicationContext()); } public static synchronized MarsWeather getInstance() { return mInstance; } } |
MarsWeather
був завантажений при старті дадатку. В тег <application>
додамо атрибун name
як показано нижче:
1
| android:name=".MarsWeather" |
Application
створений, ще до того як клас MainActivity
створений. Разом зі стандартними операціями, метод onCreate
згенерує екземпляр RequestQueue
.Ми маємо реалізувати інші методи задля завершення допоміжного класу. Перший метод замінює
Volley.newRequestQueue
, який ми назвемо getRequestQueue
. Нам також потрібен метод для додавання запитів в чергу, add
, та метод чиєю відповідальністю буде відміна запитів, cancel
. Наступний блок коду показує, як виглядає реалізація..
01
02
03
04
05
06
07
08
09
10
11
12
| public RequestQueue getRequestQueue() { return mRequestQueue; } public <T> void add(Request<T> req) { req.setTag(TAG); getRequestQueue().add(req); } public void cancel() { mRequestQueue.cancelAll(TAG); } |
TAG
це ключ, ідентифікатор об'єкту. В цьому конкретному випадку, він може бути чим завгодно:
1
| public static final String TAG = MarsWeather. class .getName(); |
Крок 3: Реалізація користувацького запиту
Як ви вже знаєте, Volley забезпечує три стандартні типи запитів:StringRequest
, ImageRequest
, та JsonRequest
. Наш додаток буде використовувати останній для збору даних погоди та передавати список випадкових зображень. За замовчуванням, Volley ставить пріорітет
NORMAL
для запитів. Зазвичай це нормально, але в нашому додатку ми маємо два запити, що трохи різні і таким чином ми змушені мати різні пріорітети в черзі. Отримання даних погоди має бути з вищим пріоритетом аніж отримання URL випадкового зображення.Ми мусимо кастомузувати клас
JsonRequest
. Створити новий клас з іменем CustomJsonRequest.java, та впевнитися, що він наслідує JsonObjectRequest
. Далі перевизначимо метод getPriority
, як показано нижче.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
| public class CustomJsonRequest extends JsonObjectRequest { public CustomJsonRequest( int method, String url, JSONObject jsonRequest, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) { super (method, url, jsonRequest, listener, errorListener); } private Priority mPriority; public void setPriority(Priority priority) { mPriority = priority; } @Override public Priority getPriority() { return mPriority == null ? Priority.NORMAL : mPriority; } } |
Крок 4: Отримання інформації
Нарешті ми дісталися до найцікавішої частини цієї статті, де ми будемо писати реалізацію завантаження даних погоди. Кінцева точка запиту в нас:Ви можете пройти за посиланням та переглянути, як буде виглядати відповідь на запит API у вигляді JSON. JSON містить простий об'єкт,
result
, що включає набір текстових констант, починаючи з температури та закінчуючи силою вітру та сходом сонця.Почнемо з визначення змінних в класі
MainActivity
:
1
2
3
| TextView mTxtDegrees, mTxtWeather, mTxtError; MarsWeather helper = MarsWeather.getInstance(); |
MarsWeather.getInstance
по за методом onCreate
. Так як клас вже ініціалізований , ви не вимушені чекати на запуск методу onStart
. Звичайно, ви маєте встановити посилання на елементи views в методі onCreate
.
1
2
3
| mTxtDegrees = (TextView) findViewById(R.id.degrees); mTxtWeather = (TextView) findViewById(R.id.weather); mTxtError = (TextView) findViewById(R.id.error); |
loadWeatherData
. Ми створюємо наш запит Volley та змінюємо пріоритет на HIGH
. Далі запускаємо метод add
з допоміжного класу щоб додати до черги запитів. Зверніть увагу на слухача результатів (result listener), тепер він впливає на інтерфейс.
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
| private void loadWeatherData() { CustomJsonRequest request = new CustomJsonRequest (Request.Method.GET, RECENT_API_ENDPOINT, null , new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { try { String minTemp, maxTemp, atmo; int avgTemp; response = response.getJSONObject( "report" ); minTemp = response.getString( "min_temp" ); minTemp = minTemp.substring( 0 , minTemp.indexOf( "." )); maxTemp = response.getString( "max_temp" ); maxTemp = maxTemp.substring( 0 , maxTemp.indexOf( "." )); avgTemp = (Integer.parseInt(minTemp)+Integer.parseInt(maxTemp))/ 2 ; atmo = response.getString( "atmo_opacity" ); mTxtDegrees.setText(avgTemp+ "°" ); mTxtWeather.setText(atmo); } catch (Exception e) { txtError(e); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { txtError(error); } }); request.setPriority(Request.Priority.HIGH); helper.add(request); } |
1
2
3
4
| private void txtError(Exception e) { mTxtError.setVisibility(View.VISIBLE); e.printStackTrace(); } |
loadWeatherData
в onCreate
і готово. Додаток готовий показувати погоду на Марсі.3. Отримання зображень
Тепер ми маємо ядро нашого додатку готовим і робочим, ми можемо сфокусуватися на зовнішньому вигляді. Ми збираємось зробити це додавши випадково обране зображення Марсу.Крок 1: Отримати випадкове зображення
Нам буде потрібен Flickr API ключ щоб отримати зображення. Кінцева точка нашого зображення:
1
2
3
| https://api.flickr.com/services/rest/?format=json&nojsoncallback=1& sort=random&method=flickr.photos.search&tags=mars,planet,rover&tag_mode=all& api_key=[YOUR_KEY] |
format=json
), але ми не конкретизували JSON callback (nojsoncallback=1
). Ви шукаєте зображення (method=flickr.photos.search
) та теги в яких ви зацікавлені та якось відносяться до Марусу (tags=mars,planet,rover
). Погляньте на документацію для отримання детальнішої інформації про запити URL.Почнемо визначати необхідні змінні:
1
2
3
4
| final static String FLICKR_API_KEY = "[INSERT HERE YOUR API KEY]" , IMAGES_API_ENDPOINT = "https://api.flickr.com/services/rest/?format=json&nojsoncallback=1&sort=random&method=flickr.photos.search&" + "tags=mars,planet,rover&tag_mode=all&api_key=" ; |
searchRandomImage
:
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
| private void searchRandomImage() throws Exception { if (FLICKR_API_KEY.equals( "" )) throw new Exception( "You didn't provide a working Flickr API!" ); CustomJsonRequest request = new CustomJsonRequest (Request.Method.GET, IMAGES_API_ENDPOINT+ FLICKR_API_KEY, null , new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { try { JSONArray images = response.getJSONObject( "photos" ).getJSONArray( "photo" ); int index = new Random().nextInt(images.length()); JSONObject imageItem = images.getJSONObject(index); ".static.flickr.com/" + imageItem.getString( "server" ) + "/" + imageItem.getString( "id" ) + "_" + imageItem.getString( "secret" ) + "_" + "c.jpg" ; // TODO: do something with *imageUrl* } catch (Exception e) { imageError(e); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { imageError(error); } }); request.setPriority(Request.Priority.LOW); helper.add(request); } |
JSONArray
наповнений зображеннями. Метод. який я написав, для збору випадкового зображення генерує число від нуля до розміру масиву. Він відбирає зображення відносно індексу результату та будує URL для зображення згідно з цим посібником.Як і раніше, ми повинні реалізувати метод для управління помилками:
1
2
3
4
5
6
| int mainColor = Color.parseColor( "#FF5722" ); private void imageError(Exception e) { mImageView.setBackgroundColor(mainColor); e.printStackTrace(); } |
searchRandomImage
в onCreate
не забудьте попіклуватися про виключні ситуації.Крок 2: Показати зображення
Тепер, оскільки ми маємо URL зображення, ми можемо показати його користувачеві. Як це зробити ви вже знаєте з попередньої статті.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
| private void loadImg(String imageUrl) { // Retrieves an image specified by the URL, and displays it in the UI ImageRequest request = new ImageRequest(imageUrl, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap bitmap) { mImageView.setImageBitmap(bitmap); } }, 0 , 0 , ImageView.ScaleType.CENTER_CROP, Bitmap.Config.ARGB_8888, new Response.ErrorListener() { public void onErrorResponse(VolleyError error) { imageError(error); } }); // we don't need to set the priority here; // ImageRequest already comes in with // priority set to LOW, that is exactly what we need. helper.add(request); } |
onResponse
що ми написали в попередньому кроці, ми можемо отримати результат.
1
| loadImg(imageUrl); |
Крок 3: Показувати нове зображення кожного дня
Можливо ви вже помітили, що примушуємо Volley отримувати випадкове зображення при кожному запуску додатку. Тепер зробимо щоб воно змінювалось кожен день, а на протязі дня залишалось незмінним.Найпростіший спосіб досягнути цього це скористатися
SharedPreferences
. Почнемо з визначення змінних, які нам знадобляться.
1
2
3
4
5
6
| SharedPreferences mSharedPref; int today = Calendar.getInstance().get(Calendar.DAY_OF_MONTH); final static String SHARED_PREFS_IMG_KEY = "img" , SHARED_PREFS_DAY_KEY = "day" ; |
onCreate
методі, перед викликом searchRandomImage
, ініціалізуємо mSharedPref
.
1
| mSharedPref = getPreferences(Context.MODE_PRIVATE); |
SharedPreferences
з сьогоднішній день. Якщо день співпадає ми використовуємо URL, який міститься у нас. В іншому випадку ми завантажуємо нове зображення та зберігаємо URL в SharedPreferences
.В
searchRandomImage
, після визначення imageUrl
, додайте наступний код:
1
2
3
4
5
6
7
8
9
| // right after *String imageUrl = .... * // store the pict of the day SharedPreferences.Editor editor = mSharedPref.edit(); editor.putInt(SHARED_PREFS_DAY_KEY, today); editor.putString(SHARED_PREFS_IMG_KEY, imageUrl); editor.commit(); // and then there's *loadImage(imageUrl);* |
onCreate
, після додання змінної mSharedPref
, тепер виглядає так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
| if (mSharedPref.getInt(SHARED_PREFS_DAY_KEY, 0 ) != today) { // search and load a random mars pict try { searchRandomImage(); } catch (Exception e) { // please remember to set your own Flickr API! // otherwise I won't be able to show // a random Mars picture imageError(e); } } else { // we already have a pict of the day: let's load it loadImg(mSharedPref.getString(SHARED_PREFS_IMG_KEY, "" )); } loadWeatherData(); |
Бонус: Вдосконалення користувацького інтерфейсу
Крок 1: Шрифт
Шрифт, що використовується в додатку, задає тон додатку. Почнемо зі зміни стандартного Roboto на більш стильний Lato light.Створіть нову директорію з назвою fonts в assets. Якщо у вас немає директорії assets, створіть її на тому ж рівні що й java. Структура директорій має виглядати так app\src\main\assets\fonts.
Зкопіюйте Lato-light.ttf в fonts. В методі
onCreate,
ви перевизначаєте шрифт за замовчуванням в тих views в яких ви хочете використовувати шрифт.
1
2
| mTxtDegrees.setTypeface(Typeface.createFromAsset(getAssets(), "fonts/Lato-light.ttf" )); mTxtWeather.setTypeface(Typeface.createFromAsset(getAssets(), "fonts/Lato-light.ttf" )); |
Крок 2: Прозорий Status Bar
Притримуючись до гайдлайнів Android Material Design, ми можемо зробити наш status bar прозорим. Таким чином, фон буде частково видимим через status bar.Ви досягаєте цього додавши трохи змін в тему. Змініть файл стилю v21\style.xml так:
1
2
3
4
5
| < resources > < style name = "AppTheme" parent = "android:Theme.Material.Light.NoActionBar" > < item name = "android:windowTranslucentStatus" >true</ item > </ style > </ resources > |
1
2
3
4
5
6
| < application android:name = ".MarsWeather" android:allowBackup = "true" android:icon = "@mipmap/ic_launcher" android:label = "@string/app_name" android:theme = "@style/AppTheme" > |
Комментариев нет:
Отправить комментарий