Возможно, для вас будет новостью, но разрабатывать мобильные приложения с функционалом, который доступен Java разработчикам, под Android с помощью фреймворка Kivy не просто просто, а очень просто! Именно этого правила я придерживаюсь, создавая свои проекты с Python + Kivy — разработка должна быть максимально простой и быстрой. Как щелчок пальцами.


На новичков подаваемая информация не расчитана, я не буду на пальцах объяснять, что, откуда и куда. Думаю, те, кто читает данную статью, обладают достаточными для понимания материала, знаниями. К тому же, Kivy, как я уже только что написал, очень простой фреймворк и вся документация с примерами использования находится в исходниках, достаточно в PyCharm кликнуть левой кнопкой мыши, зажав Ctrl, по интересующему виджету — и вы получите полную справку с примерами и описанием атрибутов!


В прошлой статье были рассмотрены несколько экранов приложения Clean Master в реализации на Kivy. Сегодня я покажу вам один из черновиков домашней страницы тестового приложения, над которым мы работаем для одного стартапа. Вот так это будет выглядеть после запуска примера:



Ну, что ж, а теперь приступим! Нам понадобиться: кофе-сигареты, террариум с третьим Python-ом, то ли птица, то ли фрукт — Kivy и немного мозгов. Наличие последних приветствуется! Заходим на github и качаем Мастер создания нового проекта для фреймворка Kivy + Python3 (да, я полностью отказался от использования Python2). Распаковываем, переходим в папку с мастером и запускаем:


python3 main.py name_project path_to_project -repo repo_project_on_github

если у проекта имеется репозиторий на github.


Или


python3 main.py name_project path_to_project

если репозитория не github не имеется.


В этом случае после создания откройте файл проекта main.py и отредактируйте, функцию отправки баг репорта вручную. А лучше заводите для своих будущих проектов репозитории на github.



Итак, в результате мы получаем дефолтный Kivy проект со следующей структурой каталогов:



Отдельно следует рассмотреть каталог Libs:



Остальные каталоги проекта в комментариях не нуждаются. Созданный проект будет иметь два экрана — главный и экран настроек:



Все что нам нужно, это использовать свой главный экран, то есть заменить файл startscreen.py в директории Libs/uix, создать новый файл разметки экрана startscreen.kv в папке Libs/uix/kv, ну, и добавить новые импорты и сопутствующие классы, если таковые имеются.


Давайте начнем с кастомных кнопкок, которые используются в нашем главном экране:



Создадим в директории Libs/uix файл custombutton.py и определим в нем класс нашей кнопки:


custombutton.py
import os

from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.properties import StringProperty

root = os.path.split(__file__)[0]
Builder.load_file('{}/kv/custombutton.kv'.format(
    root if root != '' else os.getcwd())
)

class CustomButton(Button):
    icon_button = StringProperty('')

Разметка кнопки в директории Libs/uix/kv — файл custombutton.kv:


custombutton.kv
#:kivy 1.9.1

<CustomButton>:
    background_normal: 'Data/Images/none.png'
    background_down: 'Data/Images/shadows/shadow_btn.png'
    size_hint: None, 1
    width: 150
    # Рамка
    Image:
        source: 'Data/Images/rectangle.png'
        pos: root.x + 2, root.y
        height: root.height
        width: root.width - 5
    # Иконка
    Image:
        source: root.icon_button
        pos: root.x + 16, root.y + 16
        size: 115, 115

Также мы будем использовать класс ImageButton — кнопку с изображением — для баннеров. Поскольку класс относится к UI, я поместил файл imagebutton.py в каталог Libs/uix:


imagebutton.py
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior

class ImageButton(ButtonBehavior, Image):
    pass

Далее нам потребуется класс для смены рекламных баннеров на главном экране программы. Создадим его в директории классов приложения Libs/programclass — файл show_banners.py :


show_banners.py
import os 
from random import choice 

from kivy.uix.boxlayout import BoxLayout 

from Libs.uix.imagebutton import ImageButton 

