МАЛЮВАННЯ, двомірне і тривимірне ГРАФІКИ – програмування Android

&nbsp

Колекція віджетів Android і інструментів для складання їх у більш великі елементи досить зручна і функціональна, допомагає вирішувати найрізноманітніші завдання Але що робити, якщо жоден із пропонованих віджетів не підходить для конкретного випадку Може бути, у додатку потрібно зобразити гральні карти, фази Місяця або енергію, який спрямовується на основні двигуни ракетного корабля На такі випадки потрібно вміти створювати власні віджети самостійно

Ми зробимо огляд графіки та анімації в Android Вона розрахована на програмістів, що володіють базовим досвідом роботи з графікою Тут досить докладно обговорюються питання, повязані з поворотами дисплея Вам безумовно доведеться звертатися до документації по Android, собенно тому, що з появою Honeycomb інтерфейси стали змінюватися і цей процес продовжується до цих пір Результати застосування нових технологій обовязково вразять ваших користувачів

СТВОРЕННЯ власні віджети

Вище ми вже згадували, що віджет – це просто зручний термін для позначення підкласів androidview View, як правило, представляють собою вузли-листя »на дереві видів Але внутрішні вузли дерева видів, хоча і можуть включати в себе складний код, зазвичай достатньо прості в обігу, з точки зору користувача Нехай термін «віджет» і є неофіційним, він корисний при обговоренні основних робочих компонентів користувача інтерфейсу, що містять інформацію і реалізують поведінку, які важливі для користувача

Можна досягти багато чого і не створюючи нових віджетів У цій книзі ми вже зробили кілька додатків, що складаються лише з готових віджетів, а також простих підкласів даних віджетів Код цих додатків просто вибудовує дерева видів, компонуючи їх в коді або завантажуючи з макетів, що зберігаються в XML-файлах ресурсів

Непросте додаток Microjobs, яке ми розглянемо, має вигляд, який містить список назв, які відповідають пунктам на карті У міру того як на карту додаються нові пункти, нові віджети з назвами пунктів динамічно додаються до списку Навіть у цьому динамічно змінюваному макеті використовуються тільки готові віджети нові віджети тут не створюються Технології Microjobs, образно кажучи, додають або видаляють рамки з такого дерева, як показано на рис 73

Ми покажемо, як створити власний віджет Для цього доведеться заглянути «під капот» класу View TextView, Button і DatePicker – приклади віджетів, наявних в інструментарії для створення користувацьких інтерфейсів Android Ви можете реалізувати власний віджет як підклас від одного з перерахованих вище віджетів або як прямий підклас від View

Більш складний віджет (такий, в який можуть вкладатися нові віджети) повинен бути підкласом ViewGroup, який, у свою чергу, є підкласом View Дуже складний віджет, який використовується, наприклад, як інструмент інтерфейсу і реалізовується у декількох місцях (і навіть декількома додатками), може являти собою цілий пакет класів, лише один з яких є нащадком View

Присвячена графіку, а значить, компоненту View (Вид) з паттерна «Модель-вид-контролер» (MVC) Віджети також містять код контролера, і таке рішення є хорошим, оскільки в такому випадку весь код, важливий для поведінки і його представлення на екрані, розташовується в одному місці

Тому, зосередившись на графічної інформації, ми зможемо розбити завдання, на дві важливі частини: знаходження простору на екрані і малювання в даному просторі Перше завдання повязана з компонуванням, або побудовою макета (layout) «Листовий» віджет може повідомляти про те, що йому потрібно простір, визначаючи метод onMeasure, який буде викликатися фреймворком користувача інтерфейсу Android в потрібний час Друге завдання – відображення віджета як таке – обробляється методом onDraw, що належать до віджету

Макет

Велика частина складної роботи в механізмі побудови макетів під фреймворці Android реалізується за допомогою контейнерних видів Контейнерний вид, як зрозуміло з назви, містить інші види Такі види є внутрішніми вузлами в дереві видів і в підкласах ViewGroup Інструментарій фреймворка надає складні контейнерні види, що пропонують потужні і адаптуються стратегії розміщення інформації на екрані Наприклад, часто застосовуються контейнерні види LinearLayout і RelativeLayout Користуватися ними обома відносно просто, а от правильно реалізувати самостійно – дуже складно Оскільки зручні та потужні контейнерні види і так існують, вам, можливо, ніколи не доведеться самостійно реалізовувати такий вид або його алгоритм компоновки, про який тут піде мова Але розуміння того, як все це працює (як фреймворк для користувача інтерфейсу Android управляє процесом компонування), допоможе вам писати правильні і надійні віджети

