Dauris Little

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

Creating Advance Custom Snackbar w/ Kotlin

Avatar photo
Dauris
Monday, 28 June 2021 / Published in Android, Kotlin, Programming Languages, Tutorial

Creating Advance Custom Snackbar w/ Kotlin

Ask 100 different developers about what they find most exciting about android development and you will get as many different responses. Personally, I find the customization of the various framework and design libraries. Consider it as such if there is something that doesn’t fit your needs within the development process that as a developer we create custom views without any issues. Within this post, we will be working on a custom Snackbar.

What is a Snackbar? Well simply enough a Snackbars provide lightweight feedback about an operation. They show a brief message at the bottom of the screen on mobile and lower left on larger devices. Snackbars appear above all other elements on screen and only one can be displayed at a time.

Though the Snackbar by default has a few customizable features, consider the fact that the default features may not match the application style. Fortunately, we can easily create a custom Snackbar by implementing just a few classes.  If you are unfamiliar with what a Snackbar is please review my previous post regarding the library. Be aware that it may be a little confusing at glance but fear not we will review it the most important part that is to highlight which are:

  • BottomTransientBottomBar which is the parent view
    •  ContentViewCallback
  • make() a factory method
    • find the parent view, inflate the custom view, and set additional properties 
  • customizable method

Custom View

It is assumed that you have already created/opened your current project within your IDE. Now we create a custom view that will be displayed at the bottom of the screen when called. This design will be the simplest form which contains an image and static message.

class CustomSnackbarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ConstraintLayout (context, attrs, defStyleAttr) {

    private val customImg : AppCompatImageView

    init {
        View.inflate(context, R.layout.view_custom_snackbar, this)
        clipToPadding = false
        this.customImg = findViewById(R.id.img_snack)
    }
}

this is accompanied by the following layout XML

<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">

    <androidx.appcompat.widget.AppCompatImageView
        android:layout_width="173dp"
        android:layout_height="173dp"
        android:id="@+id/img_snack"
        android:scaleType="centerInside"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:srcCompat="@drawable/pokeball" />

    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/tv_snack"
        android:gravity="center"
        android:padding="16dp"
        android:text="You have caught another sir!"
        android:textColor="#28140c"
        android:background="@drawable/custom_snackbar_bg"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="@+id/img_snack"
        app:layout_constraintLeft_toRightOf="@+id/img_snack"
        app:layout_constraintStart_toEndOf="@+id/img_snack"
        app:layout_constraintTop_toTopOf="@+id/img_snack"
        app:layout_constraintVertical_bias="0.75" />
</merge>

Implementation of Custom View

Now the objective is to implement ContentViewCallback. The interface objective is to notify when the Snackbar starts appearing  and dismissing using animateContentIn() and animateContentOut()

class CustomSnackbarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ConstraintLayout (context, attrs, defStyleAttr), ContentViewCallback {

    private val customImg : AppCompatImageView

    init {
        View.inflate(context, R.layout.view_custom_snackbar, this)
        clipToPadding = false
        this.customImg = findViewById(R.id.img_snack)
    }

    override fun animateContentIn(delay: Int, duration: Int) {
        val scaleX = ObjectAnimator.ofFloat(customImg, View.SCALE_X, 0f, 1f)
        val scaleY = ObjectAnimator.ofFloat(customImg, View.SCALE_Y, 0f, 1f)
        AnimatorSet().apply {
            interpolator = OvershootInterpolator()
            setDuration(500)
            playTogether(scaleX,scaleY)
        }.start()

    }

    override fun animateContentOut(delay: Int, duration: Int) {
        TODO("Not yet implemented")
    }

}

Take note that there is a simple scale animation as the Snackbar appears. Now setting the clipToPadding to false is important to avoid the image from clipping because of OvershootInterpolator.

Extending BaseTransientBottomBar

We are now at the point to create an equivalent class for the Snackbar.

The custom Snackbar constructor accepts two params: one for the parent view and another for the custom view that supports the implementation of ContentViewCallback.

class CustomSnackbar(parent: ViewGroup, content: CustomSnackbarView) : BaseTransientBottomBar<CustomSnackbar>(parent, content, content) {

}

Now to implement the entry point method make(). As stated earlier in this post, there are three things to accomplish:

  • There is no reason to attempt and recreate the wheel so we will borrow the logic from the Snackbar’s source code
internal fun View?.findParent(): ViewGroup? {

    var view = this
    var fallback: ViewGroup? = null

    do {
        if (view is CoordinatorLayout) {
            //the Coordinator layout has been found
            return view
        } else if (view is FrameLayout) {
            if (view.id == android.R.id.content) {
                //coordinator layout not found so we need to use the appropriate hierarchy
                return view
            } else {
                //if no view assign the fallback
                fallback = view
            }
        }

        if (view != null) {
            //will continue to search for the view
            val parent = view.parent
            view = if (parent is View) parent else null
        }
    } while (view != null)

    return fallback
}
  • Inflate the custom view
    • inflating a custom view is best done by using the XML layout
<?xml version="1.0" encoding="utf-8"?>
<com.programmingninja.advancesnackbar.snackbar.CustomSnackbarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp" />
  • Build and correct errors
class CustomSnackbar(parent: ViewGroup, content: CustomSnackbarView) : BaseTransientBottomBar<CustomSnackbar>(parent, content, content) {

    companion object {
        fun make(view: View) : CustomSnackbar{

            //set parent for this view
            val parent = view.findParent() ?: throw IllegalArgumentException("No suitable parent found from the correct view. Please correct.")

            //custom view inflated
            val customView = LayoutInflater.from(view.context).inflate(R.layout.activity_snackbar, parent, false) as CustomSnackbarView

            //creation and return this new snackbar
            return CustomSnackbar(parent, customView)
        }

    }
}

If we attempt to run the code, you will notice that there is a gray background and padding. Why is this? This is because the BaseBottomTransientBar implementation is wrapped with the SnackbarBasrLayout. The workaround for this issue is simple enough by adding an init() block

init {
        getView().setBackgroundColor(ContextCompat.getColor(view.context, android.R.color.transparent))
        getView().setPadding(0,0,0,0)
}

Wrapping it altogether

Modify the MainActivity with the following

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

        findViewById<View>(R.id.btn_catch_em).setOnClickListener {
            CustomSnackbar.make(it).show()
        }
    }
}

Now we need to modify the main activity layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn_catch_em"
        android:layout_margin="8dp"
        android:text="Throw Poke Ball"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

Tagged under: android, android development, androidx, kotlin, Mobile Development

What you can read next

val vs var
Val vs Var
Discovering C# with Console
Java vs Kotlin
Java Or Kotlin Language: Deciding Which Option to Use for Android Development

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

  • Gesture Controls for Android w/Kotlin

    Incorporating Gesture controls in your android ...
  • 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...
  • 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