Kita akan mencoba mengadaptasi Material Design versi baru (https://material.io) pada Anko Layout
Drawer Material | Typography | SnackBar Material | Bottom AppBar |
---|---|---|---|
- BaseView
- Tipografi
- Ripple
- Statusbar dan toolbar
- ProgressBar on toolbar
- CardView
- Bottom AppBar
- Material Snackbar
- Material Drawer
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"
}
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.
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)
}
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 |
---|---|
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
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 |
Tidak banyak perbedaan cardview pada Material Design 2, cuman pada roundcorner nya yang lebih bulet.
cardView {
radius = 10f
}
sebelum | sesudah |
---|---|
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 |
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 |
Perubahan tampilan untuk snackbar pada Material Design 2 cukup terlihat, snackbar jadi bergaya card.
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 |
---|---|
Material Drawer |
---|
Link Class Material Drawer
Link Penggunaan Material Drawer
Sip mantap