У прикладі 91 показаний, мабуть, найпростіший робочий віджет, який може написати кожен При додаванні такого віджета в дерево видів небудь активності (Acti vity) він буде заповнювати виділене йому простір блакитним кольором Не дуже цікаво, але перш, ніж перейти до створення чогось складнішого, уважно розглянемо, як в цьому простому прикладі вирішуються дві основні задачі компонування і отрисовки Почнемо з процесу компоновки малювання буде описано нижче, у підрозділі «Малювання із застосуванням Canvas (полотна)»

Приклад 91 Найпростіший віджет

Динамічний макет потрібний тому, що потреба віджета у вільному просторі також змінюється динамічно Припустимо, наприклад, що віджет в програмі з підтримкою GPS-навігації відображає назву міста, до якого ви зараз їдете на машині Коли ви прямуєте з Елі в Пост Міллз, віджет отримує повідомлення про зміну місця розташування Але коли програма збирається переписати назву міста, то виявляє, що на екрані мало місця і назва нового міста там не вміщується Програма повинна послати екрану запит на перемальовування, так, щоб для назви міста звільнилося більше місця, якщо це можливо

Компонування може бути дивно складним завданням, яку вельми нелегко виконати правильно Ймовірно, не так складно домогтися, щоб конкретний «листовий» віджет правильно виглядав на окремо взятому пристрої З іншого боку, дуже складно буває зробити такий віджет, який повинен буде впорядковувати свої дочірні елементи так, щоб вони правильно шикувалися на різноманітних пристроях, навіть коли параметри екрану змінюються

Процес компонування починається, коли метод requestLayout активується в якому-небудь віджеті з дерева видів Як правило, віджет викликає метод requestLayout стосовно до самого себе, коли йому потрібно додатковий простір Правда, цей метод може викликатися з будь-якої точки програми, щоб вказати, що певний вид на відображуваному в даний момент екрані вже відчуває нестачу простору

Метод requestLayout змушує фреймворк для користувача інтерфейсу Android поставити подія в чергу подій для користувача інтерфейсу Коли кожна з цих подій буде оброблено в порядку черги, фреймворк надасть всім контейнерним видам можливість запросити у кожного з його дочірніх віджетів, скільки місця потрібно цьому віджету для отрисовки Цей процес ділиться на два етапи: вимірювання дочірніх видів і подальше впорядкування їх в нових позиціях Першу фазу повинні реалізовувати всі види, друга ж важлива тільки для реалізацій контейнерних видів, яким буде потрібно управляти компонуванням їх дочірніх видів

Вимірювання

Мета фази вимірювань – надати кожному виду можливість динамічно запросити стільки простору, скільки йому в ідеалі потрібно для отрисовки Фреймворк користувача інтерфейсу починає цей процес, викликаючи метод measure у кореневого віджета дерева видів Починаючи з цієї точки, кожен контейнерний вид запитує свої дочірні види про те, скільки простору їм потрібно Виклик передається всім видам-нащадкам, починаючи з найглибших, і, таким чином, кожен дочірній елемент отримує можливість розрахувати свій розмір до того, як це зробить батьківський елемент Батьківський елемент розраховує власний розмір в залежності від розмірів вхідних у нього дочірніх елементів і повідомляє отримані дані свого батьківського елементу І так далі по всьому дереву

Наприклад, самий верхній LinearLayout опитує кожен з вкладених віджетів LinearLayout про те, які розміри йому слід мати Ці віджети, в свою чергу, опитують містяться в них кнопки (Button) або віджети EditText щодо їхніх розмірів Кожен дочірній елемент повідомляє про оптимальний для себе розмірі батьківського елементу Потім батьківські елементи складають дані, отримані від дочірніх елементів, плюс все відступи, які передає їм батьківський елемент, і повідомляють суму самого верхнього LinearLayout

Оскільки фреймворк повинен гарантувати в ході цього процесу певні поведінки для всіх видів View, метод measure є фінальним і не може бути перевизначений Замість перевизначення measure викликає метод onMeasure, який віджети можуть перевизначати, щоб отримувати для себе необхідний простір