class ShowBanners(object): 
    '''Меняет и выводит на главном экране рекламные баннеры.''' 

    def __init__(self): 
        self.banner_list = os.listdir( 
            '{}/Data/Images/banners'.format(self.directory) 
        ) 

    def show_banners(self, interval): 
        if self.screen.screen_manager.current == '': 
            name_banner = choice(self.banner_list) 

            box_banner = BoxLayout(padding=5) 
            new_banner = ImageButton( 
                id=name_banner.split('.')[0], 
                source='Data/Images/banners/{}'.format(name_banner), 
                on_release=self.on_banner 
            ) 
            box_banner.add_widget(new_banner) 

            name_screen = name_banner 
            banner = self.Screen(name=name_screen) 
            banner.add_widget(box_banner) 
            self.screen.banner_manager.add_widget(banner) 
            effect = choice(self.effects_transition) 
            self.screen.banner_manager.transition = effect() 
            self.screen.banner_manager.current = name_screen 
            self.screen.banner_manager.screens.pop() 

    def on_banner(self, instance_banner): 
        if isinstance(instance_banner, str): 
            print(instance_banner) 
        else: 
            print(instance_banner.id)

Данный класс просто меняет экраны с баннерами, устанавливая их в менеджере экранов banner_manager:



Не забываем добавить импорт созданного класса в библиотеку классов programclass в файле инициализации:



Вот так, примерно, это будет выглядеть:



Для демонстрации я выставил очень маленькую задержку смены рекламных баннеров. Кроме того, в примере всего два баннера. Набор шейдеров для анимаций смены афиш мы импортируем в базовом файле program.py:



Так, ну, и перед тем, как, собствено, создать и заменить стандартные startscreen.py и startscreen.kv, создадим еще один класс, реализующий поиск магазин из поисковой строки:




search_shop.py
class SearchShop(object):
    '''Поиск магазина по ключу из строки поиска главного экрана.'''

    def search_shop(self):
        key_name_shop = self.screen.text_input_search.text.lower()

        if key_name_shop != '':
            if key_name_shop in self.shops:
                print('Find shop {}'.format(key_name_shop.upper()))
            else:
                self.KDialog(title=self.name_program).show(
                    text=self.core.string_lang_shop_not_found.format(
                        key_name_shop
                    )
                )
                self.screen.text_input_search.text = ''

Опять же, добавляем импорт созданного класса в библиотеку классов programclass в файл инициализации.


Реализация главного экрана приложения:


startscreen.py
import os 

from kivy.uix.button import Button 
from kivy.uix.boxlayout import BoxLayout 
from kivy.uix.actionbar import ActionItem 
from kivy.uix.gridlayout import GridLayout 
from kivy.uix.screenmanager import Screen 
from kivy.uix.scrollview import ScrollView 
from kivy.core.window import Window 
from kivy.animation import Animation 
from kivy.lang import Builder 
from kivy.properties import ( 
    ObjectProperty, ListProperty, StringProperty, BooleanProperty, 
    NumericProperty 
) 

from Libs.uix.custombutton import CustomButton 
from Libs.uix.garden.stiffscroll import StiffScrollEffect 

__version__ = '0.0.1' 

root = os.path.split(__file__)[0] 
root = root if root != '' else os.getcwd()

class MyOwnActionButton(Button, ActionItem): 
    markup = BooleanProperty(True) 
    minimum_width = NumericProperty(150) 

class StartScreen(BoxLayout): 
    events_callback = ObjectProperty(None) 
    '''Функция обработки сигналов экрана.''' 

    core = ObjectProperty(None) 
    '''module 'Libs.programdata' ''' 

    color_action_bar = ListProperty( 
        [0.4, 0.11764705882352941, 0.2901960784313726, 0.5607843137254902] 
    ) 
    '''Цвет ActionBar.''' 

    color_body_program = ListProperty( 
        [0.15294117647058825, 0.0392156862745098, 0.11764705882352941, 1] 
    ) 
    '''Цвет фона экранов программы.''' 

    color_text_action_item = StringProperty('') 
    '''Цвет текста для кнопок ActionItem в ActionBar.''' 

    title_previous = StringProperty('') 
    '''Заголоок ActionBar.''' 

    text_search_shop = StringProperty('') 
    '''Текст подсказки в поле ввода поиска магазинов.''' 

    text_choice_shop = StringProperty('') 
    '''Текст списка магазинов.''' 

    banner = StringProperty('') 
    '''Текст над рекламным баннером.''' 

    Builder.load_file('{}/kv/startscreen.kv'.format(root)) 

    def __init__(self, **kvargs): 
        super(StartScreen, self).__init__(**kvargs) 

        self.screen_manager = self.ids.screen_manager 
        self.banner_manager = self.ids.banner_manager 
        self.icons_list_manager = self.ids.icons_list_manager 
        self.action_view = self.ids.action_view 
        self.action_previous = self.ids.action_previous 
        self.text_input_search = self.ids.text_input_search 
        self.label_list_icons = self.ids.label_list_icons 

        for name_item_spinner in [ 
            self.core.string_lang_settings, self.core.string_lang_plugin, 
            self.core.string_lang_license, self.core.string_lang_exit_key]: 

            item_button = MyOwnActionButton( 
                text='[color={}]{}'.format( 
                    self.color_text_action_item, name_item_spinner), 
                id=name_item_spinner, on_press=self.events_callback, 
                background_normal='Data/Images/bgd_action_item_normal.png', 
                background_down='Data/Images/bgd_action_item_down.png', 
                on_release=lambda *args: 
                    self.ids.action_overflow._dropdown.select( 
                        self.on_release_select_item_spinner() 
                    ) 
            ) 
            self.ids.action_overflow.add_widget(item_button) 

        screen = Screen(name='shops') 
        scroll_icons = ScrollView( 
            effect_cls=StiffScrollEffect, size_hint_y=None, 
            pos=(Window.size[0], 0), height=150 
        ) 
        box_shops = GridLayout(rows=1, size_hint_x=None) 
        box_shops.bind(minimum_width=box_shops.setter("width")) 

        # Логотипы магазинов. 
        for logo_shops in os.listdir('{}/Data/Images/shops'.format( 
                self.core.prog_path)): 
            box_shops.add_widget( 
                CustomButton( 
                    id=logo_shops.split('.')[0], 
                    on_release=self.events_callback, 
                    icon_button='Data/Images/shops/{}'.format(logo_shops) 
                ) 
            ) 

        scroll_icons.add_widget(box_shops) 
        screen.add_widget(scroll_icons) 

        self.icons_list_manager.add_widget(screen) 
        self.icons_list_manager.current = 'shops' 
        self.animation_scroll_icons(scroll_icons) 

    def animation_scroll_icons(self, instance_scroll_icons): 
        '''Анимация скролла иконок с логотипами магазинов.''' 

        animate = Animation(x=0) 
        animate += Animation(x=0) 
        animate.start(instance_scroll_icons) 

    def on_release_select_item_spinner(self): 
        '''Вешается на release событие кнопок спиннера ActionBar. 
        В противноном случае, список не будет автоматически скрываться. 

        ''' 

        pass

startscreen.kv
#: kivy 1.9.1 

<StartScreen> 
    orientation: 'vertical' 
    canvas: 
        Color: 
            rgb: root.color_body_program 
        Rectangle: 
            pos: self.pos 
            size: self.size 
    ActionBar: 
        id: action_bar 
        canvas: 
            Color: 
                rgb: root.color_action_bar 
            Rectangle: 
                pos: self.pos 
                size: self.size 
        ActionView: 
            id: action_view 
            ActionPrevious: 
                id: action_previous 
                app_icon: 'Data/Images/none.png' 
                title: root.title_previous 
                previous_image: 'Data/Images/logo.png' 
                with_previous: True 
                on_release: root.events_callback('previous') 
            ActionOverflow: 
                id: action_overflow 
                overflow_image: 'Data/Images/overflow.png' 

    ScreenManager: 
        id: screen_manager 
        size_hint: 1, 8 
        Screen: 
            BoxLayout: 
                orientation: 'vertical' 
                BoxLayout: 
                    spacing: 5 
                    padding: 5 
                    size_hint: 1, .15 
                    TextInput: 
                        id: text_input_search 
                        hint_text: root.text_search_shop 
                        background_normal: 'Data/Images/bgd_text_input.png' 
                        background_active: 'Data/Images/bgd_text_input.png' 
                        size_hint: .85, 1 
                        multiline: False 
                    ImageButton: 
                        source: 'Data/Images/search_button.png' 
                        size_hint: .15, 1 
                        on_release: root.events_callback('search_shop') 
                Label: 
                    id: label_list_icons 
                    text: root.text_choice_shop 
                    bold: True 
                    size_hint: 1, .15 
                    font_size: dp(25) 

                ScreenManager: 
                    id: icons_list_manager 
                    size_hint: 1, .6 

                Label: 
                    text: root.banner 
                    bold: True 
                    size_hint: 1, .15 
                    font_size: dp(25) 

                ScreenManager: 
                    id: banner_manager 
                    Screen: 
                        BoxLayout: 
                            padding: 5 
                            ImageButton: 
                                source: 'Data/Images/banners/obi_banner.png' 
                                on_release: root.events_callback('obi_banner')

Базовый класс Program:


program.py
import os
import sys

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.screenmanager import (
    Screen, SlideTransition, SwapTransition, FadeTransition, WipeTransition,
    FallOutTransition, RiseInTransition
)
from kivy.core.window import Window
from kivy.config import ConfigParser
from kivy.clock import Clock
from kivy.properties import ObjectProperty, NumericProperty
from kivy.uix.settings import SettingsWithSidebar

from Libs.uix.kdialog import KDialog, BDialog
from Libs.uix.startscreen import StartScreen
from Libs.uix.customsettings import CustomSettings
from Libs.uix.custombutton import CustomButton

from Libs.uix.garden.stiffscroll import StiffScrollEffect
from Libs.uix.garden.moretransitions import (
    PixelTransition, RippleTransition, BlurTransition, RotateTransition
)

# Классы программы.
from Libs import programclass as prog_class

from Libs import programdata as core
from Libs.manifest import Manifest

__version__ = '0.0.1'

class Program(App, prog_class.ShowPlugin, prog_class.ShowBanners,
              prog_class.SearchShop):
    '''Функционал программы.'''

    start_screen = ObjectProperty(None)
    ''':attr:`start_screen` is a :class:`~Libs.uix.startscreen.StartScreen`'''

    screen = ObjectProperty(None)
    ''':attr:`screen` is a :class:`~Libs.uix.startscreen.StartScreen`'''

    window_text_size = NumericProperty(15)

    def __init__(self, **kvargs):
        super(Program, self).__init__(**kvargs)
        Window.bind(on_keyboard=self.events_program)

        # Для области видимомти в programclass.
        self.Screen = Screen
        self.Clock = Clock
        self.effects_transition = (
            SlideTransition, SwapTransition, FadeTransition, WipeTransition,
            FallOutTransition, RiseInTransition, RippleTransition,
            PixelTransition, BlurTransition, RotateTransition
        )
        # Список магазинов.
        self.shops = [shop.split('.')[0].lower() for shop in os.listdir(
            '{}/Data/Images/shops'.format(core.prog_path))]
        # Список лкоаций дома.
        self.locations = [
            location.split('.')[0].lower() for location in os.listdir(
                '{}/Data/Images/locations'.format(core.prog_path))
        ]
        # ----------------------------------
        self.KDialog = KDialog
        self.BDialog = BDialog
        # ----------------------------------
        self.Manifest = Manifest
        self.core = core
        self.name_program = core.string_lang_title
        # ----------------------------------
        self.shop = False  # выбранный магазин
        self.open_dialog = False  # открыто диалоговое окно

    def build_config(self, config):
        config.adddefaultsection('General')
        config.setdefault('General', 'language', 'Русский')
        config.setdefault('General', 'hint', '1')

        config.adddefaultsection('Theme')
        config.setdefault('Theme', 'theme', 'default')
        config.setdefault('Theme', 'edit_theme', '0')
        config.setdefault('Theme', 'create_theme', '0')

    def build_settings(self, settings):
        CustomSettings(
            background_sections=core.color_action_bar,
            background_color=core.color_body_program,
            background_color_title=core.color_action_bar,
            button_close_background_down='Data/Images/shadows/shadow_btn.png',
            color_text_title=core.separator_color, settings_obj=settings
        )

        general_settings = open('{}/Data/Settings/general.json'.format(
            core.prog_path)).read()
        general_data = general_settings.format(
            language=core.string_lang_setting_language,
            title=core.string_lang_setting_language_title,
            desc=core.string_lang_setting_language_desc,
            russian=core.string_lang_setting_language_russian,
            english=core.string_lang_setting_language_english)
        settings.add_json_panel(
            core.string_lang_general, self.config, data=general_data
        )

    def build(self):
        self.settings_cls = SettingsWithSidebar
        self.title = self.name_program  # заголовок окна программы
        self.icon = 'Data/Images/logo.png'  # иконка окна программы
        self.use_kivy_settings = False

        self.config = ConfigParser()
        self.config.read('{}/program.ini'.format(core.prog_path))
        self.set_var_from_file_settings()

        # Главный экран программы.
        self.start_screen = StartScreen(
            color_action_bar=core.color_action_bar,
            color_body_program=core.color_body_program,
            color_text_action_item=core.theme_text_color_action_item,
            text_search_shop=core.string_lang_search_shop,
            text_choice_shop=core.string_lang_choice_shop,
            events_callback=self.events_program,
            core=core, banner=core.string_lang_banner
        )
        self.screen = self.start_screen

        if self.hint:
            Clock.schedule_once(
                lambda *args: self.show_hints(
                    core.string_lang_check_shops,
                    core.string_lang_check_shops_label), 1
            )
        Clock.schedule_interval(self.show_banners, 1)

        return self.start_screen

    def set_var_from_file_settings(self):
        '''Установка значений переменных из файла настроек program.ini.'''

        self.language = core.select_locale[
            self.config.get('General', 'language')
        ]
        self.hint = self.config.getint('General', 'hint')

    def show_hints(self, hint_text, check_text):
        '''Выводит окно подсказок.'''

        def answer_callback(answer):
            hint = int(answer[1])
            if hint:
                self.config.set('General', 'hint', '0')
                self.config.write()
                self.hint = False

        KDialog(title=self.name_program, answer_callback=answer_callback,
                separator_color=self.core.separator_color).show(
            text=hint_text, check_text=check_text, auto_dismiss=True,
            text_button_ok=core.string_lang_next, check=True, param='query'
        )

    def events_program(self, *args):
        '''Обработка событий программы.'''

        if len(args) == 2:  # нажата ссылка
            event = args[1].encode('utf-8')
        else:  # нажата кнопка программы
            try:
                _args = args[0]
                event = _args if isinstance(_args, str) else _args.id
            except AttributeError:  # нажата кнопка девайса
                event = args[1]

        if event == core.string_lang_settings:
            self.open_settings()
        elif event == core.string_lang_exit_key:
            self.exit_program()
        elif event == core.string_lang_plugin:
            self.show_plugins()
        elif event in self.locations:
            print(event)
        elif event == 'search_shop':
            self.search_shop()
        elif event == 'obi_banner':
            self.press_banner(event)
        elif event == 'previous' or event in (1001, 27):
            self.back_screen(event)
        elif event in self.shops:
            self.set_list_icons_locations(event)
        return True

    def back_screen(self, event):
        '''Менеджер экранов.'''

        # Возврат из списка локаций.
        if self.screen.screen_manager.current == '' and self.shop:
            self.screen.icons_list_manager.current = 'shops'
            self.screen.action_previous.title = self.name_program
            self.screen.label_list_icons.text = core.string_lang_choice_shop
            self.screen.action_previous.previous_image = 'Data/Images/logo.png'
            self.screen.action_previous.app_icon = 'Data/Images/none.png'
            self.shop = None
            return
        # Нажата BackKey на главном экране.
        if self.screen.screen_manager.current == '':
            if event in (1001, 27):
                self.exit_program()
            return
        if len(self.screen.screen_manager.screens) != 1:
            self.screen.screen_manager.screens.pop()
        self.screen.screen_manager.current =             self.screen.screen_manager.screen_names[-1]
        # Устанавливаем имя предыдущего экрана.
        self.screen.action_previous.title = self.screen.screen_manager.current

    def set_list_icons_locations(self, name_select_icon):
        '''Меняет логотипы магазинов на главном экране на иконки локаций
        помещения при выборе одного из магазина.'''

        self.shop = name_select_icon

        screen = Screen(name='locations')
        scroll_icons = ScrollView(
            effect_cls=StiffScrollEffect, size_hint_y=None, pos=(0, 0),
            height=150
        )
        box_locations = GridLayout(rows=1, size_hint_x=None)
        box_locations.bind(minimum_width=box_locations.setter("width"))

        # Логотипы локаций.
        for logo_location in os.listdir('{}/Data/Images/locations'.format(
                core.prog_path)):
            box_locations.add_widget(
                CustomButton(
                    id=logo_location.split('.')[0],
                    on_release=self.events_program,
                    icon_button='Data/Images/locations/{}'.format(
                        logo_location
                    )
                )
            )

        self.screen.label_list_icons.text = core.string_lang_choice_location
        self.screen.action_previous.previous_image =             'atlas://data/images/defaulttheme/previous_normal'
        self.screen.action_previous.app_icon =             'Data/Images/shops/{}.png'.format(self.shop)

        if self.hint:
            self.show_hints(
                core.string_lang_check_locations,
                core.string_lang_check_shops_label
            )

        scroll_icons.add_widget(box_locations)
        screen.add_widget(scroll_icons)
        self.screen.icons_list_manager.add_widget(screen)
        self.screen.icons_list_manager.transition = SwapTransition()
        self.screen.icons_list_manager.current = 'locations'
        self.screen.action_previous.title = core.string_name_shop.format(
            self.shop.capitalize()
        )
        self.screen.icons_list_manager.screens.pop()

    def show_about(self):
        def events_callback(instance_label, text_link):
            def answer_callback(answer):
                pass

            pass

        ADialog(events_callback=events_callback,
                name_program=self.name_program)

    def exit_program(self, *args):
        def dismiss(*args):
            self.open_dialog = False

        def answer_callback(answer):
            if answer == core.string_lang_yes:
                sys.exit(0)
            dismiss()

        if not self.open_dialog:
            KDialog(answer_callback=answer_callback, on_dismiss=dismiss,
                    separator_color=core.separator_color,
                    title=self.name_program).show(
                text=core.string_lang_exit,
                text_button_ok=core.string_lang_yes,
                text_button_no=core.string_lang_no, param='query',
                auto_dismiss=True
            )
            self.open_dialog = True

    def on_config_change(self, config, section, key, value):
        '''Вызывается при выборе одного из пункта настроек программы.'''

        def select_callback(*args):
            pass

        if key == 'language':
            # self.self.language = value
            print(value)

    def on_pause(self):
        '''Ставит приложение на 'паузу' при выхоже из него.
        В противном случае запускает программу по заново'''

        return True

    def on_resume(self):
        print('on_resume')

    def on_stop(self):
        print('on_stop')

