Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пери перевод.docx
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
103.17 Кб
Скачать

Внимание

Один из наиболее важных моментов при написании эффективного кода

в Android — необходимость избегать повторного создания и разрушения

объектов. Любой объект, выполненный внутри метода onDraw, будет

создаваться и разрушаться при каждом обновлении экрана. Повышайте

эффективность своего кода, перенося как можно больше таких объектов

(в частности, экземпляры Paint и Drawable) в область видимости класса,

передавая их создание конструктору.

В листинге 4.11 показано, как переопределить метод onDraw, чтобы ото-

бражать в центре экрана простую текстовую строку.

150 бет

Листинг 4.11. Отрисовка нестандартного Представления

@Override

protected void onDraw(Canvas canvas) {

// Получите размер элемента, основываясь на последнем вызове

обработчика onMeasure.

int height = getMeasuredHeight();

int width = getMeasuredWidth();

// Найдите центр

int px = width/2;

int py = height/2;

// Создайте новые кисти для рисования.

// ЗАМЕТКА: В целях повышения эффективности, это

// должно быть сделано в конструкторе Представления

Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mTextPaint.setColor(Color.WHITE);

// Определите строку.

String displayText = "Hello World!";

// Измерьте ширину текстовой строки.

float textWidth = mTextPaint.measureText(displayText);

// Нарисуйте текстовую строку в центре элемента.

canvas.drawText(displayText, px-textWidth/2, py, mTextPaint);

}

Более подробная информация о методах рисования сложных графиче-

ских элементов содержится в главе 15.

ПРИМЕЧАНИЕ

На сегодняшний день Android не имеет поддержки векторной графики.

Как результат, изменения в любом элементе, размещенном на объ-

екте Canvas, приводят к перерисовке всего Холста, изменение цвета

кисти не повлияет на отображаемое Представление, пока оно не будет

помечено как недействительное и не перерисуется. В качестве альтер-

нативы для отрисовки графики вы можете использовать OpenGL. Для

получения более подробной информации ознакомьтесь с описанием

класса SurfaceView в главе 15.

Задание размеров для вашего элемента

Если ваш элемент не может идеально вписаться в площадь 100  100 пик-

селов, вам необходимо переопределить обработчик onMeasure.

Данный метод вызывается в момент, когда родительское Представле-

ние размещает внутри себя дочерние элементы. Оно как бы спрашивает:

≪Cколько места вы собираетесь использовать?≫ — и передает два параметра:

widthMeasureSpec и heightMeasureSpec. Они определяют доступное для эле-

мента пространство, а также некоторые метаданные, описывающие его.

151 бет

Вместо того чтобы возвращать результат, необходимо передать высоту

и ширину Представления в метод setMeasuredDimension.

В листинге 4.12 показано, как переопределить обработчик onMeasure.

Обратите внимание на вызовы локальных методов-заглушек calculateHeight

и calculateWidth. Они будут использоваться для декодирования значений

widthHeightSpec и heightMeasureSpec, а также для вычисления предпо-

чтительных высоты и ширины.

Листинг 4.12. Определение размеров Представления

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int measuredHeight = measureHeight(heightMeasureSpec);

int measuredWidth = measureWidth(widthMeasureSpec);

setMeasuredDimension(measuredHeight, measuredWidth);

}

private int measureHeight(int measureSpec) {

// Верните измеренную высоту виджета.

}

private int measureWidth(int measureSpec) {

// Верните измеренную ширину виджета.

}

Параметры widthMeasureSpec и heightMeasureSpec, описывающие гра-

ницы элемента, из соображений эффективности передаются в виде целочис-

ленных значений. Прежде чем их использовать, они должны быть декоди-

рованы с помощью статических методов getMode и getSize, принадлежащих

классу MeasureSpec:

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

В зависимости от режима mode, свойство size представляет собой либо

максимальное пространство, доступное для элемента управления (при режи-

ме AT_MOST), либо точные размеры области, которую займет Представление

(при режиме EXACTLY). Если режим был установлен в UNSPECIFIED, ваш

элемент не будет знать, какое именно значение описывает свойство size.

Задавая свойству mode значение EXACTLY, родительский элемент де-

кларирует, что Представление будет помещено именно в ту область, размер

которой был указан. В режиме AT_MOST родительский элемент спра-

шивает Представление, какой размер оно хочет занять, предлагая при этом

определенные границы, за которые оно не может выйти. Часто бывает, что

