📜 ⬆️ ⬇️

Kivy. From creation to production one step. Part 1


Literally an article ago, by a majority of votes, it was decided to begin a series of lessons on creating an analogue of a native application written for Android in Java, but using the Kivy + Python framework. It will be considered: the creation and layout of controllers and widgets, an in-depth study of the Kv-Language UI markup technique, dynamic control of screen elements, a library that provides access to Android Material Design, and much more ...


Interested, please under the cat!


So, after an unsuccessful search of a guinea pig for a suitable application, to the best of complexity (so as not to stretch our tutorial to the scale of Santa Barbara) and not too simple (in order to illuminate as much as possible the technical aspects of Kivy development), according to the habrovich Roman Hvashchevsky , who agreed to speak Java as a consultant to our lessons (sometimes in the articles I will give the listings of the original code written in Java), I was redirected here - and the choice was made:



Conversations is an instant messaging application for Android using the XMPP / Jabber protocol. An alternative to programs such as WhatsApp, WeChat, Line, Facebook Messenger, Google Hangouts and Threema.

It is on the basis of this application that our lessons will be built, and closer to the release by the end of the final article we will have our own reptile, fruit-growing tonton of python, toad and fruit Jabber-Python-Kivy - PyConversations and the cherished apk-shechka collected from Python3!


I hope you stocked up with tea and cigarettes, because we are starting! As always, you need, if you have not yet acquired, the Wizard to create a new project for Kivy applications. Clone it in your labs, open the root directory of the wizard in the terminal and execute the command:


python3 main.py PyConversations ///// -repo https://github.com/User/PyConversations -autor Easy -mail gorodage@gmail.com 

Naturally, the Kivy framework itself, which you can read about installing here . Well, and of course, you already found the wonderful KivyMD library for creating a native interface in the style of Android Material Design by clicking the link in the repository of the New Project Wizard.


Now go to PornHub github and forknite / clone / download the PyConversations turnips, because the project that we have started is not small, and as new articles are released, it will become overgrown with new functions, classes and files. Otherwise, already in the second article you will smoke bamboo to wonder why nothing works for you.


So, the project is created:



For today's article, I took the first four Activities of the official Conversations application (Activity of new account registration), which we will now create with you:



However, before you begin, so that we understand each other, you should familiarize yourself with the basic rules and concepts ...


Creating and managing dynamic classes


Basic presentation of a dynamic class using a simple example:
 from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder from kivy.properties import StringProperty Builder.load_string(''' #: import MDFlatButton kivymd.button.MDFlatButton #    Kivy-Language    python : # from kivymd.button import MDFlatButton # #  kv-      , #  , ,  : #: include your_kv_file.kv # #    ,  Kivy  , #     Activity —   . #    Activity    BoxLayout - # ,     . <StartScreen> MDFlatButton: id: button text: 'Press Me' size_hint_x: 1 #    -  0  1 pos_hint: {'y': .5} #     'y'   #  . on_release: #   'root' -     , #            . root.set_text_on_button() ''') #  Builder.load_file('path/to/kv-file'), #   Activity   . class StartScreen(BoxLayout): ''' .''' new_text_for_button = StringProperty() #  Kivy      : # # StringProperty; # NumericProperty; # BoundedNumericProperty; # ObjectProperty; # DictProperty; # ListProperty; # OptionProperty; # AliasProperty; # BooleanProperty; # ReferenceListProperty; # #       #     . # # ,    : # # new_text_for_button = '' # #    - # TypeError: object.__init__() takes no parameters. def set_text_on_button(self): self.ids.button.text = self.new_text_for_button # ids -     Activity #   . # # ,    'button' - self.ids.button - #   ,    #      . #  ,   Properties, #         'on_', #          . def on_new_text_for_button(self, instance, value): print(instance, value) class Program(App): def build(self): ''',    .     Activity.''' return StartScreen(new_text_for_button='This new text') if __name__ in ('__main__', '__android__'): Program().run() #   

We refer to our own attributes and methods inside the Activity:
 from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder from kivy.properties import StringProperty Builder.load_string(''' #: import MDFlatButton kivymd.button.MDFlatButton <StartScreen> MDFlatButton: id: button text: 'Press Me' size_hint_x: 1 pos_hint: {'y': .5} on_release: #    'self'    #       . self.text = root.new_text_for_button ''') class StartScreen(BoxLayout): new_text_for_button = StringProperty() def on_new_text_for_button(self, instance, value): print(instance, value) class Program(App): def build(self): return StartScreen(new_text_for_button='This new text') if __name__ in ('__main__', '__android__'): Program().run() 

Using id controllers and widgets inside the Activity:
 from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder from kivy.properties import StringProperty Builder.load_string(''' #: import MDFlatButton kivymd.button.MDFlatButton <StartScreen> orientation: 'vertical' MDFlatButton: id: button text: 'Press Me' size_hint: 1, 1 pos_hint: {'center_y': .5} on_release: #    id      . #  ,        Python #  ,     Python . button_two.text = 'Id: "button_two: " {}'.format(root.new_text_for_button) MDFlatButton: id: button_two text: 'Id: "button_two: " Old text' size_hint: 1, 1 pos_hint: {'center_y': .5} ''') class StartScreen(BoxLayout): new_text_for_button = StringProperty() class Program(App): def build(self): return StartScreen(new_text_for_button='This new text') if __name__ in ('__main__', '__android__'): Program().run() 

Using methods with the 'on_' prefix inside the Activity:
 from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder from kivy.properties import StringProperty Builder.load_string(''' #: import MDFlatButton kivymd.button.MDFlatButton #: import snackbar kivymd.snackbar <StartScreen> orientation: 'vertical' MDFlatButton: id: button text: 'Press Me' size_hint: 1, 1 pos_hint: {'center_y': .5} on_release: button_two.text = 'Id: "button_two: " {}'.format(root.new_text_for_button) MDFlatButton: id: button_two text: 'Id: "button_two: " Old text' size_hint: 1, 1 pos_hint: {'center_y': .5} on_text: #      'text'. snackbar.make(', !     !') ''') class StartScreen(BoxLayout): new_text_for_button = StringProperty() class Program(App): def build(self): return StartScreen(new_text_for_button='This new text') if __name__ in ('__main__', '__android__'): Program().run() 

Using attributes and methods from the main class of the application inside the Activity:
 from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder from kivy.properties import StringProperty Builder.load_string(''' #: import MDFlatButton kivymd.button.MDFlatButton <StartScreen> MDFlatButton: #    'app' —   - #      , #     , #   kivy.app.App. text: app.string_attribute size_hint_x: 1 pos_hint: {'y': .5} ''') class StartScreen(BoxLayout): pass class Program(App): string_attribute = StringProperty('String from App') def build(self): return StartScreen() if __name__ in ('__main__', '__android__'): Program().run() 

Using Activity without root class:
 from kivy.app import App from kivy.lang import Builder Activity = ''' <MyScreen@FloatLayout>: Label: text: 'Text 1' BoxLayout: MyScreen: ''' class Program(App): def build(self): return Builder.load_string(Activity) if __name__ in ('__main__', '__android__'): Program().run() 

Using ids in an Activity without a root class:
 from kivy.app import App from kivy.lang import Builder Activity = ''' #: import MDFlatButton kivymd.button.MDFlatButton #  ,      , #   ,  .    - FloatLayout. <MyScreen@FloatLayout>: Label: id: label_1 text: 'Text 1' BoxLayout: orientation: 'vertical' MyScreen: id: my_screen MDFlatButton: text: 'Press me' size_hint_x: 1 on_press: my_screen.ids.label_1.text = 'New text' ''' class Program(App): def build(self): return Builder.load_string(Activity) if __name__ in ('__main__', '__android__'): Program().run() 

To understand what I’m going to talk about next, this is still enough, the rest will be explained in a trench along the way. Well, let's start with the starting Activity of our project. Open the start_screen.kv file. In the project tree, it, like all other Activity applications, is located in the libs / uix / kv / activity directory:



And the activity looks like this:


 #: kivy 1.9.1 #: import Toolbar kivymd.toolbar.Toolbar #: import NoTransition kivy.uix.screenmanager.NoTransition <StartScreen>: orientation: 'vertical' Toolbar: id: action_bar background_color: app.theme_cls.primary_color #    title: app.title opposite_colors: True #     elevation: 10 #   #   - # left_action_items: [['name-icon', function], …] #   - # right_action_items: [['name-icon', function], …] ScreenManager: id: root_manager transition: NoTransition() #   Activity Introduction: id: introduction #     Activity  . on_enter: self._on_enter(action_bar, app) CreateAccount: id: create_account on_enter: self._on_enter(action_bar, app, root_manager) AddAccount: id: add_account on_enter: self._on_enter(action_bar, app) #     Activity. on_leave: action_bar.title = app.data.string_lang_create_account AddAccountOwn: id: add_account_own_provider on_enter: self._on_enter(action_bar, app, root_manager) on_leave: action_bar.title = app.title; action_bar.left_action_items = [] 

But more clearly:



Now open the base class Activity StartScreen, which is located on the path libs / uix / kv / activity / baseclass :



startscreen.py:


 from kivy.uix.boxlayout import BoxLayout class StartScreen(BoxLayout): pass 

As you can see, the class is empty, but inherited from the BoxLayout container, which places widgets vertically or horizontally depending on the 'orientation' parameter - 'vertical' or 'horizontal' (by default - 'horizontal'). Here is a more detailed Activity StartScreen diagram:



The base class of Activity StartScreen, we inherited from BoxLayout, in the markup itself declared its orientation as an unconventional vertical, and placed in its ToolBar container and ScreenManager screen manager. ScreenManager is also a kind of container in which we place Screen screens with created Activity and then install them on the screen just by calling them by name. For example:


 from kivy.app import App from kivy.lang import Builder Activity = ''' #: import MDFlatButton kivymd.button.MDFlatButton ScreenManager: Screen: name: 'Screen one' #   MDFlatButton: text: 'I`m Screen one with Button' size_hint: 1, 1 on_release: root.current = 'Screen two' #   Screen: name: 'Screen two' BoxLayout: orientation: 'vertical' Image: source: 'data/logo/kivy-icon-128.png' MDFlatButton: text: 'I`m Screen two with Button' size_hint: 1, 1 on_release: root.current = 'Screen one' ''' class Program(App): def build(self): return Builder.load_string(Activity) if __name__ in ('__main__', '__android__'): Program().run() 

Our ScreenManager contains four screens with Activity: Introduction, CreateAccount, AddAccount and AddAccountOwn. Let's start with the first:



Introduction.kv
 #: kivy 1.9.1 #: import MDFlatButton kivymd.button.MDFlatButton #  Activity . <Introduction>: name: 'Start screen' BoxLayout: orientation: 'vertical' padding: dp(5), dp(20) Image: source: 'data/images/logo.png' size_hint: None, None size: dp(150), dp(150) pos_hint: {'center_x': .5} Label: text: app.data.string_lang_introduction markup: True color: app.data.text_color text_size: dp(self.size[0] - 10), self.size[1] size_hint_y: None valign: 'top' height: dp(250) Widget: BoxLayout: MDFlatButton: text: app.data.string_lang_create_account on_release: app.screen_root_manager.current = 'Create account' MDFlatButton: text: app.data.string_lang_own_provider theme_text_color: 'Primary' on_release: app.delete_textfield_and_set_check_in_addaccountroot () app.screen_root_manager.current = 'Add account own provider' 

This is what this Activity represents on the device screen (I allowed myself some liberties, but, it seemed to me, it would be better):



Here is the original Java:



Original Activity Markup in Java
 <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="?attr/color_background_primary"> <LinearLayout android:id="@+id/linearLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:minHeight="256dp" android:orientation="vertical" android:paddingBottom="10dp" android:paddingLeft="16dp" android:paddingRight="16dp"> <Space android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/welcome_header" android:textColor="?attr/color_text_primary" android:textSize="?attr/TextSizeHeadline" android:textStyle="bold"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/welcome_text" android:textColor="?attr/color_text_primary" android:textSize="?attr/TextSizeBody"/> <Button android:id="@+id/create_account" style="?android:attr/borderlessButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="@string/create_account" android:textColor="@color/accent"/> <Button android:id="@+id/use_own_provider" style="?android:attr/borderlessButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="@string/use_own_provider" android:textColor="?attr/color_text_secondary"/> </LinearLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/linearLayout" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentTop="true"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:padding="8dp" android:src="@drawable/main_logo"/> </RelativeLayout> <TextView android:paddingLeft="8dp" android:paddingRight="8dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:textColor="?attr/color_text_secondary" android:textSize="@dimen/fineprint_size" android:maxLines="1" android:text="@string/free_for_six_month" android:layout_centerHorizontal="true"/> </RelativeLayout> </ScrollView> 

The following is the Activity Introduction diagram:



Now I would like to go through the attributes of the widgets:


 BoxLayout: … padding: dp(5), dp(20) #      — /  / 

 Image: … #     ,  -  #    0  1 (.1, .5, .01  . .).    #   ,     size_hint #   None,     . # ,   : # # size_hint_x: None # width: 250 # #   # # size_hint_y: None # height: 50 # # ,    Activity,     . #    size_hint   (1, 1), #  ,       . size_hint: None, None size: dp(150), dp(150) #        'x' #   '' ,     # pos, , pos: 120, 90. pos_hint: {'center_x': .5} 

With relative positions and sizes of the widget, you can experiment with the example below:


 from kivy.app import App from kivy.lang import Builder Activity = ''' FloatLayout: Button: text: "We Will" pos: 100, 100 size_hint: .2, .4 Button: text: "Wee Wiill" pos: 280, 200 size_hint: .4, .2 Button: text: "ROCK YOU!!" pos_hint: {'x': .3, 'y': .6} size_hint: .5, .2 ''' class Program(App): def build(self): return Builder.load_string(Activity) if __name__ in ('__main__', '__android__'): Program().run() 

Further by attributes:


 Label: … # ,   markdown    #   as is. #   : # [b][/b] # [i][/i] # [u][/u] # [s][/s] # [font=<str>][/font] # [size=<integer>][/size] # [color=#<color>][/color] # [ref=<str>][/ref] # [anchor=<str>] # [sub][/sub] # [sup][/sup] markup: True # ,  . text_size: dp(self.size[0] - 10), self.size[1] #   : # 'bottom', 'middle', 'center'  'top'. valign: 'top' 

