Секундомер под Android на Python + sl4a + fullScreenUI

Вступление


Такая замечательная вещь как SL4A(Scripting Level for Android) уже давно не является новостью. С каждым новым релизом SL4A возможности API для доступа/управления смартфоном растут. Еще до недавних пор создание пользовательского интерфейса ограничивалось средствами webView и стандартными диалоговыми окнами. Но в версии r5 появился новый, как заявили разработчики, пока что экспериментальный, способ создания пользовательского интерфейса — fullScreenUI.
FullScreenUI позволяет создавать интерфейс, используя стандартные виджеты Android-а (кнопки, текстовые поля, радиокнопки, и проч.), а также обрабатывать события от них. На примере создания простого секундомера я хочу продемонстрировать возможности этого API.

Я рассчитываю, что вы уже знакомы с SL4A(если нет — то Хабре достаточно много полезной и интересной информации).

Что получится


Вот скрины конечного результата:


Разметка


Для начала создадим разметку нашего интерфейса. Это стандартная Android-овская xml-разметка(подробнее о ней можно узнать на http://developer.android.com/guide/topics/ui/index.html). Конечно же SL4A не поддерживает всех тонкостей разметки и еще недавно не поддерживал очень важного типа разметки RelativeLayout, но с версии r6 эта возможность стала поддерживаться.
Рассмотрим саму разметку:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
	android:id="@+id/MainWidget"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	xmlns:android="http://schemas.android.com/apk/res/android">
        android:background="#ff000000"
    <TextView
            android:id="@+id/display"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:textColor="#0bda51"
            android:text="00:00:00.000"
            android:textStyle="bold"
            android:gravity="center"
            android:textSize="60dp" />
    <Button
            android:id="@+id/startbutton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_below="@id/display"
            android:layout_alignParentLeft="true"
            android:textSize="40dp"
            android:layout_toLeftOf = "@id/center"/>
    <Button
            android:id="@+id/center"
            android:layout_below="@id/display"
            android:layout_height="wrap_content"
            android:layout_width="0dp"
            android:layout_centerHorizontal="true" />
    <Button
            android:id="@+id/stopbutton"
            android:layout_width="0pdp"
            android:layout_height="wrap_content"
            android:enabled="false"
            android:textSize="40dp"
            android:layout_below="@id/display"
            android:layout_alignParentRight="true"
            android:layout_toRightOf = "@id/center"/>
            
    <TextView
        android:id="@+id/info"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_below="@id/stopbutton"
        android:textColor="#FFFFFF"
        android:text=""
        android:textStyle="bold"
        android:layout_alignParentBottom="true"
        android:textSize="30dp"
        android:layout_alignParentBottom="true"/>

        
</RelativeLayout>


Если вы даже первый раз видите эту разметку, но имеете некий опыт работы с HTML или XML — там все должно быть понятно. Если говорить о поддержке RelativeLayout, то полноценное использование этого типа разметки стала возможным после того, как стали поддерживаться атрибуты такого типа, как

  • android:layout_alignParentBottom="true" — нижний край виджета будет выровнян по нижнем крае родительского виджета(аналагочно для layout_alignParentTop, layout_alignParentLeft, layout_alignParentRight )
  • android:layout_below="@id/display" – разместит виджет под виджетом с указаным id
  • android:layout_toRightOf = "@id/center" — разместит виджет слева от виджета с указаным id
  • android:layout_centerHorizontal="true" – разместит по центру родительского виджета
  • и т.д


Button с id «center» созданный только для того, чтобы кнопки «Старт» и «Стоп» растянулись до него(он размещен по центру). Вот что должно получится:



Код


Собственно сам код секундомера. Некоторый тривиальный или дублирующийся код опущен. Полная версия: http://pastebin.com/z4H2p7Wq
Да простит меня сообщество Python-а за некрасивый код и за глобальные переменные, просто с этим языком программирования я познакомился совсем недавно.
#StopWatch.py

#------------Ресурсы------------
    
layout="""<?xml version="1.0" encoding="utf-8"?>
...
</RelativeLayout>
"""
rCircle_label = "Круг"
rStart_label = "Старт"
rClear_label = "Очистить"
rStop_label = "Стоп"
#---------------------


import android, os, datetime
    
#Глобальные переменные
starttime=datetime.datetime.now()
runed = False #если True то таймер запущен
lastcircle = 0
cleared = True #если True, то показания секундомера очищены

def format_time(tm):
    hours = int(tm.seconds / 3600)
    minuts = int((tm.seconds - hours*3600)/60)
    seconds=tm.seconds - hours*3600 - minuts*60
    microseconds = round(tm.microseconds/1000)
    return "{0:0>02}:{1:0>02}:{2:0>02}.{3:0>03}".format(hours,minuts,seconds,microseconds)

#Возвращает разницу времени в виде строки. Если now =0, то разница с текущим временем
def timediff(prev, now=0):
    if not now: now=datetime.datetime.now()
    diff=now-prev	
    return format_time(diff)
		
def stopwatch_start():
    global runed,lastcircle,starttime
    runed=True
    starttime=datetime.datetime.now()
    lastcircle = starttime
    droid.fullSetProperty("startbutton","text",rCircle_label)
    droid.fullSetProperty("stopbutton","enabled","true")

def stopwatch_circle():
    #код опущен
    # t - сформированная строка с временем круга
    lastdata =  droid.fullQueryDetail("info").result['text']
    newdata = lastdata+"\n"+t;
    droid.fullSetProperty("info","text",newdata)    
    lastcircle = datetime.datetime.now()

def stopwatch_stop():
    #код опущен
	

def stopwatch_clear():
    #код опущен
	
def eventloop():
  while True:
    event=droid.eventWait(50).result
    if runed:      
        droid.fullSetProperty("display","text",timediff(starttime))
    if event != None:
        if event["name"]=="key":
            droid.vibrate(30)
            if event["data"]["key"] == '4':
                return
            elif event["data"]["key"]=='24' and cleared:
                if runed:
                    stopwatch_circle()
                else:
                    stopwatch_start()
            elif event["data"]["key"]=='25' and runed:
                stopwatch_stop()
                
        elif event["name"]=="click":
            droid.vibrate(30)
            id=event["data"]["id"]
            if id=="startbutton" and not runed:
                stopwatch_start()
            elif id=="stopbutton" and runed:
                stopwatch_stop()
            elif id=="stopbutton" and not runed:
                stopwatch_clear()
            elif id=="startbutton" and runed:
                stopwatch_circle()
        elif event["name"]=="screen":
            if event["data"]=="destroy":
                return
            

droid = android.Android()
try:
    print(droid.fullShow(layout))
    droid.fullKeyOverride([24,25],True)
    droid.fullSetProperty("MainWidget","background","#ff000000")
    droid.fullSetProperty("startbutton","text",rStart_label)
    droid.fullSetProperty("stopbutton","text",rStop_label)
    eventloop()
finally:
    droid.fullDismiss()


Разбор кода


Итак, чтобы отобразить созданный нами в виде xml-разметки интерфейс надо передать строку с разметкой в функцию droid.fullShow. Можно конечно создать отдельный файл с разметкой и потом его прочитать, но в случае, когда разметка несложная, как у меня, я просто присвоил ее переменной layout. В отладочных целях результат, возвращаемыйdroid.fullShow можно вывести на консоль:
print(droid.fullShow(layout))
Если в разметке были ошибки, или поддерживаемые атрибуты, будет выведено соответствующее сообщение. После вызова этой функции, если разметка была корректной, она отобразится на экране аппарата. Чтобы убрать ее нужно вызвать функцию:
droid.fullDismiss()
Если эта функция не будет вызвана по каким то причинам как например аварийное завершения программы, то созданный интерфейс не очистится автоматически, а просто останется, поэтому важно воспользоваться конструкцией try finally

Следующая строчка:
droid.fullKeyOverride([24,25],True)
заменяет стандартное поведение при нажатии клавиш с кодами 24 и 25(это клавиши громкости, коды других клавиш здесь). Что это значит? Это значит, что если наш скрипт запущен, и нажата одна из этих клавиш — то стандартное действие этих клавиш не будет выполнено(громкость звука не будет изменена в данном конкретном случае).

Для изменения свойств виджетов из сценария имеется функция droid.fullSetProperty, которая принимает три параметра: id виджета, название свойства, присваиваемое значение. Например этой строчкой
droid.fullSetProperty("startbutton","text",rStart_label)

мы меняем надпись на кнопке.

Для запроса свойств droid.fullQueryDetail, принимает одно значение — id виджета. Для примера, получить значение свойства — text текстового поля с id info можно так:
droid.fullQueryDetail("info").result['text']


Обработка сообщений

Все действия пользователя, как например нажатие на кнопку, или клавишу, в сценарий попадают в виде сообщений.
Обработка сообщений удобно реализовать в отдельной функции. У меня функции eventloop(). Для обработки сообщений имеется ряд функций. Для этого примера удобно было использовать: droid.eventWait. Эта функция останавливает сценарий, пока не будет получено некое сообщение. Принимает необязательный параметр — максимальное время ожидания в мс. Если за это время сообщение не было получено, сценарий продолжит выполнение, а результатом функции будет объект None. Если же сообщение получено, то результатом выполнения
event=droid.eventWait(50).result
будет ассоциативный массив с именем события и информацией о нем.
Для начала проверяем значение event["name"], если оно равно «key», то была нажата кнопка, код которой можно узнать из event["data"]["key"]. Если же оно равно «click», то было нажатия на кнопку(или другой виджет), id которого можно узнать из event["data"]["id"].

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

Итог


С появлением fullScreenUI в sl4a разработчики на Python, Perl, JRuby, Lua, BeanShell, JavaScript теперь могут конкурировать с разработчиками на Java. Хотя и поддержка fullScreenUI в sl4a еще далека от идеала, но все же создавать достаточно хороший, быстрый графический интерфейс для своих сценариев уже можно. Разработчики постоянно занимаются усовершенствованием данного API, что очень радует.

upd: Секундомер не самый удачный пример использования sl4a + python(так как специфика языка не та), но для ознакомительной статьи — в самый раз.
Share post

Similar posts

Comments 16

    +1
    Ох, не знаю… мне все это кажется ужасным… когда со своими языками лезут в какую-то существующую платформу, городят какие-то промежуточные слои, чтобы это завелось… зачем все это, зачем?
      0
      Ну тут больше не «лезут в», тут, я бы сказал, надстраивают: Scripting level же.
        0
        Потому что мне было бы проще написать прототип на питоне, протестировать его и если ок тогда учить Java или нанимать программиста. :)
        0
        Ох как раз подготовил статью как на питоне написать полноценное приложение для андроид с апкашкой и маркетом )
        кто там говорил выше про то что «нечего лезть», скажу так:
        Скорость работы приличная, так как в общем то работают сишные либы, без еще одного слоя. То есть один слой (ява) убрали, другой (питон) добавили. Тем более, для питона, есть и нативные либы написанные на си, которые компилятся и прекрасно работают на андроиде
          0
          Ну у java небольшой входной порог для старта, неужели нет желания оперировать несколькими языками?
            +2
            Ну лично у меня нет. Не буду спорить что лучше а что хуже, лично мое ИМХО:
            Я знаю хорошо питон, и пишу на нем под все используемые мной платформы: Windows, Linux, Android, пишу так же под web.
            У меня возникла задача написать специфичное приложение под android, и я сделал это на питоне, так как вместе с изучением нового фремворка времени у меня ушло не более трех-четырех дней. Яву я бы не стал специально для этого изучать, а если бы изучал бы, то на подобное приложение у меня ушел бы не один месяц.
            Да, хочу заметить — я не программист по профессии, это мое хобби.
            Когда то на symbian писал достаточно много софта на питоне, в то время их смартфоны с тачскринами только появились, а приложений толком не было. Написал достаточно популярный софтин, чтоб нокия начала практически включать питон в прошивку. Моя рисовалка, например, достаточно времени была быстрее и удобнее сишных аналогов.
            Да и какая разница, на чем написано приложение, если оно написано и работает.
              +2
              Напоминает ситуацию, когда молотком саморезы забивают
                +2
                Ведь отвертку то надо купить, а молоток уже есть
                  +1
                  Ну почему молотком? Если цели можно достичь быстрее и легче, а результат практически тот же, то зачем лично мне выбирать длинную дорогу.
                  Вы слышили что нибудь про стоимость разработки?
                  Так вот, для моего фришного приложения стоимость разработки (затраченного времени) была практически нулевая, учитывая пользу которое оно мне принесет. А время на изучение явы, изучение новых технологий, мне дорого станет.
                  Таким же образом можно ругать дотнетчиков, пхпшников и пр.
                    0
                    Вы поймите, что будет такая задача, с которой ваш язык не справится на 100%, а сроки будут поджимать, поэтому очень удобно иметь у себя в гараже «несколько инструментов».
                      0
                      Какая, например, задача? Сам изучаю Python и не вижу пока в перспективе, чтобы он с чем-то не справился.
                +1
                Люди не хотят учить ничего нового, хотят навязывать свои привычки в чужие платформы. Пофиг, что там гугл на джаве под андроид написал, мы все уберем и напишем с нуля на питоне. И становится маркет полон такоого…
                  +2
                  Простите, чего такого становится полон маркет? Не понимаю, какая разница, что там внутри под капотом. Качество приложения не зависит от используемого языка. Если уж так судить, то говна на маркете, написанного на яве.
                    0
                    Да никто ничего не навязывает. Каждый язык программирования имеет свою специфику и хорош для определенных целей. Под то же windows вы же не только на С++ пишете. А андроид тоже ведь полноценная платформа, из под которой можно и спутником управлять. Поэтому я не считаю это навязыванием своих привычек, я бы назвал это расширением потенциала андроида.
                  0
                  О, я бы почитал такое :) Сам некоторое время назад смотрел на sl4a, но копаться не начал…
                    0
                    С удовольствием бы прочитал.

                  Only users with full accounts can post comments. Log in, please.