Оставляем за бортом (не рассмотренными) файл локализации — Data/Language/russian.txt, расфасовку графических ресурсов и запускаем тестовый пример:


python3 main.py


Немного не то, что было заявлено в начале статьи. Открываем файл Data/Themes/default/default.ini и правим тему:


[color]
color_action_bar = [0.4, 0.11764705882352941, 0.2901960784313726, 0.5607843137254902]
color_body_program = [0.15294117647058825, 0.0392156862745098, 0.11764705882352941, 1]
separator_color = [1.0, 1.0, 1.0, 1.0]
text_color = #ffffffff
text_color_action_item = #00000000
key_text_color = #661e4a8f
link_color = #2fbfe0ff

Сохраняем и запускаем повторно:



Видео обзор примера:



Репозиторий на github


P.S.
Возможно некорректное отображение некоторых окон, созданных с помощью библиотеки kdialog — находится в разработке.


P.S.S
Спасибо за внимание!

Поделиться с друзьями
-->

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


  1. braska
    16.07.2016 02:53
    +3

    Аааа, мои глаза. Я вообще не питонист и под мобилки не пишу. Но искренне не понимаю чем руководствуются люди, разрабатывая под Android, и не следуя официальным гайдам.


  1. Zifix
    16.07.2016 06:40
    +4

    Вполне неплохо для начала нулевых, а у нас тут в 2016 уже два года как Material Design завезли!


    1. HeaTTheatR
      16.07.2016 08:28
      -3

      То есть, давайте сделаем 1001-ое приложение на Android, которые отличается от других только цветовой палитрой и размером кнопок?


      1. Zifix
        16.07.2016 08:31
        +2

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


        1. HeaTTheatR
          16.07.2016 09:05
          -5

          Возможно для вас это будет будет дикостью, но еще со времен старой доброй Symbian я всегда уходил от всего "родного" для ОС. И, как пользователь Android, меня всегда раздражают однотипные приложения. Такое ощущение, что у авторов напрочь отсутствует фантазия. А оказывается, что они просто придерживаются единого стиля, "родного" для ОС. Это мое мнение.


          1. Zifix
            16.07.2016 09:13
            +1

            Пользователи простили бы вам неродной стиль, будь он красивым, но увы, ваши рамки устарели лет на 15, а градиенты на 10 :/


            1. HeaTTheatR
              16.07.2016 09:20
              -1

              Так это статья и не пособие по дизайну. Ключевое слово здесь — Kivy. К тому же это — "один из черновиков домашней страницы тестового приложения". Ключевое слово здесь — "черновиков".


          1. bigfatbrowncat
            16.07.2016 10:31

            Я вам не как разработчик под Андроид с четырехлетним стажем сейчас скажу, а как простой пользователь.

            Если бы я увидел скриншот вашего приложения на Play Market, то пожалел бы о потраченных на загрузку страницы двух секундах.

            Я стал бы пользоваться приложением с таким оформлением только если бы это было совершенно необходимо для меня (например, магазин с уникальным товаром, который больше нигде не найдешь). И всё равно удалил бы при первой возможности. Потому что это ужас. Я даже в школе на Delphi под Windows 95 старался оформлять лучше. Простите, если резок.


            1. HeaTTheatR
              16.07.2016 10:42
              -1

              Читайте комментарий выше.