Skip to content

utsmannn/AnkoMaterialSample

Repository files navigation

Anko New Material Design Sample

Kita akan mencoba mengadaptasi Material Design versi baru (https://material.io) pada Anko Layout

Preview

Drawer Material Typography SnackBar Material Bottom AppBar

APK Preview

Download apk (4mb)

Index

1. BaseView

Baseview membuat pada tiap-tiap view konsisten, kita hanya membuat satu master view untuk digunakan pada tiap-tiap view.
Misalnye kita buat object BaseUi yang isinya fungsi baseText() untuk master textview dengan menerapkan typeface.

object BaseUi {
...
    fun ViewManager.baseText() = textView {
        typeface = Typeface.SANS_SERIF
    }
...
}

dan untuk menggunakannya

import com.contoh.package.BaseUi.baseText

baseText().apply {
  text = "contoh text dengan typeface sans serif"
}

2. Typografi

Material design mempunyai ciri tipografi yang jelas dan efisien yang mencangkup typeface, font dan letterspacing.
Typeface yang digunakan biasanya 'sans-serif' dengan font light sampe medium (jarang yg bold) dan letterspace yang ideal. Dalam hal ini kita tidak perlu lagi menambahkan third-font, karena secara default typeface sudah tersedia sans-serif.

Penerapan

Cukup seting typeface dan letterspacing pada baseText() tadi

  typeface = Typeface.SANS_SERIF
  letterSpacing = 0.03f

Kita juga dapat membuat fungsi untuk material tipografi tersebut dalam class Utils

fun materialFont(view: TextView) {
    view.apply {
        typeface = Typeface.SANS_SERIF
        letterSpacing = 0.03f
    }
}

Nah sekarang fungsi materialFont sudah bisa di pake untuk tiap textView dan Button

textView("textnya") {
    Utils.materialFont(this)
}

Link code material text

3. Ripple

Jika kita menggunakan xml, untuk mengaktifkan animasi ripple cukup dengan menambahkan ?android:attr/selectableItemBackground pada foreground atau background view.
Lalu untuk penggunaan pada Anko, kodenya sebagai berikut

val rippleValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.selectableItemBackground, rippleValue, true)
foreground = ContextCompat.getDrawable(context, rippleValue.resourceId)

// atau di background
// background = ContextCompat.getDrawable(context, rippleValue.resourceId)

Kita dapat membuat fungsi tersebut di dalam class Utils kemudian dipanggil pada view di anko sehingga bisa dipakai berulang

fun ripple(view: View, type: Int) {
    view.apply {
        val rippleValue = TypedValue()
        context.theme.resolveAttribute(android.R.attr.selectableItemBackground, rippleValue, true)

        when (type) {
            0 -> foreground = ContextCompat.getDrawable(context, rippleValue.resourceId)
            1 -> background = ContextCompat.getDrawable(context, rippleValue.resourceId)
        }
    }
}

Fungsinya dapat dipanggil seperti ini

cardView {
  Utils.ripple(this, 0) // 0 untuk foreground dan 1 untuk foreground
}
not ripple ripple

Link code ripple

4. StatusBar dan Toolbar

Chrome Play Games Keep

Pada banyak aplikasi google yang menerapkan Material Design 2, status bar dan toolbar memiliki warna yang sama. Caranya adalah dengan menyamakan colorPrimary dengan colorPrimaryDark. Kemudian pada kita juga akan mengapikasikan tipografi pada title toolbar, caranya dengan custom toolbar yg pake theme themedToolbar.
Kita buat sebuah baseView baseToolbar

fun ViewManager.baseToolbar() = themedToolbar(R.style.ThemeOverlay_AppCompat_Dark) {
    id = R.id.toolbar
    title = resources.getString(R.string.app_name)

    val viewTitle = this.getChildAt(0) as TextView
    materialFont(viewTitle)
}

Pada custom toolbar tersebut, kita memanggil textView untuk diaplikasikan fungsi tipografi

Link code toolbar
Link code fake status bar
Link code disable statusbar

5. ProgressBar in Toolbar

ProgressDialog sudah deprecated di level api 26 karena kata google itu menutup interaksi user dengan aplikasi. Maka solusi nya adalah menggunakan progressbar yang diletakan secara baik, salah satunya di Toolbar. Caranya tambahkan progressbar ke dalam toolbar.

horizontalProgressBar {
    bottomPadding = dip(-7)
    topPadding = dip(-7)

    isIndeterminate = true
    visibility = View.VISIBLE
    backgroundColorResource = android.R.color.white
}.lparams(matchParent, wrapContent)

