Processing:Библиотеки/Processing for Android/Руководства/«Живые» обои

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску


Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.



«Живые» обои[1]

Это руководство рассказывает, как при помощи режима программирования Android создать интерактивные «живые» обои.

Что такое «живые» обои?

«Живые» обои – это особый тип приложений, которые генерируют анимированные и интерактивные фоны на домашнем экране устройства. Технически это служба Android (т.е. подкласс класса Service), предназначенный для долгой работы в фоновом режиме. Режим программирования Android для Processing позволяет не беспокоиться обо всех этих низкоуровневых деталях и вместо этого сосредоточиться на написании кода. По сути, «живыми» обоями можно сделать любой скетч с 2D- или 3D-графикой. Все, что нужно сделать – это выбрать в PDE опцию Android > Wallpaper:

Создание «живых» обоев

Давайте начнем с написания простых «живых» обоев, которые будут просто фоном, на котором будут в случайном порядке меняться цвета. Для создания плавных переходов цветового тона и насыщенности мы воспользуемся цветовым режимом HSB. Мы можем сначала протестировать этот скетч в режиме программирования Java – чтобы проверить, правильно ли рассчитано время переходов. В первой версии мы просто будем менять цветовой тон и насыщенность каждые 5 секунд безо всяких плавных переходов:

float currH, currB;
int lastChange = 0;

void setup() {
  size(400, 400);
  colorMode(HSB, 100);
  currH = 100;
  currB = 100;
}

void draw() {
  background(currH, currB, 100);
  if (5000 < millis() - lastChange) { 
    pickNextColor();
    lastChange = millis();
  }
}

void pickNextColor() {
  currH = random(100);
  currB = random(100);
}

Теперь можно добавить плавные переходы между цветами:

float currH, currB;
float nextH, nextB;
float easing = 0.001;
int lastChange = 0;

void setup() {
  size(400, 400);
  colorMode(HSB, 100);
  currH = nextH = 100;
  currB = nextB = 100;
}

void draw() {
  background(currH, currB, 100);
  updateCurrColor();
  if (5000 < millis() - lastChange) { 
    pickNextColor();
    lastChange = millis();
  }
}

void pickNextColor() {
  nextH = random(100);
  nextB = random(100);
}

void updateCurrColor() {
  // плавный переход между текущим и следующим цветом:
  currH += easing * (nextH - currH);
  currB += easing * (nextB - currB);
}

Настроив нужным образом плавность и время перехода, замените функцию size(400, 400) на функцию fullScreen() – чтобы «живые» обои использовали весь экран устройства. Это нужно сделать перед запуском скетча на устройстве Android.

После установки скетч не появится сразу же на домашнем экране устройства Android. Вам нужно будет открыть селектор «живых» обоев и полистать все имеющиеся там обои, пока не найдете те, что сделали сами. Селектор «живых» обоев может выглядеть по-разному в зависимости от версии Android и того, какие обои у вас уже установлены. На Android 5.0 и новее он должен выглядеть примерно так:

Использование в обоях данных от датчиков

В руководстве о датчиках рассказывалось, как использовать API датчиков в Android для считывания данных от акселерометра. Мы можем использовать ту же самую технику и для получения данных, необходимых для создания компаса. В данном случае нам понадобятся данные одновременно от акселерометра и геомагнитного датчика – чтобы определить ориентацию устройства относительно магнитной оси Земли. Начнем со следующего шаблона:

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;

Context context;
SensorManager manager;
SensorListener listener;
Sensor accelerometer;
Sensor magnetometer;

void setup() {
  fullScreen(P2D);
  orientation(PORTRAIT);
  
  context = getContext();  
  listener = new SensorListener();
  manager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
  accelerometer = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
  magnetometer  = manager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
  manager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
  manager.registerListener(listener, magnetometer, SensorManager.SENSOR_DELAY_NORMAL);  
}

void draw() {
  background(255);
}

void resume() {
  if (manager != null) {
    manager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
    manager.registerListener(listener, magnetometer, SensorManager.SENSOR_DELAY_NORMAL);  
  }
}