в обоих случаях возвращаемые значения идентичны.

Как бы то ни было, вы должны рассматривать эти ограничения как аб-

солютные. Иногда целесообразно возвращать размеры, выходящие за эти

152 бет

рамки. В таких случаях родительский элемент сам выбирает, как поступать

с Представлением, используя обрезание или прокрутку.

В листинге 4.13 показана типичная реализация управления размерами

Представления.

Листинг 4.13. Типичная реализация управления размерами Представления

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int measuredHeight = measureHeight(heightMeasureSpec);

int measuredWidth = measureWidth(widthMeasureSpec);

setMeasuredDimension(measuredHeight, measuredWidth);

}

private int measureHeight(int measureSpec) {

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

// Размер по умолчанию, если ограничения не были установлены.

int result = 500;

if (specMode == MeasureSpec.AT_MOST) {

// Рассчитайте идеальный размер вашего

// элемента в рамках максимальных значений.

// Если ваш элемент заполняет все доступное

// пространство, верните внешнюю границу.

result = specSize;

} else if (specMode == MeasureSpec.EXACTLY) {

// Если ваш элемент может поместиться внутри этих границ, верните это

значение.

result = specSize;

}

return result;

}

private int measureWidth(int measureSpec) {

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

// Размер по умолчанию, если ограничения не были установлены.

int result = 500;

if (specMode == MeasureSpec.AT_MOST) {

// Рассчитайте идеальный размер вашего

// элемента в рамках максимальных значений.

// Если ваш элемент заполняет все доступное

// пространство, верните внешнюю границу.

result = specSize;

} else if (specMode == MeasureSpec.EXACTLY) {

// Если ваш элемент может поместиться внутри этих границ, верните это

значение.

result = specSize;

}

return result;

}

153 бет

Обработка событий, основанных на взаимодействии

с пользователем

Чтобы новое Представление было интерактивным, оно должно реагировать

на нажатие клавиш и касание экрана. В Android есть несколько обработчиков

событий, которые позволяют реагировать на пользовательский ввод.

• onKeyDown. Вызывается при нажатии любой аппаратной клавиши,

включая манипулятор D-pad, клавиатуру, а также кнопки для теле-

фонного вызова, отмены звонка, возврата и управления камерой.

• onKeyUp. Вызывается, когда пользователь отпускает нажатую кла-

вишу.

• onTrackballEvent. Вызывается при перемещении трекбола.

• onTouchEvent. Вызывается при нажатии/отпускании сенсорного

экрана или же при обнаружении движения.

В листинге 4.14 показан каркас класса, в котором переопределяются все

обработчики событий, отвечающие за взаимодействие с пользователем.

Листинг 4.14. Обработка пользовательского ввода для Представления

@Override

public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {

// Верните значение true, если событие было обработано.

return true;

}

@Override

public boolean onKeyUp(int keyCode, KeyEvent keyEvent) {

// Верните значение true, если событие было обработано.

return true;

}

@Override

public boolean onTrackballEvent(MotionEvent event ) {

// Получите тип действия, которое представлено данным событием.

int actionPerformed = event.getAction();

// Верните значение true, если событие было обработано.

return true;

}

@Override

public boolean onTouchEvent(MotionEvent event) {

// Получите тип действия, которое представлено данным событием.

int actionPerformed = event.getAction();

// Верните значение true, если событие было обработано.

return true;

}

Подробности об использовании каждого из этих обработчиков, включая де-

тальную информацию о параметрах, принимаемых данными методами, а также

сведения о поддержке множественных касаний представлены в главе 15.

154

Создание приложения Compass View

В следующем примере1 вы создадите новое Представление CompassView,

наследуя класс View. Этот элемент будет выводить на экран традиционное

изображение компаса (в виде розы ветров), указывая курс (направление).

По завершении он должен выглядеть так, как показано на рис. 4.2.

Рис. 4.2.

Этот компас — пример графического Представления, которое требует

абсолютно иного подхода к отображению, нежели элементы TextView

и Button, доступные в SDK. Это делает его отличным кандидатом на соз-

дание с нуля.

ПРИМЕЧАНИЕ

В главе 14 вы будете использовать Представление CompassView со-

вместно с аппаратным акселерометром, чтобы отображать текущее на-

правление пользователя. В главе 15 изучите продвинутые методики для

рисования на объекте Canvas, с помощью которых можно кардинальным

образом улучшить внешний вид этого компаса.

1 Все фрагменты кода в этом примере — часть проекта Compass из главы 4, их можно за-

грузить с сайта Wrox.com.

155 бет

1. Создайте новый проект под названием Compass, а также Активность

для вывода на экран вашего нового Представления CompassView. Затем

выполните новый класс CompassView, наследующий View. Создайте

конструкторы, которые позволят получать экземпляры Представления

как внутри кода программы, так и с помощью ресурса с разметкой.

Добавьте новый метод initCompassView — он будет использоваться

для инициализации элемента — и вызовите его внутри каждого кон-

структора.

package com.paad.compass;

import android.content.Context;

import android.graphics.*;

import android.graphics.drawable.*;

import android.view.*;

import android.util.AttributeSet;

import android.content.res.Resources;

public class CompassView extends View {

public CompassView(Context context) {

super(context);

initCompassView();

}

public CompassView(Context context, AttributeSet attrs) {

super(context, attrs);

initCompassView();

}

public CompassView(Context context,

AttributeSet ats,

int defaultStyle) {

super(context, ats, defaultStyle);

initCompassView();

}

protected void initCompassView() {

setFocusable(true);

}

}

2. Элемент, отображающий компас, всегда должен представлять собой

идеальную окружность, занимающую все доступное пространство

Холста. Переопределите обработчик onMeasure, чтобы вычислить дли-

ну короткой грани, и вызовите метод setMeasuredDimension, чтобы

установить высоту и ширину, используя полученное значение.

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

// Компас представляет собой окружность, занимающую все доступное

// пространство.

// Установите размеры элемента, вычислив короткую грань (высоту

// или ширину).

int measuredWidth = measure(widthMeasureSpec);

156 бет

int measuredHeight = measure(heightMeasureSpec);

int d = Math.min(measuredWidth, measuredHeight);

setMeasuredDimension(d, d);

}

private int measure(int measureSpec) {

int result = 0;

// Декодируйте параметр measureSpec.

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.UNSPECIFIED) {

// Если границы не указаны, верните размер по умолчанию (200).

result = 200;

} else {

// Так как вам нужно заполнить все доступное пространство,

// всегда возвращайте максимальный доступный размер.

result = specSize;

}

return result;

}

3. Создайте два новых файла с ресурсами для хранения цветов и тексто-

вых строк, которые будут использоваться при рисовании компаса.

3.1. Создайте ресурс со строками res/values/strings.xml.

<?xml version="1.0" encoding="utf-8"?>

<resources>

<string name="app_name">Compass</string>

<string name="cardinal_north">N</string>

<string name="cardinal_east">E</string>

<string name="cardinal_south">S</string>

<string name="cardinal_west">W</string>

</resources>

3.2. Создайте ресурс со значениями цветов res/values/colors.xml.

<?xml version="1.0" encoding="utf-8"?>

<resources>

<color name="background_color">#F555</color>

<color name="marker_color">#AFFF</color>

<color name="text_color">#AFFF</color>

</resources>

4. Теперь вернитесь к классу CompassView. Добавьте новое пустое свой-

ство, чтобы хранить отображаемое направление, и создайте для него

геттер и сеттер.

private float bearing;

public void setBearing(float _bearing) {

bearing = _bearing;

157 бет

}

public float getBearing() {

return bearing;

}

5. Перейдите к методу initCompassView и получите ссылки на ресурсы,

созданные на шаге 3. Сохраните строковые значения в виде свойств,

используйте значения цветов для создания нового объекта Paint, при-

надлежащего классу. Эти объекты вы примените позже при рисовании

циферблата для компаса.

private Paint markerPaint;

private Paint textPaint;

private Paint circlePaint;

private String northString;

private String eastString;

private String southString;

private String westString;

private int textHeight;

protected void initCompassView() {

setFocusable(true);

circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

circlePaint.setColor(r.getColor(R.color.background_color));

circlePaint.setStrokeWidth(1);

circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);

Resources r = this.getResources();

northString = r.getString(R.string.cardinal_north);

eastString = r.getString(R.string.cardinal_east);

southString = r.getString(R.string.cardinal_south);

westString = r.getString(R.string.cardinal_west);

textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

textPaint.setColor(r.getColor(R.color.text_color));

textHeight = (int)textPaint.measureText("yY");

markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

markerPaint.setColor(r.getColor(R.color.marker_color));

}