Visible dapat di setel jika progress on, jika sudah off, bisa disetel View.GONE, bottomPadding dan topPadding menghilangkan background putih di atas dan dibawahnya. Background putih disarankan agar membedakan dengan background toolbar dan progress primary.

progressbar in toolbar

link code progressbar

6. CardView

Tidak banyak perbedaan cardview pada Material Design 2, cuman pada roundcorner nya yang lebih bulet.

cardView {
    radius = 10f
}
sebelum sesudah

7. Bottom AppBar

Salah satu komponen yang ditambahkan pada material design adalah BottomAppBar, komponen ini berbeda dengan bottom navigation. Kalo di android material (AndroidX) kita gak usah bikin custom view lagi, nah untuk Anko kita harus bikin custom view dengan komposisi

cordinator layout
----- vertical layout
---------- view shadow top
---------- toolbar v7
----- fab anchor ke vertical layout di atas

Maka kodenya kurang lebih kaya begini

coordinatorLayout{
    // parent layout
    
    ...
    verticalLayout {
        id = bottom_app_bar
        view {
           background = ContextCompat.getDrawable(context, R.drawable.shadow)
        }
        toolbar {
            id = bottom_toolbar
            backgroundColorResource = R.color.colorPrimary
        }
    }.lparams { gravity = Gravity.Bottom }
    
    floatingActionButton {
        setImageResource(R.drawable.ic_plus)
    }.lparams {
        anchorId = bottom_app_bar
        anchorGravity = Gravity.CENTER_HORIZONTAL
        margin = dip(8)
    }
}

Untuk view shadow codenya

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:startColor="#1F000000"
        android:endColor="@android:color/transparent"
        android:angle="90" />
</shape>
Custom bottom appbar

Menambahkan menu dan navigasi

Pada toolbar yang ada di atas (id bottom_toolbar) bisa kita pasang navigasi icon dan menu, caranya sama kaya toolbar biasa

...
override fun onCreate(...) {
    ...
    val bottomToolbar = find(bottom_toolbar)
    bottomToolbar.navigationIconResource = R.drawable.ic_menu // setup navigasi
    bottomToolbar.inflateMenu(R.menu.bottom_menu) // setup menu
    ...
}
custom bottom appbar with menu and navigation

Kita juga bisa mengganti floatingActionButton dengan CardView seperti aplikasi Google Tasks. Seting cardview dengan radius = 50f

custom bottom appbar with cardview

Link code bottom appbar

8. Snackbar

Perubahan tampilan untuk snackbar pada Material Design 2 cukup terlihat, snackbar jadi bergaya card.

source: material.io
material snackbar

Nah kita akan mencoba merubah snackbar biasa menjadi lebih material. Untuk itu kita perlu menambahkan view snackbar ke view custom yang kita buat dan mendisable child view asli seperti textView dan button, Kemudian membuat fungsi untuk textview dan button pada view custom.
Pertama buat class SnackbarUi untuk layout snackbar

class SnackBarUi(private val marginBottom: Int) : AnkoComponent<ViewGroup> {
    override fun createView(ui: AnkoContext<ViewGroup>): View = with(ui) {
        relativeLayout {
            id = R.id.snackbar_layout
            lparams(matchParent, wrapContent)

            cardView {
                radius = 7f

                linearLayout {
                    gravity = Gravity.CENTER_VERTICAL
                    backgroundColor = Color.parseColor("#323232")
                    textView {
                        id = R.id.snackbar_text
                        textColorResource = android.R.color.white
                        BaseUi.materialFont(this)
                    }.lparams(matchParent, wrapContent) {
                        weight = 3f
                        leftMargin = dip(16)
                        rightMargin = dip(16)
                    }

                    styledButton(R.style.Widget_AppCompat_Button_Borderless) {
                        id = R.id.snackbar_action
                        textColorResource = R.color.colorAccent
                    }.lparams(wrapContent, wrapContent)

                }.lparams(matchParent, wrapContent)
            }.lparams(matchParent, wrapContent) {
                leftMargin = dip(12)
                rightMargin = dip(12)
                topMargin = dip(12)
                bottomMargin = dip(marginBottom)
            }
        }
    }

    inline fun ViewManager.styledButton(styleRes: Int = 0, init: Button.() -> Unit): Button = ankoView({
        if (styleRes == 0) Button(it)
        else Button(ContextThemeWrapper(it, styleRes), null, 0)
    }, 0, init)
}