You can experiment with the text enclosing area with the example below:


 from kivy.app import App from kivy.uix.label import Label class LabelTextSizeTest(App): def build(self): return Label( text=' ,  \n' * 50, text_size=(250, 300), #     line_height=1.5 ) if __name__ == '__main__': LabelTextSizeTest().run() 

Further on Activity:


 Widget: 

In context it is used as an analog in Java:


 <Space android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> 

Further:


 BoxLayout: MDFlatButton: text: app.data.string_lang_create_account #  Activity   'Create account'. on_release: app.screen_root_manager.current = 'Create account' MDFlatButton: text: app.data.string_lang_own_provider #        #   theme_text_color  'Custom' #     - text_color: .7, .2, .2, 1 theme_text_color: 'Primary' on_release: #      . #     , ,  #  ,       #    Activity,    . app.delete_textfield_and_set_check_in_addaccountroot() app.screen_root_manager.current = 'Add account own provider' 

So. We still have not considered one more question. Let's go back to the Activity StartScreen markup:


  Introduction: id: introduction #     Activity  . on_enter: self._on_enter(action_bar, app) 

That is, as soon as the Activity is displayed, the on_enter event code will be executed. Let's see what the _on_enter method does in the base Activity class (file libs / uix / kv / activity / baseclass / introduction.py ):



 from kivy.uix.screenmanager import Screen class Introduction(Screen): def _on_enter(self, instance_toolbar, instance_program): instance_toolbar.left_action_items = [] instance_toolbar.title = instance_program.title 

The _on_enter method removes the icon in the ToolBar on the left, setting the value of left_action_items as an empty list, and changes the ToolBar signature to the application name.


For example, here is the control class from the original Java:


WelcomeActivity
 package eu.siacs.conversations.ui; import android.app.ActionBar; import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; import android.view.View; import android.widget.Button; import eu.siacs.conversations.R; public class WelcomeActivity extends Activity { @Override protected void onCreate(final Bundle savedInstanceState) { if (getResources().getBoolean(R.bool.portrait_only)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } final ActionBar ab = getActionBar(); if (ab != null) { ab.setDisplayShowHomeEnabled(false); ab.setDisplayHomeAsUpEnabled(false); } super.onCreate(savedInstanceState); setContentView(R.layout.welcome); final Button createAccount = (Button) findViewById(R.id.create_account); createAccount.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(WelcomeActivity.this, MagicCreateActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivity(intent); } }); final Button useOwnProvider = (Button) findViewById(R.id.use_own_provider); useOwnProvider.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(WelcomeActivity.this, EditAccountActivity.class)); } }); } } 

So. With this sorted out. We have an Activity and two usable buttons. Let's start with the first:



When you click on the button, Activity CreateAccount will be displayed:


 MDFlatButton: text: app.data.string_lang_create_account on_release: app.screen_root_manager.current = 'Create account' 

Activity CreateAccount (Kivy):



Activity CreateAccount (original):



Let's open the Activity CreateAccount of our project:



createaccount.kv
 #: kivy 1.9.1 #: import SingleLineTextField kivymd.textfields.SingleLineTextField #: import snackbar kivymd.snackbar # Activity   . #     'Create account'  Activity. <CreateAccount>: name: 'Create account' BoxLayout: orientation: 'vertical' padding: dp(5), dp(20) Image: source: 'data/images/logo.png' size_hint: None, None size: dp(150), dp(150) pos_hint: {'center_x': .5} Label: text: app.data.string_lang_enter_user_name markup: True color: app.data.text_color text_size: dp(self.size[0] - 10), self.size[1] size_hint_y: None valign: 'top' height: dp(215) Widget: size_hint_y: None height: dp(10) SingleLineTextField: id: username hint_text: 'Username' message: 'username@conversations.im' message_mode: 'persistent' on_text: app.check_len_login_in_textfield(self) Widget: BoxLayout: MDFlatButton: text: app.data.string_lang_next on_release: if username.text == '' or username.text.isspace(): \ snackbar.make(app.data.string_lang_not_valid_username) else: app.screen_root_manager.current = 'Add account' 

There is nothing new for you here, in the diagram below I will only give what we have not yet discussed:



The header and icon in the ToolBar are set in the base class Activity CreateAccount in the _on_enter method:


 from kivy.uix.screenmanager import Screen class CreateAccount(Screen): def _on_enter(self, instance_toolbar, instance_program, instance_screenmanager): instance_toolbar.title = instance_program.data.string_lang_create_account instance_toolbar.left_action_items = [ ['chevron-left', lambda x: instance_program.back_screen( instance_screenmanager.previous())] ] 

