image

Пробовали ли Вы читать книгу или статью вроде этой в автобусе или идя по улице? Могу поспорить пробовали! В этом случае Вы должны были заметить что чтение текста таким образом является не лучшей идеей из-за постоянной тряски. Похоже что тряска экрана является достаточно серьезной проблемой и устранение ее может дать очень хорошее улучшение UX. Моя идея состоит в том, чтоб использовать датчики ускорения для компенсации тряски так-же как зеркальные камеры стабилизируют сенсор или линзы. Технически это возможно так что почему бы не попробовать сделать это самому!

Существующие решения


Для начала давайте посмотрим на существующие решения. В Сети есть несколько интересных статей на такую-же тематику.

  1. NoShake: Content Stabilization for Shaking Screens of Mobile Devices от Lin Zhong, Ahmad Rahmati и Clayton Shepard об стабилизации экрана для iPhone (3) опубликованная в 2009 г. Статья подытоживает что стабилизация экрана работает и дает заметные результаты, но алгоритм потребляет “в среднем 30% мощности у 620 МГц ARM процессора”. Это делает эту реализацию непрактичной для реального применения. И хотя современные айфоны могут легко справится с данной задачей авторы не предоставили ни исходников ни собранного приложения чтоб можно было попробовать это в деле.

  2. Walking with your Smartphone: Stabilizing Screen Content от Kevin Jeisy. Эта статься была опубликована в 2014 и имеет хорошее математическое обоснование. Статья подытоживает что «используя скрытую марковскую модель мы получили хорошую стабилизацию в теории». К сожалению не предоставлено ни исходные кодов, ни собранного приложения, так что посмотреть не получится.

  3. Shake-Free Screen. Исследуется тот же самый вопрос, но нету готовых результатов чтоб попробовать.

Эти статьи дают хорошее объяснение темы нашей статьи но к сожалению не дают ни исходных кодов, ни скомпилированных приложений чтоб посмотреть на это в живую. Давайте попробуем изобрести колесо заново и реализуем стабилизацию экрана по своему.

Теория


Датчик ускорения может быть использован для определения перемещения устройства. Но судя по названию этот датчик предназначен все таки для определения ускорения. Чтоб ответить на вопрос «как определить перемещение имея ускорение», давайте посмотрим на устройства с датчиками:
image
Как видно там есть есть три оси, соответственно датчик дает три значения на выходе. Технически датчик состоит из трех датчиков расположенных по разным осям, но давайте воспринимать его как единое целое.

Три значения на выходе обозначают ускорение вдоль соответствующей оси:

image

Ускорение меряется в “м/с2”. Как можно видеть там есть некоторое ускорение вдоль оси Y. На самом деле это ускорение свободного падения и любой поворот устройства изменит все три значения:

image

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

Хорошо, но что насчет определения перемещения?


Я не могу показать какой-то наглядный пример, но если Вы немного переместите устройство, то вектор изменится: на самом деле он будет состоять из двух векторов: 1) вектор земного притяжения как и раньше; 2) вектор ускорения устройства из-за перемещения вдоль соответствующих осей. Самое интересное для нас это «чистый» вектор перемещения. Его достаточно просто просто получить путем вычитания вектора земного притяжения из результирующего вектора, но как определить истинный вектор земного притяжения? Эта задача может быть решена разными путями, но к счастью Андроид имеет специальный датчик линейного ускорения который делает как раз то, что нам нужно. В нормальных условиях выходные значения у датчика 0, и только перемещая устройство можно получить не нулевые значения. Здесь его исходный код если интересно. Мы на один шаг ближе к определению перемещения устройства. Давайте начнем программировать что нибудь.

Реализация


Чтоб найти как высчитать перемещение устройства давайте разработаем одно простое приложение с одной активити. Это приложение будет мониторить изменение ускорения и двигать специальный вью элемент соответствующим образом. Также оно будет показывать «сырые» значения ускорения на графике:

image

Я покажу только ключевые примеры кода. Полностью весь код есть в GIT репозитории. Ключевые вещи следующие:

1. Специальный элемент который мы будем двигать. Это синий блок с текстом внутри контейнера:


<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/graph1"
android:background="@drawable/dots_repeat_bg"
android:clipChildren="false">

<LinearLayout
    android:id="@+id/layout_sensor"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="20dp"
    android:orientation="vertical"
    android:background="#5050FF">
        <ImageView
        android:id="@+id/img_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>

    <TextView
        android:id="@+id/txt_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/img_test"
        android:textSize="15sp"
        android:text="@string/test"/>
</LinearLayout>
</FrameLayout>

Для перемещения layout_sensor мы будем использовать методы View.setTranslationX и View.setTranslationY.

Также подпишемся на событие нажатия на какой-либо элемент для сброса внутренних значений в 0 потому что на первых порах они могут быть очень непослушными:

private void reset()
{
    position[0] = position[1] = position[2] = 0;
    velocity[0] = velocity[1] = velocity[2] = 0;
    timestamp = 0;

    layoutSensor.setTranslationX(0);
    layoutSensor.setTranslationY(0);
}

2. Подпишемся на события датчика ускорения:


sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
sensorManager.registerListener(sensorEventListener, accelerometer, SensorManager.SENSOR_DELAY_FASTEST);

3. И самое главное: слушатель изменений. Его базовая реализация:


private final float[] velocity = new float[3];
private final float[] position = new float[3];
private long timestamp = 0;
private final SensorEventListener sensorEventListener = new SensorEventListener()
{
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {}

    @Override
    public void onSensorChanged(SensorEvent event)
    {
   	 if (timestamp != 0)
   	 {
   		 float dt = (event.timestamp - timestamp) * Constants.NS2S;

   		 for(int index = 0; index < 3; ++index)
   		 {
   			 velocity[index] += event.values[index] * dt;
   			 position[index] += velocity[index] * dt * 10000;
   		 }
   	 }
   	 else
   	 {
   		 velocity[0] = velocity[1] = velocity[2] = 0f;
   		 position[0] = position[1] = position[2] = 0f;
   	 }
    }
};

Давайте разберемся что здесь происходит. Метод onSensorChanged вызывается каждый раз когда значение ускорения изменяется (прим. переводчика: ну на самом деле он вызывается по таймеру не зависимо от того какие значения ускорения). Первым делом вы проверяем инициализирована ли переменная timestamp. В этом случае мы просто инициализируем основные переменные. В случае если метод вызван повторно, мы производим вычисления использую следующую формулу:

deltaT = time() - lastTime;
velocity += acceleration * deltaT;
position += velocity * deltaT;
lastTime = time();

Вы должны были заметить интересную константу 10000. Воспринимайте ее как некое магическое число.

И результат:



Как видно текущая реализация имеет две проблемы:

  • Дрифтинг и уползание значений
  • Контрольный элемент не возвращается в 0

На самом деле решение для обоих проблем есть общее — нужно ввести в формулу торможение. Измененная формула выглядит так:

deltaT = time() - lastTime;
velocity += acceleration * deltaT - VEL_FRICTION * velocity;
position += velocity * deltaT - POS_FRICTION * position;
lastTime = time();



Хорошо. Текущая реализация выглядит неплохо. Я бы добавил некоторые косметические улучшения типа низкочастотного фильтра для сглаживания, отрезание недопустимых значений и настройки программы.

Готовое приложение находится в репозитории в ветке “standalone_app”.

AOSP


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

Эта задача требует некоторого опыта в сборке AOSP. Google предоставляет всю необходимую документацию. В общем нужно скачать исходные коды Андроид для выбранного Nexus устройства. Собрать прошивку для Nexus и прошить ее. Не забывайте включить все необходимые драйвера перед сборкой.

Как только получится собрать стоковую прошивку, можно приступать к разработке и интеграции стабилизации экрана.

План реализации следующий:


  1. Найти способ смещения экрана в устройстве
  2. Разработать API во внутренностях AOSP чтоб дать возможность задавать смещение в стандартном Андроид приложении
  3. Разработать службу в демо приложении которая будет обрабатывать данные с датчика ускорения и задавать смещение используя API выше. Служба будет запускаться автоматически при включении устройства так что стабилизация будет работать сразу после включения

Сейчас я просто расскажу как я решил эти задачи.


1. Первый файл для исследования DisplayDevice.cpp который контролирует параметры экрана. Метод на который нужно смотреть void DisplayDevice::setProjection(int orientation, const Rect& newViewport, const Rect& newFrame). Самое интересное находится в строке 483:

image

где финальная матрица преобразований образуется из других компонентов. Все эти переменные являются экземплярами класса Transform. Этот класс предназначен для обработки преобразований и имеет несколько перегруженных операторов (например *). Чтоб добавить сдвиг добавим новый элемент:

image

Если Вы скомпилируете и прошьете Ваше устройство, экран там будет смещен на translateX пикселей по горизонтали и translateY пикселей по вертикали. В конечном итоге нам нужно добавить новый метод void setTranslate(int x, int y); который будет отвечать за матрицу сдвига.

2. Второй интересный файл SurfaceFlinger.cpp. Этот файл есть ключевым в создании API для доступа к параметрам экрана. Просто добавим новый метод:

image

который будет вызывать метод setTranslate для всех дисплеев. Другая часть выглядит немного странной, но я объясню это позже. Нам нужно модифицировать метод status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) добавив новую секцию в конструкцию switch:

image

Этот код является точкой входа в наше улучшение.

3. Служба обработки данных достаточно простая: она использует алгоритм разработанный ранее для получения значений смещения. Дальше эти значения через IPC передаются в SurfaceFlinger:

image

ServiceManager не распознается Android Studio потому что он недоступен для не системных приложений. Системные приложения должны собираться вместе с AOSP с помощью системы сборки makefile. Это позволит нашему приложению получить необходимые права доступа в скрытым API Андроид. Чтоб получить доступ к службе SurfaceFlinger приложение должно обладать правами “android.permission.ACCESS_SURFACE_FLINGER”. Эти права могут иметь только системные приложения (см. далее). Чтоб иметь право вызывать наше API с кодом 2020, приложение должно иметь правами “android.permission.HARDWARE_TEST”. Эти права также могут иметь только системные приложения. И что в конце концов сделать наше приложение системным, модифицируйте его манифест следующим образом:

image

Также создаете соответствующий makefile:

image

Остальные вещи в приложении (broadcast receiver загрузки, настройки, другое) достаточно стандартные и я не буду из касаться здесь. Осталась показать как сделать это приложение предустановленным (т.е. вшитым в прошивку). Просто разместите исходный код в каталоге {aosp}/packages/apps и измените файл core.mk так чтоб он включал наше приложение:

image

Финальная демонстрация:



Вы можете найти детальную информацию и исходный код на GitHub



Там есть приложение ScreenStabilization которое должно быть размещено в каталоге {aosp}/packages/apps, AOSP патч-файлы: 0001-ScreenStabilization-application-added.patch должен быть применен к каталогу {aosp}/build, 0001-Translate-methods-added.patch должен быть применен к каталогу {aosp}/frameworks/native.

Прошивка для Nexus 2013 Mobile собрана в конфигурации “userdebug” так что она больше подходит для тестирования. Чтоб прошить прошивку загрузитесь в режим загружчика удерживая кнопку “volume down” и нажимая кнопку “power” одновременно. Дальше введите:

fastboot -w update aosp_deb_screen_stabilization.zip

Эта процедура удалит все существующие данные на Вашем устройстве. Имейте ввиду что для того чтоб прошить любую нестандартную прошивку Вы должны разблокировать загружчик командой:

fastboot oem unlock

Заключение