6. В завершение необходимо нарисовать циферблат компаса, используя

объекты String и Paint, созданные в пункте 5. Следующий фрагмент

кода сопровождается всего лишь краткими комментариями. В главе 15

вы можете найти больше подробностей о рисовании на объекте Canvas

с использованием продвинутых эффектов.

6.1. В первую очередь начните с того, что переопределите метод onDraw

в классе CompassView.

@Override

protected void onDraw(Canvas canvas) {

158 бет

6.2. Найдите центральную точку элемента управления и сохраните

короткую грань в качестве радиуса компаса.

int px = getMeasuredWidth() / 2;

int py = getMeasuredHeight() /2 ;

int radius = Math.min(px, py);

6.3. С помощью метода drawCircle нарисуйте внешнюю границу и до-

бавьте фон для циферблата. Воспользуйтесь объектом circlePaint,

который создали в пункте 5.

// Нарисуйте фон

canvas.drawCircle(px, py, radius, circlePaint);

6.4. Компас отображает текущее направление таким образом, чтобы

оно всегда указывало на верхнюю часть устройства, поворачивая

при этом циферблат. Чтобы достичь такого эффекта, поворачи-

вайте Холст в направлении, противоположном текущему.

// Поворачивайте ракурс таким образом, чтобы

// "верх" всегда указывал на текущее направление.

canvas.save();

canvas.rotate(-bearing, px, py);

6.5. Все, что теперь осталось сделать, — нарисовать метки. Сделайте

полный поворот Холста, рисуя отметки каждые 15 и обозначая

направления каждые 45.

int textWidth = (int)textPaint.measureText("W");

int cardinalX = px-textWidth/2;

int cardinalY = py-radius+textHeight;

// Рисуйте отметки каждые 15 и текст каждые 45.

for (int i = 0; i < 24; i++) {

// Нарисуйте метку.

canvas.drawLine(px, py-radius, px, py-radius+10, markerPaint);

canvas.save();

canvas.translate(0, textHeight);

// Нарисуйте основные точки

if (i % 6 == 0) {

String dirString = "";

switch (i) {

case(0) : {

dirString = northString;

int arrowY = 2*textHeight;

canvas.drawLine(px, arrowY, px-5, 3*textHeight,

markerPaint);

canvas.drawLine(px, arrowY, px+5, 3*textHeight,

markerPaint);

break;

}

case(6) : dirString = eastString; break;

case(12) : dirString = southString; break;

case(18) : dirString = westString; break;

159 бет

}

canvas.drawText(dirString, cardinalX, cardinalY, textPaint);

}

else if (i % 3 == 0) {

// Отображайте текст каждые 45

String angle = String.valueOf(i*15);

float angleTextWidth = textPaint.measureText(angle);

int angleTextX = (int)(px-angleTextWidth/2);

int angleTextY = py-radius+textHeight;

canvas.drawText(angle, angleTextX, angleTextY, textPaint);

}

canvas.restore();

canvas.rotate(15, px, py);

}

canvas.restore();

}

7. Чтобы вывести компас на экран, отредактируйте ресурс с разметкой

main.xml, заменив TextView на новосозданный элемент Compass-

View. Этот процесс более подробно описан в следующем разделе.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent">

<com.paad.compass.CompassView

android:id="@+id/compassView"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

/>

</LinearLayout>

8. Запустив Активность, вы должны увидеть на экране CompassView. Что-

бы узнать, как связать CompassView с аппаратным компасом в устрой-

стве, перейдите к главе 14.

Использование нестандартных элементов управления

Создавая собственные Представления, вы можете использовать их внутри

кода программы и в разметке, как и любые другие элементы. В листинге 4.15

показано, как переопределить метод onCreate, чтобы добавить в Активность

элемент CompassView, созданный в предыдущем примере.

Листинг 4.15. Использование нестандартного Представления

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

CompassView cv = new CompassView(this);

setContentView(cv);

cv.setBearing(45);

}

160 бет

Чтобы использовать этот же элемент внутри ресурса, укажите полное

имя класса при создании нового узла в описании разметки, как показано

в следующем фрагменте XML-кода:

<com.paad.compass.CompassView

android:id="@+id/compassView"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

/>

Вы можете наполнить разметку и получить ссылку на CompassView как

обычно, используя следующий код:

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

CompassView cv = (CompassView)this.findViewById(R.id.compassView);

