Dauris Little

  • About
  • Dauris’s Portfolio
  • Blogging Lyf
  • Contact

Gesture Controls for Android w/Kotlin

Avatar photo
Dauris
Monday, 03 January 2022 / Published in Android, blog, Kotlin, Mobile Development, Programming Languages, Tutorial

Gesture Controls for Android w/Kotlin

If you have never used or unaware of what is gestures control. Incorporating gesture functionality makes an app more functional and engaging for users. As you will find in this tutorial the objective will be to implement two different types of gestures in the application. Assuming this is not your first rodeo lets dive into the development process.

Project Creation

Well of course you need somewhere to start so firing up your Android Studio it you haven’t already. Select the project you prefer to use but for this tutorial we are going to use the “Empty Activity” template just to keep it simple. (If you require more content on the different templates check out: Blah Blah)

NOTE: minimum SDK for this project is 5.0

Update Activity Layout

After you project has finished loading and building itself, we are going to head over ‘activity_main.xml’. First let’s change the parent layout from constraint to frame layout. Withing the frame we are going to use the Material Cardview and inside the card view we will add the constraint layout. Last the constraint layout will hold the important stuff which are buttons and text view. Let’s see how the code looks altogether.

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.circularreveal.CircularRevealFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/layout_parent"
    android:background="@color/design_default_color_primary"
    tools:context=".MainActivity">

    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/cv_display"
        android:layout_gravity="center"
        android:layout_margin="20dp"
        app:cardCornerRadius="15dp">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_question"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                android:text="@string/txt_question_value"
                android:textSize="70sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_solution"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_question"
                android:layout_marginTop="10dp"
                android:text="@string/txt_answer_value"
                android:textSize="40sp"
                app:layout_constraintBottom_toTopOf="@id/tv_points"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tv_question" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/btn_addition"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_question"
                android:contentDescription="@string/txt_add_descriptor"
                android:src="@drawable/ic_baseline_add_24"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toBottomOf="@+id/tv_solution"
                app:layout_constraintStart_toEndOf="@+id/tv_solution"
                app:layout_constraintTop_toTopOf="@+id/tv_solution" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/btn_subtraction"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_question"
                android:layout_toStartOf="@+id/tv_solution"
                android:contentDescription="@string/txt_minus_descriptor"
                android:src="@drawable/ic_baseline_remove_24"
                app:layout_constraintBottom_toBottomOf="@id/tv_solution"
                app:layout_constraintEnd_toStartOf="@+id/tv_solution"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="@+id/tv_solution" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_points"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_solution"
                android:layout_marginBottom="30dp"
                android:text="@string/txt_string_score"
                android:textSize="30sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="@id/btn_addition"
                app:layout_constraintStart_toStartOf="@id/btn_subtraction" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </com.google.android.material.card.MaterialCardView>
</com.google.android.material.circularreveal.CircularRevealFrameLayout>
inner class GestureControlListener : GestureDetector.SimpleOnGestureListener() {

        override fun onFling(evt1: MotionEvent, evt2: MotionEvent, xVelocity: Float, yVelocity: Float) : Boolean {

            pts -= 75
            generateQuestion(userAnsw)
            view.setBackgroundColor(Random.nextInt())

            Snackbar.make(view, "Skipped", Snackbar.LENGTH_SHORT).show()
            cv.text = "Current Score: $pts"
            return true
        }
}

Writing Kotlin Code

Now we’ll create an inner class for detecting gestures that extends to ‘GestureDetector.SimpleOnGestureListener’. Within the class we will keep it simple and override the two functions that detects fling and double tap gestures.

class MainActivity : AppCompatActivity() {

    /** Global Variables Start **/
    //gesture
    lateinit var gdc: GestureDetectorCompat

    ///Textviews
    lateinit var qv: AppCompatTextView
    lateinit var sv: AppCompatTextView
    lateinit var cv: AppCompatTextView

    //Buttons
    lateinit var ab: FloatingActionButton
    lateinit var sb: FloatingActionButton

    //view
    lateinit var view:FrameLayout

    //integer variables
    var pts = 0
    var userAnsw = 0
    var questionValue1 = 0
    var questionValue2 = 0
    /** Global Variables end **/

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

}

Comprehending what we just declared. First the GestureDetectorCompat object contains functions which we will override to enable us to respond to the different gestures. Then we have the next 6 variables that are associated with the Views within the cardview. Then the variables that will represent the score along with the users answer, and then the question that is displayed to the user.

Setup Views and Listeners

We’ll start by referring to all our views. After we have accomplished that will set up onClickListerners to increase or decrease with the floating action button. Now that the click listeners are set, we need to define a way to generate new questions for the user to answer. We’ll create a function called generateQuestion that accepts a numeric parameter. The objective of the function will assign two random numbers based on the parameter passed in and assigned to the variable created earlier questionValue1 and questionValue2.

class MainActivity : AppCompatActivity() {

    /** Global Variables Start **/
    //gesture
    lateinit var gdc: GestureDetectorCompat

    ///Textviews
    lateinit var qv: AppCompatTextView
    lateinit var sv: AppCompatTextView
    lateinit var cv: AppCompatTextView

    //Buttons
    lateinit var ab: FloatingActionButton
    lateinit var sb: FloatingActionButton

    //view
    lateinit var view:FrameLayout

    //integer variables
    var pts = 0
    var userAnsw = 0
    var questionValue1 = 0
    var questionValue2 = 0
    /** Global Variables end **/

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //lets do some work
        view = findViewById<FrameLayout>(R.id.layout_parent)
        qv =  findViewById(R.id.tv_question)
        sv = findViewById(R.id.tv_solution)
        cv = findViewById(R.id.tv_points)
        ab = findViewById(R.id.btn_addition)
        sb = findViewById(R.id.btn_subtraction)

        //assign listeners
        ab.setOnClickListener {
            userAnsw++
            sv.text = "$userAnsw"
        }

        sb.setOnClickListener {
            userAnsw--
            sv.text = "$userAnsw"
        }

        generateQuestion(userAnsw)
    }


    //generate question function for user
    fun generateQuestion(num: Int) {
        var randomNum = 0
        randomNum = if (num == 0) 12
        else num

        questionValue1 = Random.nextInt(randomNum)
        questionValue2 = Random.nextInt(randomNum)

        val problem = "$questionValue1 + $questionValue2"
        qv.text = problem
    }
}

Creating an Inner Class

Now we’ll create an inner class for detecting gestures that extends to ‘GestureDetector.SimpleOnGestureListener’. Within the class we will keep it simple and override the two functions that detects fling and double tap gestures.

class MainActivity : AppCompatActivity() {

    /** Global Variables Start **/
    //gesture
    lateinit var gdc: GestureDetectorCompat

    ///Textviews
    lateinit var qv: AppCompatTextView
    lateinit var sv: AppCompatTextView
    lateinit var cv: AppCompatTextView

    //Buttons
    lateinit var ab: FloatingActionButton
    lateinit var sb: FloatingActionButton

    //view
    lateinit var view:FrameLayout

    //integer variables
    var pts = 0
    var userAnsw = 0
    var questionValue1 = 0
    var questionValue2 = 0
    /** Global Variables end **/

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //lets do some work
        view = findViewById<FrameLayout>(R.id.layout_parent)
        qv =  findViewById(R.id.tv_question)
        sv = findViewById(R.id.tv_solution)
        cv = findViewById(R.id.tv_points)
        ab = findViewById(R.id.btn_addition)
        sb = findViewById(R.id.btn_subtraction)

        //assign listeners
        ab.setOnClickListener {
            userAnsw++
            sv.text = "$userAnsw"
        }

        sb.setOnClickListener {
            userAnsw--
            sv.text = "$userAnsw"
        }

        generateQuestion(userAnsw)
    }


    //generate question function for user
    fun generateQuestion(num: Int) {
        var randomNum = 0
        randomNum = if (num == 0) 12
        else num

        questionValue1 = Random.nextInt(randomNum)
        questionValue2 = Random.nextInt(randomNum)

        val problem = "$questionValue1 + $questionValue2"
        qv.text = problem
    }

    /**
    *
    * */
    inner class GestureControlListener : GestureDetector.SimpleOnGestureListener() {

        
    }
}

The function we just wrote above objective is supposed to detect the fling gesture and when a user uses the fling gesture the application will skip the question and displays a new question. Then we call the function generateQuestion and new questions will be assigned to the AppCompatTextview. A brief message will appear to inform the user that the question was skipped.

class MainActivity : AppCompatActivity() {

    /** Global Variables Start **/
    //gesture
    lateinit var gdc: GestureDetectorCompat

    ///Textviews
    lateinit var qv: AppCompatTextView
    lateinit var sv: AppCompatTextView
    lateinit var cv: AppCompatTextView

    //Buttons
    lateinit var ab: FloatingActionButton
    lateinit var sb: FloatingActionButton

    //view
    lateinit var view:FrameLayout

    //integer variables
    var pts = 0
    var userAnsw = 0
    var questionValue1 = 0
    var questionValue2 = 0
    /** Global Variables end **/

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //lets do some work
        view = findViewById<FrameLayout>(R.id.layout_parent)
        qv =  findViewById(R.id.tv_question)
        sv = findViewById(R.id.tv_solution)
        cv = findViewById(R.id.tv_points)
        ab = findViewById(R.id.btn_addition)
        sb = findViewById(R.id.btn_subtraction)

        //assign listeners
        ab.setOnClickListener {
            userAnsw++
            sv.text = "$userAnsw"
        }

        sb.setOnClickListener {
            userAnsw--
            sv.text = "$userAnsw"
        }

        generateQuestion(userAnsw)
    }

    //generate question function for user
    fun generateQuestion(num: Int) {
        var randomNum = 0
        randomNum = if (num == 0) 12
        else num

        questionValue1 = Random.nextInt(randomNum)
        questionValue2 = Random.nextInt(randomNum)

        val problem = "$questionValue1 + $questionValue2"
        qv.text = problem
    }

    /**
    *
    * */
    inner class GestureControlListener : GestureDetector.SimpleOnGestureListener() {

        override fun onFling(evt1: MotionEvent, evt2: MotionEvent, xVelocity: Float, yVelocity: Float) : Boolean {

            pts -= 75
            generateQuestion(userAnsw)
            view.setBackgroundColor(Random.nextInt())

            Snackbar.make(view, "Skipped", Snackbar.LENGTH_SHORT).show()
            cv.text = "Current Score: $pts"
            return true
        }

    }
}

Now we’ll add another function to override in the inner class which is onDoubleTap. As you are probably already aware this function detects a double tap gesture. The double tap gesture will act as a submission for the user answers to the question presented. Upon submission the answer is checked in the conditional and they are found equal the user score is increased and reflected in the view. A new problem is generated but if is found not to be equal then a deduction from the score is applied and brief message is display to the user before generating a new question.

inner class GestureControlListener : GestureDetector.SimpleOnGestureListener() {

        override fun onFling(evt1: MotionEvent, evt2: MotionEvent, xVelocity: Float, yVelocity: Float) : Boolean {

            pts -= 75
            generateQuestion(userAnsw)
            view.setBackgroundColor(Random.nextInt())

            Snackbar.make(view, "Skipped", Snackbar.LENGTH_SHORT).show()
            cv.text = "Current Score: $pts"
            return true
        }



        override fun onDoubleTap(evt: MotionEvent?): Boolean {

            if (userAnsw == questionValue1 + questionValue2) pts = 100*125
            else {
                pts -= 125
                view.setBackgroundColor(Color.RED)
                Snackbar.make(view, "Incorrect", Snackbar.LENGTH_SHORT).show()
            }

            generateQuestion(userAnsw)
            cv.text = "Current Score: $pts"
            return true
        }
}

Finally, we need to override onTouch in the MainActivity. When a touch is detected the function fires, so we’ll use this function to call the gestureDetector onTouchEventFunction.

class MainActivity : AppCompatActivity() {

    /** Global Variables Start **/
    //gesture
    lateinit var gdc: GestureDetectorCompat

    ///Textviews
    lateinit var qv: AppCompatTextView
    lateinit var sv: AppCompatTextView
    lateinit var cv: AppCompatTextView

    //Buttons
    lateinit var ab: FloatingActionButton
    lateinit var sb: FloatingActionButton

    //view
    lateinit var view:FrameLayout

    //integer variables
    var pts = 0
    var userAnsw = 0
    var questionValue1 = 0
    var questionValue2 = 0
    /** Global Variables end **/

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //lets do some work
        view = findViewById<FrameLayout>(R.id.layout_parent)
        qv =  findViewById(R.id.tv_question)
        sv = findViewById(R.id.tv_solution)
        cv = findViewById(R.id.tv_points)
        ab = findViewById(R.id.btn_addition)
        sb = findViewById(R.id.btn_subtraction)

        //assign listeners
        ab.setOnClickListener {
            userAnsw++
            sv.text = "$userAnsw"
        }

        sb.setOnClickListener {
            userAnsw--
            sv.text = "$userAnsw"
        }

        generateQuestion(userAnsw)
        gdc = GestureDetectorCompat(this, GestureControlListener())
    }

    override fun onTouchEvent(evt: MotionEvent): Boolean {
        gdc.onTouchEvent(evt)
        return true
    }

    //generate question function for user
    fun generateQuestion(num: Int) {
        var randomNum = 0
        randomNum = if (num == 0) 12
        else num

        questionValue1 = Random.nextInt(randomNum)
        questionValue2 = Random.nextInt(randomNum)

        val problem = "$questionValue1 + $questionValue2"
        qv.text = problem
    }

    /**
    *
    * */
    inner class GestureControlListener : GestureDetector.SimpleOnGestureListener() {

        override fun onFling(evt1: MotionEvent, evt2: MotionEvent, xVelocity: Float, yVelocity: Float) : Boolean {

            pts -= 75
            generateQuestion(userAnsw)
            view.setBackgroundColor(Random.nextInt())

            Snackbar.make(view, "Skipped", Snackbar.LENGTH_SHORT).show()
            cv.text = "Current Score: $pts"
            return true
        }



        override fun onDoubleTap(evt: MotionEvent?): Boolean {

            if (userAnsw == questionValue1 + questionValue2) pts = 100*125
            else {
                pts -= 125
                view.setBackgroundColor(Color.RED)
                Snackbar.make(view, "Incorrect", Snackbar.LENGTH_SHORT).show()
            }

            generateQuestion(userAnsw)
            cv.text = "Current Score: $pts"
            return true
        }
    }
}

Install, Test, Conclude

Use the virtual or install on your device. When you increase or decrease the answer to the sum of the presented question. To submit your answer then double tap and if the answer is correct, you score will increase and a new problem will be presented to you. However, if you fling across the screen then problem is skipped, and a message is displayed to your user. With that I believe we have accomplished the task set out before us. If you are confused about anything, please don’t hesitate to contact me or check out the github repository on the project. You now have the “JUICE”!

Note: This class override a variety of functions

Tagged under: android, Android Dev, android development, double tap, fling, fling or double tap, gesture control, Mobile Development

What you can read next

Show/Hide On-Screen Keyboard
val vs var
Val vs Var
Using Enum w/Swift

Leave a Reply Cancel reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Categories

Recent Posts

  • Android Rating: In-App Review API

    An app rating and reviews are crucial if you wa...
  • QR Reader in Android w/ Kotlin

    Turn your phone's camera into a QR scanner...
  • Creating Advance Custom Snackbar w/ Kotlin

    Ask 100 different developers about what they fi...
  • Swift Has Tuple

    Swift provides us with a type called Tuple whic...
  • “Structs vs Classes” and using them

    This is because it plays a significant role in ...

© 2017. All rights reserved. Designed by Dauris Little

TOP
This site uses tracking cookies to personalize content and ads. AcceptLearn More