Lab 2
Introduction
In this lab we will try our first steps with the Kotlin language and add basic interactive behaviour, such as click handling, to our UI.
- Make sure you have an understanding of these Kotlin concepts, discussed during the lecture:
- Mutable and immutable variable declaration (var vs val)
- Null-safety, including safe calls and the !! operator
- Inferred types
Exercise 1: In-code GUI creation
in MainActivity.kt onCreate():
- Create an instance of the LinearLayout class. The primary constructor expects a Context type argument, you can use the this keyword (which points to the current Activity instance).
- Set it to be vertical by updating the value of its' orientation
- Let's add 10 buttons to the LinearLayout using a loop.( Feel free to try out different ways to achieve this with Kotlin, e.g. with for loops or using repeat )
- In the loop, create a new Button instance, and use LinearLayout-s
addView()
function to add it to the layout.
- In the loop, create a new Button instance, and use LinearLayout-s
- Update the loop so that the attributes of each button are modified:
- Set the text of each one to "Button X", where X is an integer representing current iteration number within the loop.
- Set the background of the buttons to a color based on the iteration number. You can do this with
setBackgroundColor(Color.rgb( red, green, blue))
, where the 3 arguments are values between 0-255 for each of the 3 color channels.
- Make the Activity display the LinearLayout object instead of the default XML resource (using setContentView(..) ).
Exercise 2: ConstraintLayout practice
Change back your MainActivity so that it uses the XML resource activity definition. Create the following ConstraintLayout configuration:
- Divide the UI vertically into 2 (2/3 and 1/3, as shown on picture), using a Guideline in percentage mode
- Top should have 1 TextView with long text
- Vertically centered w.r.t. guideline
- Horizontally constrained to edges of layout, with 32dp margin
- Add 2 Buttons to the bottom
- Vertically centered
- Create a horizontal chain of them
- Set chain to "packed" or "spread inside".
- Horizontally constrainted to edges of the parent
Exercise 3: Click behaviour
- Set up a click handler for both buttons (see below for more details)
- For the 1st button, use the programmatic approach and make the click update the button to show the number of times it has been clicked. Each time it gets clicked, it should also add a word to the TextView (hint: use TextView.append(..) or Kotlin repeat).
- For the 2nd button, use
XML onClick
and make it restart the counter. (Also try clearing the TextView)
You can achieve this based on the below tips:
Getting a reference to a UI element defined in XML
- you can access UI View Objects directly using its ID: e.g. if your XML object has an ID of
myButton
, use the findViewById() method, to get View objects that have been declared in XML resources or created in code.val b:Button = findViewById(R.id.myButton)
Setting click listeners
- To set a click listener from the code, you can use Button.setOnClickListener( .. )
button1.setOnClickListener { view: View? -> Log.i(TAG, "View: ${view?.id} was clicked!") }
- Alternatively, you can specify android:onClick attribute of the XML object definition, setting its value to the name of the method to be called
- The method has to be defined in the Activities' .kt file and accept a single argument of type View:
fun myHandler(view: View) { /** react to click */ }
- The method has to be defined in the Activities' .kt file and accept a single argument of type View:
Exercise 4: Touch listener
Let's add more fine-grained control of button presses using a touch listener. With a touch listener, we can differentiate the moment when the button is pressed down and when it is released.
The basic code for attaching a touch listener looks like this:
my_button.setOnTouchListener({ view, motionEvent -> // do something useful true // return value - whether this listener consumed the event })
In the above, the return value indicates whether or not this listener consumed the event (if you set it false and also have a click listener for the same button, the click listener also gets triggered, whereas if the touch listener consumes the event, the click listener won't get triggered).
the motionEvent
can be used to determine which kind of touch event it is.
Let's handle two kinds of events: DOWN and UP using Kotlin's when construct:
when (motionEvent.action){ MotionEvent.ACTION_UP -> { /** TODO */ } MotionEvent.ACTION_DOWN -> { /** TODO */ } else -> { /** something else happened */} }
- You may see a warning about not calling performClick(), you can ignore it for this lab, but here is a good explanation of the warning.
- Replace the TextView in your ConstraintLayout with a vertical LinearLayout (in the XML layout file).
- Using the onTouchListener, record how long the button was held down in milliseconds (use
System.currentTimeMillis()
) - Whenever the button is released and you have the press duration, add a new TextView to the LinearLayout
- Make the TextView display the duration
- To empty the LinearLayout (for the reset button), you can use
my_layout.removeAllViews()
Exercise 5: Enabling View Binding
It can become bothersome and inefficient to manage UI manipulation using findViewById(..). For this purpose, View Binding is a technique which automatically generates classes representing XML-defined layouts, and provides references to the objects therein.
https://developer.android.com/topic/libraries/view-binding
By default, View Binding isn't enabled. Let's add it to our project:
Update your module level build.gradle
file, by adding the below buildFeatures part:
android { ... buildFeatures { viewBinding true } }
In MainActivity.kt, let's initialize the Binding object. Since we enabled View Binding in the Gradle file, a class is automatically created for each Activity. In our case, the class ActivityMainBinding has been generated . Let's use it in the code:
// Declare variable for storing the binding private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) ... // we can access UI elements from the binding, e.g. binding.myButton.text = "Button" setContentView(binding.root) // binding.root refers to the root Layout }
Note: the lateinit
is a special Kotlin keyword which alllows us to have a non-nullable type variable, whose initial value is undefined. We define it later, in onCreate, but thanks to lateinit, the type is : ActivityMainBinding and not : ActivityMainBinding?