Class SnackBarUi membawa kontruktor marginBottom agar margin bottom dari card sifatnya dinamis. Hal ini berguna karena pengaturan dalam Snackbar yang baru tidak mengizinkan mengoverlap actionview dibawah layar seperti appbar bottom atau bottom navigasi, jadi perlu ketiggian yang dinamis.

Setelah itu kita buat class MaterialSnackbar

class MaterialSnackbar private constructor(context: Context) {
    private var background: Int = 0
    var contentView: View? = null
        private set
    private var duration: LENGTH? = null
    private var swipe: Boolean = false
    private var bottomMargin: Int = 12
    private var snackbar: Snackbar? = null

    private var ctx = context

    val isShowing: Boolean
        get() = snackbar != null && snackbar!!.isShown

    init {
        this.duration = LENGTH.LONG
        this.background = -1
        this.swipe = true
    }


    fun duration(duration: LENGTH): MaterialSnackbar {
        this.duration = duration
        return this
    }

    fun swipe(swipe: Boolean): MaterialSnackbar {
        this.swipe = swipe
        return this
    }

    fun bottomMargin(bottomMargin: Int) : MaterialSnackbar {
        this.bottomMargin = bottomMargin
        return this
    }

    fun build(view: View): MaterialSnackbar {
        when (duration) {
            MaterialSnackbar.LENGTH.INDEFINITE -> snackbar = Snackbar.make(view, "", Snackbar.LENGTH_INDEFINITE)
            MaterialSnackbar.LENGTH.SHORT -> snackbar = Snackbar.make(view, "", Snackbar.LENGTH_SHORT)
            MaterialSnackbar.LENGTH.LONG -> snackbar = Snackbar.make(view, "", Snackbar.LENGTH_LONG)
        }
        val snackbarView = snackbar?.view as Snackbar.SnackbarLayout

        if (!swipe) {
            snackbarView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
                override fun onPreDraw(): Boolean {
                    snackbarView.viewTreeObserver.removeOnPreDrawListener(this)
                    (snackbarView.layoutParams as CoordinatorLayout.LayoutParams).behavior = null
                    return true
                }
            })
        }

        snackbarView.setPadding(0, 0, 0, 0)
        snackbarView.setBackgroundResource(android.R.color.transparent)
        val text = snackbarView.findViewById<View>(android.support.design.R.id.snackbar_text) as TextView
        text.visibility = View.INVISIBLE
        val action = snackbarView.findViewById<View>(android.support.design.R.id.snackbar_action) as TextView
        action.visibility = View.INVISIBLE
        contentView = SnackBarUi(bottomMargin).createView(AnkoContext.create(ctx, snackbarView))
        snackbarView.addView(contentView, 0)
        return this
    }

    fun setText(text: CharSequence): MaterialSnackbar {
        val textView = contentView?.find<TextView>(R.id.snackbar_text)
        textView?.text = text
        return this
    }

    fun setAction(text: CharSequence, listener: View.OnClickListener): MaterialSnackbar {
        val actionView = contentView?.find<Button>(R.id.snackbar_action)
        actionView?.text = text
        actionView?.visibility = View.VISIBLE
        actionView?.setOnClickListener { view ->
            listener.onClick(view)
            dismiss()
        }
        return this
    }

    fun show() {
        snackbar?.show()
    }

    fun dismiss() {
        if (snackbar != null) snackbar?.dismiss()
    }

    enum class LENGTH {
        INDEFINITE, SHORT, LONG
    }

    companion object {

        fun Builder(context: Context): MaterialSnackbar {
            return MaterialSnackbar(context)
        }
    }
}

Pada class diatas setidaknya ada beberapa fungsi, yakni duration, swipe, bottomMargin. Ketiga fungsi tersebut harus dijalankan sebelum fungsi build. Kemudian setelah fungsi build, kita bisa memanggil fungsi setText dan setAction. Hal ini karena fungsi build membangun custom layout yang berisi custom text dan button action, jadi setelah build kedua view tersebut baru bisa dipake.

Untuk menggunakannya kodenya seperti berikut

MaterialSnackbar.Builder(this)
    .duration(MaterialSnackbar.LENGTH.INDEFINITE) // optional, defaultnya LONG
    .bottomMargin(100) // optional, defaultnya 12dip
    .swipe(false) // optional, defaultnya false
    .build(find(R.id.parent))
    .apply { 
        setText("ini material snackbar")
        setAction("okeh", View.OnClickListener {
            // aksi oke
        })
    }.show()
default dengan bottomMargin

Link MaterialSnackbar

Bonus - Material Drawer

Material Drawer

Link Class Material Drawer
Link Penggunaan Material Drawer


Sip mantap

About

Implementation New Material Design on Anko Layout

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages