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

Разметка
Для начала создадим разметку нашего интерфейса. Это стандартная
Рассмотрим саму разметку:
<?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"– разместит виджет под виджетом с указаным idandroid:layout_toRightOf = "@id/center"— разместит виджет слева от виджета с указаным idandroid: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Следующая строчка:
заменяет стандартное поведение при нажатии клавиш с кодами 24 и 25(это клавиши громкости, коды других клавиш здесь). Что это значит? Это значит, что если наш скрипт запущен, и нажата одна из этих клавиш — то стандартное действие этих клавиш не будет выполнено(громкость звука не будет изменена в данном конкретном случае).droid.fullKeyOverride([24,25],True)
Для изменения свойств виджетов из сценария имеется функция
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(так как специфика языка не та), но для ознакомительной статьи — в самый раз.
