Animation / Animation Set / Button / Text

Dirty Phrasebook – Part 4

On 1st April 2015 I published a joke app to Google Play named Dirty Phrasebook which is based upon Monty Python’s Dirty Hungarian Phrasebook sketch. In this series of articles we’ll take a look in to the code (which will be open-sourced along with the final article). In this article we’ll look at the custom View that we use to handle user input.

ic_launcherThe decision to create a custom view in this instance was because there are some animations and behaviours which need to be applied to a group of controls, so by using an aggregate custom View we can apply these easily to a ViewGroup – in this case we’re using a CardView from the CardView support library so that we can get a nice shadow on the ViewGroup.

We’ve already taken a look at the layout used for this, so we need to inflate it obtain references to some of the component controls:

There’s a fair bit happening here already. Either constructor will call the initialise() method which is responsible for getting a couple of custom attribute values from the InputView element within the parent layout. One of these is to indicate the distance that we want to move the InputView when we enter Input mode, and the second controls the animation duration. It also creates a GestureListener – more on this later.

onFinishedInflate() gets called once the parent layout inflation has completes, and in here we inflate the layout for this control, obtain references to the component Views, and set a number of different listeners on those Views.

The first of these is FocusChangeListener which is called whenever the EditText control receives or loses focus:

This is simple enough: We just enter or exit input mode depending on the focus state of this control.

Next we have InputCompleteListener which gets called for various actions on the EditText control:

In our case we’re only interested in when the user clicks on the “Done” button in the soft keyboard and we end input mode when this occurs.

Next we have InputChangeListener which gets called whenever the text within the EditText View changes:

The purpose here is to control the visibility of a button which allows the user to clear the input. There’s no point in showing this if the EditText is already empty, so we detect changes in the EditText and change the state of the clear control accordingly.

Next we have DoneListener which gets called when the user clicks a “Done” button which overlays the EditText (many thanks to Sebastiano Poggi for suggesting this):

This provides one of a number of different methods of exiting input mode – providing lots of distinct methods of the user exiting input mode makes discoverability much better!

Next up is the GestureListener that we created earlier:

This provides yet another mechanism for the user to exit input mode by swiping downwards (many thanks to Benoit Duffez for the suggestion). We check that the y component of the fling is both positive and exceeds the x component by a factor of 5 to constitute a downward swipe.

The final listener is ClearListener which gets called when the user taps the clear button which we mentioned earlier:

This looks pretty straightforward until we think of the bigger picture. InputView encapsulate a set of Views which are specific to the EditText and input mode. However we also need to communicate with the outside world because there are other Views in the main layout which may also need to be updated when input mode changes. We therefore need a mechanism to communicate with external components, and this is done using an custom Listener:

We can now look at the inputChanged() method that is called by the ClearListener to see that this is a mechanism for communicating changes to stakeholders:

There are a couple of methods which have been called a few times which require some explanation:

These construct the property animations that will be played when entering and exiting input mode. I won’t do a deep dive in to the property animations here because there is an upcoming series which will cover property animations in much greater depth – watch this space! There are, however, a couple of important things that occur in endInputMode().

The first is that we request the focus for focusHolder. We mentioned focusHolder in a previous article: It is a zero sized View which enables us to hold focus away from the EditText control and, because it is not an EditText itself, will cause the IME to be hidden when it receives focus. So, by requesting focus to this View, we are controlling the IME visibility.

The second important thing is notifying the InputLister(s) that the input text has changed once the user exits input mode. This enables us to trigger an update of the translation text which is extrinsic to InputView.

We have a getter/setter which we’ll need to to change the text:

In the next article we’ll look at the code for our MainActivity and see how that integrates with InputView to connect up all of our business logic.

The source code for this series is available here.

I am deeply indebted to my fantastic team of volunteer translators who generously gave their time and language skills to make this project sooo much better. They are Sebastiano Poggi (Italian), Zvonko Grujić (Croatian), Conor O’Donnell (Gaelic), Stefan Hoth (German), Hans Petter Eide (Norwegian), Wiebe Elsinga (Dutch), Imanol Pérez Iriarte (Spanish), Adam Graves (Malay), Teo Ramone (Greek), Mattias Isegran Bergander (Swedish), Morten Grouleff (Danish), George Medve (Hungarian), Anup Cowkur (Hindi), Draško Sarić (Serbian), Polson Keeratibumrungpong (Thai), Benoit Duffez (French), and Vasily Sochinsky (Russian).

© 2015, Mark Allison. All rights reserved.

CC BY-NC-SA 4.0 Dirty Phrasebook – Part 4 by Styling Android is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Permissions beyond the scope of this license may be available at http://blog.stylingandroid.com/license-information.

2 Comments

  1. Hi,
    You inflated the InputView layout in the onFinishInflate, but it isn’t better to inflate in the constructor? So when the parent layout inflate itself, it already know their children measures?

    1. If you do that you then cannot add it to the parent because the parent inflation has not completed at that point. That’s why we have to wait for the parent layout inflation to complete.

Leave a Reply

Your email address will not be published. Required fields are marked *