void pause() {
  if (manager != null) {
    manager.unregisterListener(listener);
  }
}

class SensorListener implements SensorEventListener {
  float[] gravity = new float[3];
  float[] geomagnetic = new float[3];
  float[] I = new float[16];
  float[] R = new float[16];
  float orientation[] = new float[3]; 
 
  public void onSensorChanged(SensorEvent event) {
    if (event.accuracy == SensorManager.SENSOR_STATUS_ACCURACY_LOW) return;
    
    if (event.sensor.getType() ==  Sensor.TYPE_MAGNETIC_FIELD) {
      arrayCopy(event.values, geomagnetic);
    }
    if (event.sensor.getType() ==  Sensor.TYPE_ACCELEROMETER) {
      arrayCopy(event.values, gravity);
    }
  }
  public void onAccuracyChanged(Sensor sensor, int accuracy) { }
}

Здесь нужно отметить пару важных вещей:

  • Во-первых, мы считываем контекст при помощи context = (Context) getComponent() вместо getActivity(). Активность используется в обычных приложениях, тогда как в работе «живых» обоев – нет.
  • Для считывания данных акселерометра и геомагнитного датчика используется один и тот же слушатель событий. Кроме того, в скетче также есть пользовательская функция onSensorChanged(), а внутри нее функция event.sensor.getType() – значение, которое она возвращает, используется для того, чтобы определить, от какого именно датчика мы получаем данные.
  • Скорость считывания данных от датчиков задана на SENSOR_DELAY_NORMAL – это самая медленная предзаданная настройка скорости. Важно, чтобы «живые» обои расходовали заряд батареи по минимуму, поскольку они работают постоянно. Таким образом, при использовании датчиков скорость считывания данных лучше снизить до уровня, которого будет достаточно для генерирования фонового изображения. Вы даже можете задать собственную скорость, и это делается следующим образом:
int READING_RATE = 100000; // время в микросекундах
sensorManager.registerListener(listener, compass, READING_RATE);

Векторы силы притяжения и геомагнитного поля – это вся информация, что нужна, чтобы задать ориентацию устройства относительно поверхности Земли. Функция [,%20float[],%20float[],%20float[]) getRotationMatrix()] вычисляет матрицы наклона и вращения, из которых мы при помощи функции [,%20float[]) getOrientation()] получаем ориентацию устройства относительно направления геомагнитного поля. Таким образом, полная версия onSensorChanged() будет выглядеть следующим образом:

public void onSensorChanged(SensorEvent event) {
    if (event.accuracy == SensorManager.SENSOR_STATUS_ACCURACY_LOW) return;
    
    if (event.sensor.getType() ==  Sensor.TYPE_MAGNETIC_FIELD) {
      arrayCopy(event.values, geomagnetic);
    }
    if (event.sensor.getType() ==  Sensor.TYPE_ACCELEROMETER) {
      arrayCopy(event.values, gravity);
    }
    if (SensorManager.getRotationMatrix(R, I, gravity, geomagnetic)) {
      SensorManager.getOrientation(R, orientation);
      azimuth += easing * (orientation[0] - azimuth);
      pitch += easing * (orientation[1] - pitch);
      roll += easing * (orientation[2] - roll);
    }    
  }

Финальный шаг – код для отрисовки компаса. Ниже показано, как нарисовать максимально простой компас:

void draw() {
  background(255);
  
  float cx = width * 0.5;
  float cy = height * 0.4;
  float radius = 0.8 * cx;
  
  translate(cx, cy);
  
  noFill();
  stroke(0);
  strokeWeight(2);
  ellipse(0, 0, radius*2, radius*2);
  line(0, -cy, 0, -radius);
 
  fill(192, 0, 0);
  noStroke();    
  rotate(-azimuth);
  beginShape();
  vertex(-30, 40);
  vertex(0, 0);
  vertex(30, 40);
  vertex(0, -radius);
  endShape();
}

В результате получатся примерно такие «живые» обои:

Полный код для этого скетча можно посмотреть тут.

См.также

Внешние ссылки