Главная » Разработка для Android » Пример усовершенствованного циферблата для компаса

0

В главе 4 вы создали простой пользовательский интерфейс для компаса. В главе 14 вы вернулись  к нему, добавив отображение  параметров  pitch и roll, используя аппаратные  возможности акселерометра.

Для этих примеров  был создан довольно примитивный пользователь- ский интерфейс,  чтобы не засорять код лишними  деталями.

В следующем примере1 вы внесете некоторые существенные изменения в метод onDraw из Представления CompassView, превращая простой, плоский компас в динамический автогоризонт, показанный на рис. 15.2.

Рис. 15.2.

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

1. Начните  с редактирования файла  colors.xml, добавляя значения цветов для градиента  окантовки,  затенения  стекла  компаса, неба

1     Все фрагменты  кода в этом примере — часть проекта по автогоризонту из данной главы, их можно загрузить с сайта Wrox.com.

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

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

<resources>

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

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

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

<color name="shadow_color">#7AAA</color>

<color name="outer_border">#FF444444</color>

<color name="inner_border_one">#FF323232</color>

<color name="inner_border_two">#FF414141</color>

<color name="inner_border">#FFFFFFFF</color>

<color name="horizon_sky_from">#FFA52A2A</color>

<color name="horizon_sky_to">#FFFFC125</color>

<color name="horizon_ground_from">#FF5F9EA0</color>

<color name="horizon_ground_to">#FF00008B</color>

</resources>

2. Объекты Paint и Shader, использованные для отображения неба и зем- ли в автогоризонте, создаются на основе размеров текущего Представ- ления, поэтому они нестатические (в отличие от объектов Paint, кото- рые вы создали в главе 4). Вместо работы с объектом Paint определите массив градиентов, а также несколько цветов для них.

int[] borderGradientColors;

float[] borderGradientPositions;

int[] glassGradientColors;

float[] glassGradientPositions;

int skyHorizonColorFrom;

int skyHorizonColorTo;

int groundHorizonColorFrom;

int groundHorizonColorTo;

3. Примените метод initCompassView из CompassView  для инициали- зации полей, созданных вами в предыдущем пункте. Воспользуйтесь для этого ресурсами из пункта 1. Уже написанный код можно оставить почти нетронутым,  внеся лишь некоторые  изменения в переменные textPaint, circlePaint и markerPaint, как отмечено в следующем фраг- менте.

protected void initCompassView() {

setFocusable(true);

// Получите внешние ресурсы

Resources r = this.getResources();

circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); circlePaint.setColor(R.color.background_color); circlePaint.setStrokeWidth(1); circlePaint.setStyle(Paint.Style.STROKE);

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)); textPaint.setFakeBoldText(true); textPaint.setSubpixelText(true); textPaint.setTextAlign(Align.LEFT);

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

markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); markerPaint.setColor(r.getColor(R.color.marker_color)); markerPaint.setAlpha(200); markerPaint.setStrokeWidth(1); markerPaint.setStyle(Paint.Style.STROKE);

markerPaint.setShadowLayer(2, 1, 1, r.getColor(R.color.shadow_color));

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

borderGradientColors = new int[4];

borderGradientPositions = new float[4];

borderGradientColors[3] = r.getColor(R.color.outer_border); borderGradientColors[2] = r.getColor(R.color.inner_border_one); borderGradientColors[1] = r.getColor(R.color.inner_border_two); borderGradientColors[0] = r.getColor(R.color.inner_border); borderGradientPositions[3] = 0.0f;

borderGradientPositions[2] = 1-0.03f; borderGradientPositions[1] = 1-0.06f; borderGradientPositions[0] = 1.0f;

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

«стеклянного купола», который покрывает Представление и создает эффект  объема.

glassGradientColors = new int[5];

glassGradientPositions = new float[5];

int glassColor = 245;

glassGradientColors[4] = Color.argb(65, glassColor, glassColor, glassColor);

glassGradientColors[3] = Color.argb(100, glassColor, glassColor, glassColor);

glassGradientColors[2] = Color.argb(50, glassColor, glassColor, glassColor);

glassGradientColors[1] = Color.argb(0, glassColor, glassColor, glassColor);

glassGradientColors[0] = Color.argb(0, glassColor,

glassColor, glassColor);

glassGradientPositions[4] = 1-0.0f; glassGradientPositions[3] = 1-0.06f; glassGradientPositions[2] = 1-0.10f;

glassGradientPositions[1] = 1-0.20f;

glassGradientPositions[0] = 1-1.0f;

3.3. В завершение получите цвета, которые понадобятся для создания линейных градиентов, чтобы отобразить небо и землю в автогори- зонте.

skyHorizonColorFrom = r.getColor(R.color.horizon_sky_from);

skyHorizonColorTo = r.getColor(R.color.horizon_sky_to);

groundHorizonColorFrom = r.getColor(R.color.horizon_ground_from);

groundHorizonColorTo = r.getColor(R.color.horizon_ground_to);

}

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

private enum CompassDirection { N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW }

Теперь необходимо полностью заменить метод onDraw. Начните с вы- числения некоторых значений, зависящих от размера, включая центр Представления, радиус окружности компаса и прямоугольники, вме- щающие в себя внешний (по контуру) и внутренний циферблаты.

@Override

protected void onDraw(Canvas canvas) {

5. Рассчитайте толщину  внешнего кольца, исходя из размера шрифта, используемого для направляющих значений.

float ringWidth = textHeight + 4;

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

int height = getMeasuredHeight();

int width =getMeasuredWidth();

int px = width/2;

int py = height/2;

Point center = new Point(px, py);

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

RectF boundingBox = new RectF(center.x – radius, center.y – radius, center.x + radius, center.y + radius);

RectF innerBoundingBox = new RectF(center.x –      radius + ringWidth, center.y –    radius + ringWidth,

center.x +   radius – ringWidth, center.y +   radius – ringWidth);

float innerRadius = innerBoundingBox.height()/2;

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

Начните  с нижнего  внешнего слоя (внешнего  кольца),  постепенно продвигаясь наверх и внутрь. Создайте новый шейдер RadialGradient, используя цвета и позиции,  заданные  в пункте 3.2. Передайте  этот шейдер в новый объект Paint,  прежде чем рисовать окружность.

RadialGradient borderGradient = new RadialGradient(px, py, radius, borderGradientColors, borderGradientPositions, TileMode.CLAMP);

Paint pgb = new Paint();

pgb.setShader(borderGradient);

Path outerRingPath = new Path();

outerRingPath.addOval(boundingBox, Direction.CW);

canvas.drawPath(outerRingPath, pgb);

8. Теперь нужно нарисовать  автогоризонт. Разделите циферблат на две части, одна из которых будет означать небо, а другая — землю. Про- порции каждой из частей зависят от текущего параметра pitch.

Начните  с создания  объектов  Shader  и Paint,  они понадобятся для рисования неба и земли.

LinearGradient skyShader = new LinearGradient(center.x, innerBoundingBox.top, center.x, innerBoundingBox.bottom, skyHorizonColorFrom, skyHorizonColorTo, TileMode.CLAMP);

Paint skyPaint = new Paint();

skyPaint.setShader(skyShader);

LinearGradient groundShader = new LinearGradient(center.x, innerBoundingBox.top, center.x, innerBoundingBox.bottom, groundHorizonColorFrom, groundHorizonColorTo, TileMode.CLAMP);

Paint groundPaint = new Paint();

groundPaint.setShader(groundShader);

9. Нормализуйте значения  pitch  и roll, закрепив  их в пределах  ±90°

и ±180° соответственно.

float tiltDegree = pitch;

while (tiltDegree > 90 || tiltDegree < -90)

{

if (tiltDegree > 90) tiltDegree = -90 + (tiltDegree – 90);

if (tiltDegree < -90) tiltDegree = 90 – (tiltDegree + 90);

}

float rollDegree = roll;

while (rollDegree > 180 || rollDegree < -180)

{

if (rollDegree > 180) rollDegree = -180 + (rollDegree – 180);

if (rollDegree < -180) rollDegree = 180 – (rollDegree + 180);

}

10. Создайте контуры, по которым станет закрашиваться каждый сегмент окружности (земля и небо). Пропорции сегментов должны соответ- ствовать закрепленному параметру pitch.

Path skyPath = new Path();

skyPath.addArc(innerBoundingBox,

-tiltDegree,

(180 + (2 * tiltDegree)));

11. Поверните  Холст вокруг  центра  в направлении, противоположном параметру roll, и закрасьте контуры для неба и земли, используя объ- екты, созданные в пункте 4.

canvas.rotate(-rollDegree, px, py); canvas.drawOval(innerBoundingBox, groundPaint); canvas.drawPath(skyPath, skyPaint); canvas.drawPath(skyPath, markerPaint);

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

int markWidth = radius / 3;

int startX = center.x – markWidth;

int endX = center.x + markWidth;

13. Чтобы горизонтальные значения были более простыми для восприя- тия, шкала параметра pitch должна всегда начинаться с текущего зна- чения. Следующий код вычисляет положение границы между землей и небом на горизонтальной шкале.

double h = innerRadius*Math.cos(Math.toRadians(90-tiltDegree));

double justTiltY = center.y – h;

14. Вычислите  количество  пикселей,  соответствующих каждому  углу наклона.

float pxPerDegree = (innerBoundingBox.height()/2)/45f;

15. Переберите  значения  от 90° до –90°, исходя из текущего наклона, чтобы получить равномерную шкалу значений для параметра pitch.

for (int i = 90; i >= -90; i -= 10)

{

double ypos = justTiltY + i*pxPerDegree;

// Нарисуйте шкалу в рамках внутреннего циферблата. if ((ypos < (innerBoundingBox.top + textHeight)) ||

(ypos > innerBoundingBox.bottom – textHeight))

continue;

// Нарисуйте линию и угол наклона для каждой метки шкалы. canvas.drawLine(startX, (float)ypos,

endX, (float)ypos, markerPaint);