Аргументи методу onMeasure описують простір, який готовий виділити батьківський елемент: вказують висоту і ширину Ці величини виражаються в точках Фреймворк припускає, що жоден вигляд не може бути за розміром менше 0 і більше 230 пікселів, тому використовує старші двійкові розряди переданого параметра Іnt для кодування режиму специфікації вимірів (measurement specification mode) Ситуація така, як якби onMeasure викликався з чотирма аргументами: режимом специфікації ширини, шириною, режимом специфікації висоти і висотою Не намагайтеся самі виконувати зсув бітів для розділення пар аргументів Замість цього слід використовувати статичні методи MeasureSpec getMode і MeasureSpec getSize

Режими спеціфікацці описують, як контейнерний вид вимагає від дочірнього виду інтерпретувати розміри, передані йому Таких режимів три:

MeasureSpec EXACTLY – контейнерний вид, що виконує виклик, вже визначив точний розмір дочірнього виду

MeasureSpecAT_MOST – контейнерний вид, що виконує виклик, задав максимальне значення для цього параметра, але дочірній вид може запросити і менше місця

MeasureSpecUNSPECIFIED – контейнерний вид, що виконує виклик, не поставив дочірньому увазі ніяких обмежень дочірній вид може запросити будь необхідний розмір

Віджет завжди повинен сам повідомити свого батьківського елементу в дереві видів, скільки йому (віджету) потрібна місця Це робиться шляхом виклику методу setMeasuredDi mensi ons, що задає значення висоти і ширини Батьківський елемент може пізніше отримати дані про ці властивості за допомогою методів getMeasuredHei ght і getMeasuredWidth Якщо ваша реалізація перевизначає onMeasure, але не викликає setMeasuredDimensions, то метод measure не завершена нормально, а видасть виняток LegalStateException

Стандартна реалізація onMeasure, що успадковується від View, викликає setMeasuredDimensіons з одним з двох значень для кожного напрямку Якщо батьківський вузол задає режим MeasureSpec UNSPECIFIED, то метод setMeasuredDimensions, що відноситься до дочірнього елементу, використовує стандартний розмір вигляду У такій якості застосовується значення, одержуване або від getSuggestedMinimumWidth, або від getSuggestedMinimumHeight Якщо батьківський елемент використовує один з двох інших режимів специфікації, то стандартна реалізація застосовує розмір, запропонований батьківським елементом Це дуже доцільна стратегія, і вона дозволяє типовою реалізації віджета повністю опрацьовувати весь етап вимірювань, просто задаючи значення, повернуті методами getSuggestedMinimumWidth і getSuggestedMinimumHeight Саме такий мінімалістський прийом ми використовували в прикладі 91

Насправді віджет може і не отримати тієї кількості простору, який запитує Розглянемо вид, ширина якого дорівнює 100 пикселам і який має три дочірні елемента Мабуть, зовсім очевидно, як батьківський елемент повинен розташувати свої дочірні елементи, якщо сумарна ширина в пікселах, запитана всіма дочірніми елементами, становить 100 або менше Але якщо, наприклад, кожен з дочірніх елементів запросить по 50 пікселів в ширину, батьківський вид не зможе задовольнити всі три запиту

Контейнерний вигляд повністю контролює розташування своїх дочірніх елементів У вищеописаних обставин він може «вчинити справделіво» і виділити кожному дочірньому елементу по 33% доступної ширини З тим же успіхом він може віддати 50 пікселів самому лівому віджету і по 50 – двом іншим І нарешті він може відвести всі 100 пікселів єдиному віджету, а двом іншим нічого не залишити Але незалежно від методу саме батьківський елемент визначає розмір і положення обмежує рамки для кожного дочірнього елемента

Інший варіант того, як контейнерний вид управляє простором, що відводиться кожному віджету, зрозумілий з прикладу 91 Віджет, показаний у цьому прикладі, завжди запрошувати стільки простору, скільки йому потрібно, незалежно від того, скільки місця йому пропонується (на відміну від стандартної реалізації) Такою стратегією зручно користуватися при роботі з віджетами, які будуть додаватися до контейнерів зі стандартного інструментарію, зокрема до контейнера LinearLayout Такі контейнери реалізують властивість тяжіння (gravity) Гравітація – це властивість, що використовується в деяких видах для вказівки вирівнювання своїх піделементи Коли ви почнете працювати з одним з цих контейнерів, вас, можливо, здивує одна обставина Виявляється, що за замовчуванням отрісовивается тільки перший із створених вами віджетів Щоб виправити це, можна або змінити властивість тяжіння на GravityFILL за допомогою методу setGravity, або змусити віджети «наполягати» на отриманні саме такої кількості простору, який їм потрібно

Необхідно також відзначити, що контейнерний вид може кілька разів викликати метод measure дочірнього елемента за один етап вимірювань При реалізації onMeasure правильно зроблений контейнерний вид, який намагається побудувати горизонтальний ряд віджетів, може, наприклад, викликати метод measure кожного дочірнього віджета в режимі MEASURE_SPEC UNSPECIFIED і з шириною 0, щоб визначити, який розмір «віддасть перевагу» той чи інший віджет Зібравши, таким чином, переважні значення ширини для всіх своїх дочірніх елементів, він може порівняти суму цих значень з шириною, доступної в даній ситуації (ця ширина вказувалася у виклику, надісланому від батьківського елемента до методу measure) Тепер батьківський елемент знову може викликати метод measure кожного дочірнього віджета, але на цей раз – уже в режимі MeasureSpec AT_MOST із зазначенням ширини, яка пропорційно найкраще підходить кожному віджету з урахуванням наявного простору Оскільки measure можна викликати кілька разів, реалізація onMeasure повинна бути Ідемпотентний і не міняти стану програми

Зрозуміло, що реалізація методу onMeasure з контейнерним видом зазвичай досить складна ViewGroup, суперклас всіх контейнерних видів, не дає стандартної реалізації цього методу У кожному контейнерному вигляді фреймворка користувача інтерфейсів Android така реалізація – власна Якщо ви роздумуєте реалізувати контейнерний вид, можна вибрати один з таких методів в якості стандартного і орієнтуватися на нього Якщо ж, навпаки, вам доведеться виконувати вимірювання на ходу, то, швидше за все, вам потрібно буде викликати метод measure для кожного дочірнього елемента, а також випробувати на практиці допоміжні методи ViewGroup: measureChild, measureChildren і measureChildWithMargins При завершенні етапу вимірювань контейнерний вигляд, як і будь-який інший віджет, повинен повідомити про те, скільки місця йому потрібно Це робиться шляхом виклику setMeasuredDimensions

Впорядкування

Після того як всі контейнерні види в дереві видів зможуть «домовитися» про розміри своїх дочірніх елементів, фреймворк приступає до другого етапу компонування, який полягає в упорядкуванні дочірніх елементів Знову ж якщо ви не реалізуєте власний контейнерний вид, то вам, ймовірно, ніколи не доведеться працювати і з власним кодом для впорядкування елементів У цьому пункті описується базовий процес упорядкування, щоб ви могли краще зрозуміти, як він, можливо, вплине на ваші віджети Стандартний метод, реалізований у класі View, буде працювати з типовими «листовими» віджетами, як це показано в прикладі 91

Оскільки відноситься до виду метод onMeasure можна викликати кілька разів, фреймворк повинен використовувати інший метод, щоб сигналізувати, що даний етап вимірювань завершений і що контейнерні види повинні остаточно зафіксувати положення своїх дочірніх елементів Як і етап вимірювань, етап впорядкування реалізується за допомогою двох методів Фреймворк активує фінальний метод 1 ayout у верхній точці дерева видів Метод 1 ayout обробляє всі види за загальним принципом, а потім викликає метод onLayout Користувальницькі віджети перевизначають метод onLayout, щоб реалізовувати власні поведінки Призначена для користувача реалізація onLayout повинна як мінімум розрахувати розміри робочого прямокутника, який надаватиметься для кожного дочірнього елемента при відображенні, і, в свою чергу, активувати метод layout для кожного дочірнього елемента (адже цей елемент сам по собі може бути батьківським для інших віджетів) Цей процес може бути досить складний Якщо вашому віджету потрібно впорядковувати дочірні види, спробуйте взяти в якості основи для нього вже наявний контейнер, наприклад LinearLayout або RelativeLayout

Ще раз відзначимо, що віджет не обовязково отримує стільки простору, скільки запрошувати Він повинен бути здатний отрісовани незалежно від того, скільки саме простору йому відводиться Якщо він намагається отрісовиваться поза того простору, який йому виділив батьківський елемент, то малюнок буде обрізатися прямокутником відсікання (clip rectangle) Щоб забезпечити філігранний контроль (то Тобто, наприклад, щоб віджет заповнював рівно стільки простору, скільки йому виділено), потрібно або реалізувати onLayout і записати параметри відведеного простору, або стежити за прямокутником відсікання Canvas, аргументом onDraw

Джерело: Android Програмування на Java для нового покоління мобільних пристроїв

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*