Java's original control class MagicCreateActivity
 package eu.siacs.conversations.ui; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import java.security.SecureRandom; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; public class MagicCreateActivity extends XmppActivity implements TextWatcher { private TextView mFullJidDisplay; private EditText mUsername; private SecureRandom mRandom; private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456780+-/#$!?"; private static final int PW_LENGTH = 10; @Override protected void refreshUiReal() { } @Override void onBackendConnected() { } @Override protected void onCreate(final Bundle savedInstanceState) { if (getResources().getBoolean(R.bool.portrait_only)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } super.onCreate(savedInstanceState); setContentView(R.layout.magic_create); mFullJidDisplay = (TextView) findViewById(R.id.full_jid); mUsername = (EditText) findViewById(R.id.username); mRandom = new SecureRandom(); Button next = (Button) findViewById(R.id.create_account); next.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String username = mUsername.getText().toString(); if (username.contains("@") || username.length() < 3) { mUsername.setError(getString(R.string.invalid_username)); mUsername.requestFocus(); } else { mUsername.setError(null); try { Jid jid = Jid.fromParts(username.toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null); Account account = xmppConnectionService.findAccountByJid(jid); if (account == null) { account = new Account(jid, createPassword()); account.setOption(Account.OPTION_REGISTER, true); account.setOption(Account.OPTION_DISABLED, true); account.setOption(Account.OPTION_MAGIC_CREATE, true); xmppConnectionService.createAccount(account); } Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); intent.putExtra("init", true); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show(); startActivity(intent); } catch (InvalidJidException e) { mUsername.setError(getString(R.string.invalid_username)); mUsername.requestFocus(); } } } }); mUsername.addTextChangedListener(this); } private String createPassword() { StringBuilder builder = new StringBuilder(PW_LENGTH); for(int i = 0; i < PW_LENGTH; ++i) { builder.append(CHARS.charAt(mRandom.nextInt(CHARS.length() - 1))); } return builder.toString(); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (s.toString().trim().length() > 0) { try { mFullJidDisplay.setVisibility(View.VISIBLE); Jid jid = Jid.fromParts(s.toString().toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null); mFullJidDisplay.setText(getString(R.string.your_full_jid_will_be, jid.toString())); } catch (InvalidJidException e) { mFullJidDisplay.setVisibility(View.INVISIBLE); } } else { mFullJidDisplay.setVisibility(View.INVISIBLE); } } } 

... triggered by the on_enter event (when the Activity was displayed):


 <StartScreen>: … ScreenManager: … CreateAccount: on_enter: self._on_enter(action_bar, app, root_manager) … 

We are also interested in the on_text event when the value of the text field changes:


 <CreateAccount>: … SingleLineTextField: … on_text: app.check_len_login_in_textfield(self) 

The check_len_login_in_textfield method from the main application class:



 def check_len_login_in_textfield(self, instance_textfield): #       20 . if len(instance_textfield.text) > 20: instance_textfield.text = instance_textfield.text[:20] #        #      . instance_textfield.message = 'username@conversations.im' \ if instance_textfield.text == '' \ else '{}@conversations.im'.format(instance_textfield.text) 


So, if the text field data is correct, we derive the Activity AddAccount:


 MDFlatButton: … on_release: if … … else: app.screen_root_manager.current = 'Add account' 

Otherwise, display a message about incorrect data:


 MDFlatButton: … on_release: if username.text == '' or username.text.isspace(): \ snackbar.make(app.data.string_lang_not_valid_username) … 


Well, and finally, we have the last Activity ...


Original:



Kivy:



Yes, this is one Activity. From the second, when it is displayed on the screen, we simply programmatically remove the “extra” text field.


 <StartScreen>: … ScreenManager: … AddAccount: id: add_account on_enter: self._on_enter(action_bar, app) on_leave: action_bar.title = app.data.string_lang_create_account AddAccountOwn: id: add_account_own_provider on_enter: self._on_enter(action_bar, app, root_manager) on_leave: action_bar.title = app.title; action_bar.left_action_items = [] 


In the markup files, we created Activity templates:


 <AddAccount>: name: 'Add account' AddAccountRoot: id: add_account_root 

 <AddAccountOwn>: name: 'Add account own provider' AddAccountRoot: id: add_account_root 

«» Activity AddAccountRoot:



Activity AddAccountRoot
 #: kivy 1.9.1 #: import progress libs.uix.dialogs.dialog_progress #: import MDFlatButton kivymd.button.MDFlatButton #: import SingleLineTextField kivymd.textfields.SingleLineTextField #: import MDCheckbox kivymd.selectioncontrols.MDCheckbox # Activity     . <AddAccountRoot@BoxLayout>: canvas: Color: rgba: app.data.background Rectangle: size: self.size pos: self.pos orientation: 'vertical' padding: dp(10), dp(10) BoxLayout: id: box canvas: Color: rgba: app.data.rectangle Rectangle: size: self.size pos: self.pos Color: rgba: app.data.list_color Rectangle: size: self.size[0] - 2, self.size[1] - 2 pos: self.pos[0] + 1, self.pos[1] + 1 orientation: 'vertical' size_hint_y: None padding: dp(10), dp(10) spacing: dp(15) height: app.window.height // 2 SingleLineTextField: id: username hint_text: 'Username' on_text: if self.message != '': app.check_len_login_in_textfield(self) SingleLineTextField: id: password hint_text: 'Password' password: True BoxLayout: id: box_check size_hint_y: None height: dp(40) MDCheckbox: id: check size_hint: None, None size: dp(40), dp(40) active: True on_state: if self.active: box.add_widget(confirm_password) else: box.remove_widget(confirm_password) if username.message != '': confirm_password.hint_text = 'Confirm password' Label: text: 'Register new account on server' valign: 'middle' color: app.data.text_color size_hint_x: .9 text_size: self.size[0] - 10, self.size[1] SingleLineTextField: id: confirm_password password: True Widget: Widget: BoxLayout: padding: dp(0), dp(10) MDFlatButton: text: app.data.string_lang_cancel theme_text_color: 'Primary' on_release: if app.screen.ids.root_manager.current == 'Add account own provider': \ app.screen.ids.root_manager.current = 'Start screen'; \ app.screen.ids.action_bar.title = app.title else: \ app.screen.ids.root_manager.current = 'Create account'; app.screen.ids.action_bar.title = app.data.string_lang_create_account MDFlatButton: text: app.data.string_lang_next on_release: instance_progress, instance_text_wait = \ progress(text_wait=app.data.string_lang_text_wait.format(app.data.text_color_hex), \ events_callback=lambda x: instance_progress.dismiss()) 


Kivy canvas . , : . Activity , , ( , ). .


:


 MDCheckbox: … on_state: # True/False — /  if self.active: box.add_widget(confirm_password) else: box.remove_widget(confirm_password) … 

Activity AddAccount , :


 from kivy.uix.screenmanager import Screen from kivy.clock import Clock class AddAccount(Screen): def _on_enter(self, instance_toolbar, instance_program): instance_toolbar.title = self.name self.ids.add_account_root.ids.username.focus = True #      . Clock.schedule_once(instance_program.set_text_on_textfields, .5) 

:


 def set_focus_on_textfield(self, interval=0, instance_textfield=None, focus=True): if instance_textfield: instance_textfield.focus = focus def set_text_on_textfields(self, interval): add_account_root = self.screen.ids.add_account.ids.add_account_root field_username = add_account_root.ids.username field_password = add_account_root.ids.password field_confirm_password = add_account_root.ids.confirm_password field_username.text = self.screen.ids.create_account.ids.username.text.lower() field_password.focus = True password = self.generate_password() field_password.text = password field_confirm_password.text = password Clock.schedule_once( lambda x: self.set_focus_on_textfield( instance_textfield=field_password, focus=False), .5 ) Clock.schedule_once( lambda x: self.set_focus_on_textfield( instance_textfield=field_username), .5 ) 

! Activity , , . . . , Kivy , , , .


, PyConversations , . See you!


PyConversations github.



')

Source: https://habr.com/ru/post/314236/


All Articles