Missing Android
Material Components
Dmytro Danylyk
Timee GmbH.
What we have in AppCompat v7?
What we have in design spec?
Ingredients
Typography
Recipe
<style name="Base.TextAppearance.AppCompat.Body1">
<item name="android:textSize">@dimen/abc_text_size_body_1_material</item>
<item name="android:textColor">?android:textColorPrimary</item>
</style>
<style name="Base.TextAppearance.AppCompat.Caption">
<item name="android:textSize">@dimen/abc_text_size_caption_material</item>
<item name="android:textColor">?android:textColorSecondary</item>
</style>
...
<style name="Base.TextAppearance.AppCompat.Button">
<item name="android:textSize">@dimen/abc_text_size_button_material</item>
<item name="textAllCaps">true</item>
<item name="android:textColor">?android:textColorPrimary</item>
</style>
Code
Material text styles and dimensions are available in appcompat-v7 library.
styles_base_text.xml
Selectors
Lollipop
Older versions
Recipe
<?xml version="1.0" encoding="utf-8"?>
<ripple android:color="@color/flat_pressed">
<item
android:id="@android:id/mask"
android:drawable="@drawable/btn_flat_normal"/>
</ripple>
Code
Result
Android L - Ripple effect
res/drawable-v21
Recipe
<?xml version="1.0" encoding="utf-8"?>
<selector
android:exitFadeDuration="400"
android:enterFadeDuration="400">
<item android:drawable="@drawable/btn_flat_pressed"
android:state_pressed="true" />
<item android:drawable="@android:color/transparent" />
</selector>
Code
Result
Older versions - Fade selector
res/drawable
Shadow
Recipe 1
Wrap view with a CardView and set app:cardElevation attribute.
Remarks
CardView can't draw circle shadow.
Not possible to set shadow position.
2.
1.
Not possible to set shadow color. (Make shadow darker or lighter)
3.
No method to set selector for shadow. (Lift on touch)
4.
Recipe 2
Set background 9.png image.
Remarks
For every component you need shadow image for all drawable-dpi folders.
1.
Ton's of resources increase apk size.
2.
Not flexible for changes.
3.
Shadow Layout
app:sl_shadowRadius="4dp"
app:sl_dy="0dp"
app:sl_dx="0dp"
app:sl_shadowRadius="8dp"
app:sl_dy="4dp"
app:sl_dx="0dp"
Recipe 3
<com.dd.ShadowLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:sl_shadowColor="#AA000000"
app:sl_cornerRadius="4dp"
app:sl_dy="1dp"
app:sl_dx="1dp"
app:sl_shadowRadius="4dp">
<!--Your view here-->
</com.dd.ShadowLayout>
Code
Custom view group which will wrap View and display shadow for pre Lollipop devices.
res/layout
Remarks
setShadowLayer(float radius, float dx, float dy, int shadowColor)
Approach 1: draw shadow on generated Bitmap with shadow layer.
1.
Simple ~ 100 lines of code.
3.
Not very efficient.
4.
Approach 2: draw shadow on view canvas. (without hardware acceleration)
2.
Shadow Layout
Links & Resources
Simple cooking
Buttons
- Floating action button
- Raised button
- Flat button
Flat button
Lollipop
Older versions
Recipe
<style name="AppCompat.Button.Flat" parent="Base.TextAppearance.AppCompat">
<item name="android:background">@drawable/btn_flat_selector</item>
<item name="android:minWidth">88dp</item>
<item name="android:minHeight">36dp</item>
<item name="android:textAllCaps">true</item>
<item name="android:textSize">14sp</item>
<item name="android:textStyle">bold</item>
...
</style>
Code
res/values/styles.xml
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
style="@style/AppCompat.Button.Flat"/>
res/layout
Button with custom style and transparent background selector.
Raised button
Lollipop
Older versions
Recipe
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
app:cardPreventCornerOverlap="false"
app:cardElevation="2dp">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="88dp"
android:minHeight="36dp"
android:text="Button"
...
/>
</android.support.v7.widget.CardView>
Code
Button wrapped with a CardView and cardElevation.
res/layout
Floating action button
Lollipop
Older versions
Recipe
<com.dd.ShadowLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:shadowColor="#AA000000"
app:cornerRadius="56dp"
app:dy="1dp"
app:shadowRadius="4dp">
<ImageButton
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_gravity="center"
android:src="@drawable/ic_add_white"
android:background="@drawable/selector"
android:elevation="4dp"/>
</com.dd.ShadowLayout>
Code
ImageButton with icon and oval shape as a background, wrapped with ShadowLayout to display shadow for pre Lollipop devices.
res/layout
Questions and answers
Why not to use CardView for shadow?
1.
You can't make card view circle.
Questions and answers
Why not to use png image for shadow?
2.
Different buttons have different size, shadow radius, position, color - creating separate set of resources is not convenient - but most efficient.
Floating action button
Links & Resources
Raised button
Flat button
(in progress)
(in progress)
(in progress)
(in progress)
Text fields input error
Recipe
<com.dd.ErrorLabelLayout
android:id="@+id/nameErrorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/editFirsName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="First name"/>
</com.dd.ErrorLabelLayout>
Code
Custom view group which will wrap EditText, show error label and tint EditText background to red or any other color.
res/layout
Remarks
mEditText.getBackground().setColorFilter(mErrorColor, PorterDuff.Mode.SRC_ATOP);
To tint EditText drawable you can use ColorFilter.
public void setError(String text) {
mErrorLabel.setVisibility(VISIBLE);
mErrorLabel.setText(text);
// tint drawable
mDrawable.setColorFilter(mErrorColor, PorterDuff.Mode.SRC_ATOP);
// changing focus from EditText to error label, necessary for Android L only
// EditText background Drawable is not tinted, until EditText remains focus
mErrorLabel.requestFocus();
}
EditText background Drawable is not tinted, until EditText remains focus, don't forget to switch focus to other view.
2.
1.
Keep it simple ~ 100 lines of code
3.
Error Label Layout
Links & Resources
Floating label
Recipe
<com.dd.FloatLabelLayout
android:id="@+id/nameFloatLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/editFirsName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="First name"/>
</com.dd.FloatLabelLayout>
Code
Custom view group which will wrap EditText, show floating label when EditText is in focus.
res/layout
Float Label Layout
Links & Resources
Dialogs
Recipe
MaterialDialog.Builder build = new MaterialDialog.Builder(this)
.body(...)
.title(...)
.buttonMode(MaterialDialog.ButtonMode.NORMAL)
.positiveButton("Agree",
new MaterialDialog.ButtonClickListener() {
@Override
public void onButtonClicked(MaterialDialog dialog, View view) {
dialog.dismiss();
}
})
.negativeButton("Disagree",
new MaterialDialog.ButtonClickListener() {
@Override
public void onButtonClicked(MaterialDialog dialog, View view) {
dialog.dismiss();
}
});
MaterialDialog materialDialog = build.build();
materialDialog.show();
Code
Extend Dialog and set custom content view. As a root layout use either CardView or ShadowLayout.
Remarks
As a root view preferable to use ShadowLayout which gives you darker shadow in comparison with CardView.
getWindow().setDimAmount(0.4f);
Material dialog use lighter dim amount value.
2.
1.
For buttons use AppCompat.Button.Flat style (see Flat button section).
3.
For text style use TextAppearance.AppCompat.Body1 and TextAppearance.AppCompat.Title from appcompat-v7 library.
4.
Material Dialogs
Links & Resources
(in progress)
(in progress)
Snackbar & Toast
Recipe
Snackbar.Builder builder = new Snackbar.Builder(this)
.timeout(Snackbar.TIMEOUT_MEDIUM)
.type(Snackbar.Type.SNACKBAR)
.title(...)
.buttonColor(Color.BLUE)
.button("Undo", new Snackbar.ButtonClickListener() {
@Override
public void onButtonClicked(Snackbar snackbar, View view) {
snackbar.cancel();
}
});
Snackbar snackbar = builder.create();
snackbar.show();
Code
Create class which will add or remove Snackbar's view to activity decor view over all other components.
Remarks
To show snackbar use activity decor view group.
activity.getWindow().getDecorView();
1.
For buttons use AppCompat.Button.Flat style (see Flat button section).
2.
For text style use TextAppearance.AppCompat.Body1 from appcompat-v7 library.
3.
Snackbar & Toast
Links & Resources
(in progress)
(in progress)
Complex cooking
Pickers
Recipe
DatePickerDialog datePickerDialog = DatePickerDialog.newInstance(this,
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH));
datePickerDialog.setYearRange(1985, 2015);
datePickerDialog.show(getFragmentManager(), null);
TimePickerDialog timePickerDialog = TimePickerDialog.newInstance(this,
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE), true);
timePickerDialog.show(getFragmentManager(), null);
Code
Clone and add as a library project.
https://android.googlesource.com/platform/frameworks/opt/datetimepicker
Chips
Available as third-party library.
Progress
Available as third-party library.
Sliders
Available as third-party library.
Tabs
Available as third-party library.
Summary
Use CardView or ShadowLayout if you need shadow for pre Lollipop.
Almost all third party libraries have issues and only partly imitates Material Design spec behavior.
1.
2.
Some components are easy to implement - it is worth it to make your own material-support library and reuse it.
3.
@dmytrodanylyk
dmytrodanylyk.com
slides.com/dmytrodanylyk
S
missing-material-components
By Dmytro Danylyk
missing-material-components
- 30,059