Эта статья показывает как реализовать простой алгоритм стабилизации экрана и применить его ко всему устройству путем модификации исходных кодов Андроид и сборки пользовательской прошивки. Алгоритм не идеальный но достаточный для целей демонстрации. Мы создали модифицированную прошивку для устройства Nexus 2013 Mobile, но наш исходный код может быть применен к любому Nexus устройству и даже к любой AOSP системе типа CyanogenMod что делает возможным интеграцию стабилизации экрана в новые устройства.

P.S. На самом деле я также являюсь и автором оригинальной англоязычной версии статьи, которая была опубликована на blog.lemberg.co.uk, так что могу ответить на технические вопросы.
Поделиться с друзьями
-->

Комментарии (38)


  1. antonksa
    12.12.2016 23:02
    +1

    Интересная идея! Финальное видео подтверждает жизнеспособность! Только ходить уткнувшись в девайс опасно для здоровья :D


    1. lgorSL
      12.12.2016 23:31
      +1

      Этой возможности иногда не хватает при чтении в транспорте.


      Ещё к компенсации перемещений можно попробовать добавить вращения — они могут возникать, если, например, держать телефон одной рукой сбоку в ландшафтном режиме. Но возможно, появится куча некрасивых эффектов сглаживания из-за вертикальных и горизонтальных элементов интерфейса.


      1. DrZlodberg
        13.12.2016 08:32

        Прикрутить кирпич батарейку побольше и в полу-расслабленой руке будет стабилизороваться нативно :)
        А вообще обычно достаточно придерживать второй рукой или даже просто опереть на другую руку/сумку/что-нибудь ещё.
        Но идея интересная.


  1. EndUser
    13.12.2016 00:00

    Занятно. Хотелось бы пользоваться. Но у меня CM11.


  1. vjjvr
    13.12.2016 00:22
    -4

    Матрицы фотоаппаратов уж лет 10 как стабилизируют.
    Но там миллиметров достаточно.

    Для хорошей стабилизации экрана не хватает места.


    1. vjjvr
      13.12.2016 16:35
      -2

      Господа минусаторы — еще раз повторяю для идиотов:

      Формально — это возможно.

      1. Фактически — нет. Нужна гораздо большая амплитуда
      2. И еще одно — мешает шлейф от перерисовки на экране.

      Программно это сделать можно.
      Но без конструктивно заложенных возможностей — толку от этого 0.

      Только по практиковаться в программировании датчиков.


  1. justmara
    13.12.2016 00:30

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


    1. vjjvr
      13.12.2016 00:39

      такие вещи нужно решать не чисто программно.


    1. justmara
      13.12.2016 09:23
      +5

      Не, правда. Я не к тому, что "всё равно найдутся недовольные".
      Идея клёвая. Реально вот направление мысли, реализация — молодцом.


      Вот только конечный результат абсолютно беспомощен и бесполезен :) Но так что уже аппаратные ограничения.


    1. St321
      13.12.2016 12:19
      +2

      при уровне вибрации, вызывающей шлейфы, вам явно не до чтения будет=))


  1. da411d
    13.12.2016 00:39

    Было б круто иметь такой модуль для Xposed


    1. IgeNiaI
      13.12.2016 01:29

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


    1. lonelymyp
      13.12.2016 13:53

      Тоже хочу =)


  1. klim76
    13.12.2016 01:29

    как батарея себя чувствует с новой прошивкой?


    1. r_ii
      13.12.2016 01:33
      +1

      Ощутимой разницы я не заметил.


  1. slayerhabr
    13.12.2016 03:04
    +6

    Не сочтите за не конструктивную критику.

    1. Для глаз было 2 движущихся слоя, стало 3.
    2. Датчики и реакция заметно отстают от реального движения.
    В связи с этим нагрузка на глаза может стать еще больше


    1. Stan_1
      13.12.2016 08:13

      У меня после просмотра видео — такое же ощущение. Стаблизация камеры, на которую ссылается автор — построена вообще по другому — на гироскопах. Здесь же есть задержка обработки, и изображение все-равно двигается, просто более плавно, чем «дергает» транспорт. Но, например, было ли для меня комфортнее читать текст таким образом — скорее всего нет.


      1. aamonster
        13.12.2016 11:39
        -1

        «Более плавно» оно двигается только относительно экрана. Относительно глаз — рывками (ВЧ-составляющие движения не убираются из-за недостаточного быстродействия).
        Ну и да, даже если бы удалось сделать идеально (скажем, датчик движения -> аппаратная обработка -> аппаратное смещение изображения на экране), и, допустим, сам экран достаточно быстродействующий — всё равно получили бы две проблемы:
        1. Двигающаяся в поле зрения рамка экрана (и держащая её рука)
        2. Голову-то при тряске тоже мотает! Т.е. как ни стабилизируй — картинка в поле зрения будет дёргаться. Привязываться надо к глазам, для этого крепить что-то на голову — что естественным образом приводит к идее очков.


    1. zikolach
      13.12.2016 11:28

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


    1. ad1Dima
      13.12.2016 14:08

      Вы точнее меня сформулировали мою мысль.

      За собой заметил что в транспорте прислонившись к стеклю читать становится сложнее: голова начинает двигаться с частотой опоры, а рука с частотой туловища, которое прекрасно справляется со стабилизацией. Фактически получается тот же лишний «слой»

      Мне интересно. Те, кому сложно читать в транспорте, прижимают локти к туловищу или нет?


  1. xxvy
    13.12.2016 05:29
    +2

    Укачивать не будет?


  1. alan008
    13.12.2016 09:40

    Сравнение между демо-1 и демо-2 вызывает желание создать демо-3 (идеальный вариант), когда текст просто жёстко зафиксирован :-) (т.е. обычный режим, без приложения :-))


    1. perfect_genius
      13.12.2016 10:58

      Потому что вы не трясётесь. А вы посмотрите видео в транспорте =)


      1. ad1Dima
        13.12.2016 14:10

        Вот, вы мне скажите, вы когда видео в транспорте смотрите локти к туловищу прижимаете, или на коленях/вытянутых руках смотрите?


        1. perfect_genius
          13.12.2016 16:33

          Я не смотрю и не читаю в транспорте, но когда было — на вытянутых, полусогнутых. Синхронизировал с головой.


          1. ad1Dima
            13.12.2016 16:54

            И я тоже жесткой фиксацией рук и головы к плечам вполне комфортно читаю. Толи дороги ровнее, толи что.


  1. mickvav
    13.12.2016 11:05
    -1

    Дорогой автор, закрепите в штативе камеру, которой снимаете демо. Прицепите к вашему девайсу какой-нибудь контролируемый источник вибрации (микроконтроллер с вибродвижком). Запилите в качестве опорной картинки какую-нибудь референсную — например, тупо клетчатое поле. Реализуйте на OpenCV вычисление функционала «качества сглаживания» — например, евклидову норму разницы между картинкой в статике и с подключенным источником вибрации, осредненную за некий период времени. Дальше можно любым алгоритмом многомерной оптимизации автоматически подкрутить ваши волшебные константы до минимума функционала, проходясь каждый раз по спектру. Двое-трое суток работы такой установки — и у вас профиль из десятка циферок для конкретного телефона, который можно (попробовать) продать производителю телефона за денюжку. Но для референсных моделей придется выложить в паблик, чтобы народ начал массово хотеть эту фишку ;)


    1. Free_ze
      13.12.2016 19:56

      Синтетика.


  1. St321
    13.12.2016 11:38

    В оболочку ОС я бы добавил такую функцию! Ведь люди читают текст не только в электронных книгах.


  1. edogs
    13.12.2016 14:31

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

    Хотелось бы узнать скорость реакции стабилизации. Т, е. вот начали Вы движение экрана — через сколько времени Ваш софт перерисует экран на новом месте? Наверняка же можете тех. данные по дебагу посмотреть?
    По ощущениям там не больше 15к/с, для качественной стабилизации надо не меньше 120 к/с.


    1. r_ii
      13.12.2016 15:01

      Вы правы в том, что изображение все таки не стоит на месте. В этом плане алгоритм требует доработки.
      Здесь требуется разработка очень точной инерциальной системы позиционирования.
      Вот здесь например обсуждение: http://stackoverflow.com/questions/7829097/android-accelerometer-accuracy-inertial-navigation

      Насчет скорости реакции — она зависит от частоты опроса датчика, задержки между-процессного взаимодействия и задержки обновления UI. Основной параметр все таки частота опроса датчика — и она задана как SENSOR_DELAY_FASTEST, что должно давать 100 Гц.
      Здесь также есть возможности для улучшений путем переноса реализации алгоритма из отдельного приложения в драйвер датчика.

      Какая реальная задержка — нужно еще подумать как это можно измерить.


      1. aamonster
        13.12.2016 22:27
        +1

        К сожалению, проблема — не дрожание телефона относительно Земли, а дрожание телефона относительно глаз. Т.е. стабилизировать изображение на экране — мало, надо стабилизировать движение головы. Как это можно сделать — лично мне непонятно, единственная идея — фронтальной камерой ловить положение глаз (что-то не соображу, даст ли это достаточно информации).

        Но к подходу поставленной задачки вы подошли серьёзно, приятно было посмотреть =)


    1. MaxxONE
      14.12.2016 00:53
      +1

      «Резинка» должна присутствовать в любом случае. Представьте себе — вы с устройством шагнули вперед, а изображение «осталось» в метре за вашей спиной. Это случай идеальной стабилизации. И ведь экран после воздействия колебаний никогда не вернется в прежнее положение, даже если у вас просто дрогнула рука. Так что в реальном устройстве изображение всегда будет стремиться занять центр экрана с тем или иным ускорением. А это ускорение будет восприниматься как та же «резинка».
      Да, я понял, что вас больше напрягает скорость реакции прототипа на возмущение. Но даже с хорошей скоростью стабилизации эффект останется.


      1. edogs
        14.12.2016 02:08

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

        Но даже с хорошей скоростью стабилизации эффект останется.
        Отчего же?

        «Резинка» должна присутствовать в любом случае. Представьте себе — вы с устройством шагнули вперед, а изображение «осталось» в метре за вашей спиной.
        А не фиг ходить пялясь в экран, это надо вбивать ремнем с младенчества.


        1. MaxxONE
          14.12.2016 11:39

          Отчего же?

          Собственно, я об этом написал.
          в реальном устройстве изображение всегда будет стремиться занять центр экрана с тем или иным ускорением. А это ускорение будет восприниматься как та же «резинка».

          То есть — либо изображение постоянно улетает за пределы экрана, «ползёт», либо вы получаете «резинку». Но уже не по причине низкой FPS.
          А не фиг ходить пялясь в экран, это надо вбивать ремнем с младенчества.

          Да я, собственно, абстрактный пример привел, для иллюстрации явления.


          1. edogs
            14.12.2016 17:03

            Собственно, я об этом написал.
            Так мы об этом и спросили:) Отчего же будет стремиться? Само не будет. Алгоритмически это не нужно, надо просто прибить изображение к точке в реальном мире. Можно сделать кнопочку для центровки изображения, что бы иметь какой-то контроль и/или делать это при перелистывании страницы. Но постоянно ползающий текст это ппц.


            1. MaxxONE
              14.12.2016 23:15

              Алгоритмически это нужно. Представьте: едете вы в автобусе, читаете книгу. Автобус тормозит, и текст на экране смартфона бодро улетает далеко за пределы экрана. Будете при каждом ускорении кнопку центрирования жмакать?


              1. edogs
                15.12.2016 01:44

                За пределы экрана — можно автоматом перерисовывать в 0 позицию если улетает дальше 20% экрана или можно дальше пределов экрана вообще не отпускать — достигло границ и все.
                Лучше мы один раз при торможении нажмем кнопку, чем постоянно будем пытаться отследить уползающий текст. А он будет уползать именно постоянно, если сам режим уползания сделать.