int displayPos = (int)(tiltDegree – i);

String displayString = String.valueOf(displayPos);

float stringSizeWidth = textPaint.measureText(displayString);

canvas.drawText(displayString,

(int)(center.x-stringSizeWidth/2), (int)(ypos)+1,

textPaint);

}

16. Нарисуйте более толстую линию на границе  земли и неба, изменив сперва толщину  начертания объекта  markerPaint (потом  верните предыдущее значение).

markerPaint.setStrokeWidth(2);

canvas.drawLine(center.x – radius / 2, (float)justTiltY, center.x + radius / 2, (float)justTiltY, markerPaint);

markerPaint.setStrokeWidth(1);

17. Чтобы  сделать точное значение  параметра  roll более простым  для восприятия, нарисуйте  стрелку и выведите текстовую  строку, пред- ставляющую  это значение.

Создайте  новый  объект Path  с использованием методов moveTo/ lineTo, чтобы нарисовать  стрелку,  указывающую вверх. Нарисуйте контур и отобразите  текстовую строку, которая  показывает  текущее значение параметра roll.

// Нарисуйте стрелку

Path rollArrow = new Path();

rollArrow.moveTo(center.x – 3, (int)innerBoundingBox.top + 14); rollArrow.lineTo(center.x, (int)innerBoundingBox.top + 10); rollArrow.moveTo(center.x + 3, innerBoundingBox.top + 14); rollArrow.lineTo(center.x, innerBoundingBox.top + 10); canvas.drawPath(rollArrow, markerPaint);

// Отобразите строку

String rollText = String.valueOf(rollDegree);

double rollTextWidth = textPaint.measureText(rollText);

canvas.drawText(rollText,

(float)(center.x – rollTextWidth / 2), innerBoundingBox.top + textHeight + 2, textPaint);

18. Верните Холст обратно в вертикальное положение, чтобы нарисовать оставшиеся  метки для циферблата.

canvas.restore();

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

canvas.save();

canvas.rotate(180, center.x, center.y);

for (int i = -180; i < 180; i += 10)

{

// Выводите цифровое значение каждые 30°

if (i % 30 == 0) {

String rollString = String.valueOf(i*-1);

float rollStringWidth = textPaint.measureText(rollString); PointF rollStringCenter =

new PointF(center.x-rollStringWidth/2, innerBoundingBox.top+1+textHeight);

canvas.drawText(rollString,

rollStringCenter.x, rollStringCenter.y, textPaint);

}

// В ином случае, рисуйте отметку else {

canvas.drawLine(center.x, (int)innerBoundingBox.top, center.x, (int)innerBoundingBox.top + 5,

markerPaint);

}

canvas.rotate(10, center.x, center.y);

}

canvas.restore();

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

canvas.save();

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

double increment = 22.5;

for (double i = 0; i < 360; i += increment) { CompassDirection cd = CompassDirection.values()

[(int)(i / 22.5)]; String headString = cd.toString();

float headStringWidth = textPaint.measureText(headString); PointF headStringCenter =

new PointF(center.x – headStringWidth / 2, boundingBox.top + 1 + textHeight);

if (i % increment == 0)

canvas.drawText(headString,

headStringCenter.x, headStringCenter.y, textPaint);

else

canvas.drawLine(center.x, (int)boundingBox.top, center.x, (int)boundingBox.top + 3, markerPaint);

canvas.rotate((int)increment, center.x, center.y);

}

canvas.restore();

21. Закончив с циферблатом, добавьте завершающие  штрихи. Начните с создания  «стеклянного купола» поверх циферблата, сделав его по- хожим на механические  часы. Применив массив со значениями для радиального градиента, определенный ранее, создайте новые объекты Shader  и Paint.  Используйте их, чтобы нарисовать  окружность над внутренней  поверхностью, как будто она покрыта стеклом.

RadialGradient glassShader =

new RadialGradient(px, py, (int)innerRadius, glassGradientColors, glassGradientPositions, TileMode.CLAMP);

Paint glassPaint = new Paint();

glassPaint.setShader(glassShader);

canvas.drawOval(innerBoundingBox, glassPaint);

22. Осталось  нарисовать  еще две окружности,  чтобы очертить границы для внутренней  и внешней  поверхностей.  Сделав  это, восстановите Холст в вертикальном положении  и закончите метод onDraw.

// Нарисуйте внешнее кольцо canvas.drawOval(boundingBox, circlePaint);

// Нарисуйте внутреннее кольцо circlePaint.setStrokeWidth(2); canvas.drawOval(innerBoundingBox, circlePaint);

canvas.restore();

}

Источник: Майер P. Android 2 : программирование приложений для планшетных компьютеров и смартфонов : [пер. с англ. ] / Рето Майер. — М. : Эксмо, 2011. — 672 с. — (Мировой компьютерный бестселлер).

По теме:

  • Комментарии