cv.setBearing(45);

}

Ресурсы Drawable

В главе 3 вы познакомились с системой ресурсов, узнали, как отделять

ресурсы от программы и подключать их версии для разных аппаратных

платформ.

В этом разделе вы познакомитесь с несколькими новыми типами ре-

сурсов для рисования, включая фигуры, преобразования и компоновку,

узнаете, каким образом их можно применять при создании пользовательских

интерфейсов, не зависящих от размера и разрешения экрана.

Все эти ресурсы могут быть описаны и использованы в коде программы,

но в этом разделе мы будем создавать их с помощью XML.

ПРИМЕЧАНИЕ

Фреймворк, о котором шла речь в главе 3, годится не только для опре-

деления альтернативных ресурсов, нацеленных на разные аппаратные

конфигурации, но может быть использован и при описании ресурсов

Drawable, рассматриваемых в этом разделе.

Фигуры, цвета и градиенты

Android включает простые ресурсы для рисования, которые можно

полностью описать в формате XML. Это касается классов ColorDrawable,

ShapeDrawable и GradientDrawable. Данные ресурсы хранятся в каталоге

res/drawable и могут быть идентифицированы в коде приложения по именам

файлов, записанным в нижнем регистре.

161 бет

Если описывать эти ресурсы в формате XML и указывать атрибуты

для них с помощью аппаратно-независимых пикселов (density-independent

pixels), система сможет их плавно масштабировать. Как и в случае с вектор-

ной графикой, эти ресурсы могут динамически масштабироваться, отобража-

ясь корректно и без артефактов при любых размерах и разрешениях экрана,

независимо от плотности пикселов. Исключение — ресурс GradientDrawable,

радиус для которого должен быть указан в пикселах.

По ходу чтения данной главы вы увидите, что эти элементы могут быть

использованы в сочетании с ресурсами для трансформации и компоновки.

Комбинируя их, вы сможете создавать динамические, легковесные и мас-

штабируемые элементы пользовательского интерфейса, выглядящие оди-

наково четко на любом экране.

ColorDrawable

ColorDrawable — простейший ресурс для рисования, он позволяет указы-

вать свойство изображения, основанное на единственном сплошном цвете.

ColorDrawable описывается в виде XML-файлов (хранящихся в каталоге

с ресурсами) с помощью тега <color>. В листинге 4.16 показан код для

ресурса, описывающего сплошной красный цвет.

Листинг 4.16. Ресурс, описывающий сплошной красный цвет

<color xmlns:android="http://schemas.android.com/apk/res/android"

android:color="#FF0000"

/>

ShapeDrawable

Данный вид ресурсов позволяет описывать простые геометрические

фигуры, указывая их размеры, фон и контур с помощью тега <shape>.

Каждый такой тег состоит из типа (указывается с помощью атрибута

android:shape), атрибутов, определяющих размер фигуры, и дочерних

узлов, в которых задаются значения для отступов, контура (или очертания)

и фона.

На сегодняшний день Android поддерживает несколько типов фигур,

задать которые можно в атрибуте android:shape.

• oval. Простой овал.

• rectangle. Поддерживает также вложенный тег <corners>, с помощью

которого можно создать прямоугольник с закругленными углами (ис-

пользуя атрибут radius).

• ring (кольцо). Поддерживает атрибуты innerRadius и thickness,

с помощью которых задаются внутренний радиус кольца и его толщи-

на соответственно. В качестве альтернативы вы можете использовать

162 бет

атрибуты innerRadiusRatio и/или thicknessRatio, чтобы ука-

зать те же параметры в виде значений, пропорциональных ширине

(где внутренний радиус, равный четверти ширины, будет использовать

значение 4).

Используйте вложенный тег <stroke>, чтобы с помощью атрибутов

width и color задать контур для своих фигур.

Вы также можете добавить узел <padding>, чтобы задать отступ при

позиционировании вашей фигуры на Холсте.

Что еще более полезно — можно включить в описание дочерний тег для

определения фонового цвета. В простейшем случае это использование узла

<solid> в сочетании с атрибутом color, который описывает сплошной

цвет заливки.

Следующий раздел посвящен классу GradientDrawable. Вы узнаете,

каким образом можно задать градиентную заливку для различных типов

ShapeDrawable.

В листинге 4.17 показан ресурс с фигурой (прямоугольником), которая

имеет сплошной фон, закругленные углы, отступ от каждой грани размером