AndroidでViewを角丸にする4つの方法
こんにちは。タピオカです♪
初めての記事よろしくお願いします。
今回は、AndroidでViewを角丸にする方法についてご紹介します。
概要
AndroidでViewを角丸にすることは複雑な問題ではないですが、実装方法が様々な方法があります。
今回はよく使われる4つの方法について、それぞれの実装方法とメリット・デメリットをご紹介したいと思います。
1.背景を指定して角丸にする方法
XMLで実装する場合
Viewを角丸にするために <shape>
を使うのが最もシンプルな方法であり、以下2つのステップで実装できます。
1.res/drawable
に shape.xml
を作る
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/black" />
<corners android:radius="16dp" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/black" />
<corners android:radius="16dp" />
</shape>
2.Viewの背景をshape.xml
に指定する
<View
android:layout_height="48dp"
android:layout_width="match_parent"
android:background="@drawable/shape" />
※topLeftRadius
やbottomLeftRadius
などを指定すると、角を個別に調整することができます。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/black" />
<corners android:topLeftRadius="16dp"
android:topRightRadius="8dp"
android:bottomRightRadius="8dp" />
</shape>
※shape.xml
の代わりに.9.png
を使うことで、複雑な形状を実装することができます。(e.g.チャットアプリの吹き出しなど)
コードで実装する場合
コードで実装する場合、GradientDrawable
を利用し、以下2つのステップで実装できます。
1.layout.xml
でViewのid
を指定する
<View android:id="@+id/view"
android:layout_height="48dp"
android:layout_width="match_parent" />
- Viewの背景に
GradientDrawable
を指定する
val view = findViewById<View>(R.id.view)
val shape = GradientDrawable()
shape.color = ColorStateList.valueOf(getColor(android.R.color.black))
shape.cornerRadius = resources.displayMetrics.density * 16 // 16dp
view.background = shape
Tips. backgroundTintを指定すると、shapeの色を変更できる
<View ... android:backgroundTint="@android:color/white" />
背景指定のメリットとデメリット
- メリット
- 実装が非常に簡単なこと
- 角を個別に調整することができること
- デメリット
- Viewのクロッピングがないので、その他のプロパティやサブViewに制約がないこと
サブViewやsrc
、foreground
などはshape.xml
の範囲を超えられます。 - 複雑な仕様で再利用するのは難しいこと
例えば、backgroundTintにて色を指定すると全ての<shape>
が指定した色になるため、それぞれの<shape>
に色を変更したい場合は、指定したい色ごとにshape.xml
を作成する必要があります。また、角丸の形状をそれぞれ指定したい場合も、別々にshape.xml
を作成する必要があります。
- Viewのクロッピングがないので、その他のプロパティやサブViewに制約がないこと
2.ViewOutlineProviderを使って角丸にする方法
ViewOutlineProvider
はViewをクリップする方法で、以下2つのステップで実装できます。
1.layout.xml
でViewのid
を指定する
<View android:id="@+id/view"
android:layout_height="48dp"
android:layout_width="match_parent"
android:background="@android:color/black" />
2.コードでViewOutlineProvider
を設定する
view.clipToOutline = true
view.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(v: View, outline: Outline) {
outline.setRoundRect(0, 0, v.width, v.height, 16 * v.resources.displayMetrics.density)
}
}
ViewOutlineProviderを使うメリットとデメリット
- メリット
outlineProvider
を指定して、clipToOutline
をtrue
に設定すると、サブViewやsrc
、foreground
、background
などはOutline
の範囲を超えられること
- デメリット
<shape>
のtopLeftRadius
プロパティのような個別に角丸のサイズを設定できないこと- xmlで直接使用できないこと
3.CardViewを使って角丸にする方法
CardView
はGoogle公式のカスタムViewで、以下2つのステップで実装できます。
1.build.gradle
でCardView
を導入する
dependencies {
implementation "androidx.cardview:cardview:1.0.0"
}
2.layout.xml
でCardView
を使う
<androidx.cardview.widget.CardView
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:cardBackgroundColor="@android:color/black">
<View
android:layout_width="match_parent"
android:layout_height="48dp" />
</androidx.cardview.widget.CardView>
CardViewを使うメリットとデメリット
- メリット
CardView
はViewOutlineProvider
に似ているが、サブViewやforeground
などがCardView
の範囲を超えられないこと
- デメリット
- 追加のライブラリをインポートする必要があること
- 角丸のサイズを個別に設定できないこと
- 本質はFrameLayoutであるので、FrameLayoutが必要ない場合はレイアウト階層が増加すること
4.カスタムViewを作って角丸にする方法
カスタムViewの実現は以下2つのステップで実装できます。
1.カスタムViewを定義する
/** カスタムViewの例の一つは下の通りに */
class CustomLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
FrameLayout(context, attrs, defStyleAttr) {
override fun dispatchDraw(canvas: Canvas) = with(context) {
val radius = 16f * v.resources.displayMetrics.density
val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
val path = Path().apply { addRoundRect(rect, radius, radius, Path.Direction.CW) }
canvas.clipPath(path)
super.dispatchDraw(canvas)
}
}
2.layout.xml
でカスタムViewを使う
<[パッケージ名].CustomLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<View
android:layout_height="48dp"
android:layout_width="match_parent"
android:background="@android:color/black" />
</[パッケージ名].CustomLayout>
カスタムViewを使うメリットとデメリット
- メリット
- 仕様に合わせて角丸のサイズだけでなく、様々な形を設定することができること
- デメリット
- 実装が非常に複雑であること
Extra. databindingを活用して、より複雑な仕様に対応する方法
コードで実装する場合
より複雑な仕様に対応する場合、上記でご紹介した各方法では簡潔さに欠けてしまいます。
そこで、databindingを活用すると3つのステップでシンプルに実装できます。
※例えば、ボーダーを描画し、クリック効果があるボタンを角丸にしたい場合等
1.まず、databindingを導入する
apply plugin: "kotlin-kapt"
android {
buildFeatures.dataBinding = true
}
2.ViewOutlineProvider
を改造するViewOutlineProvider
はxmlで直接使用できないとご紹介しましたが、このデメリットはdatabindingである程度補うことができます。
databindingのBindingAdapter
アノテーションを利用することで、xmlで使用できる属性を定義できます。
@JvmStatic
@BindingAdapter("corner")
fun View.corner(radiusPixel: Float) {
clipToOutline = true
outlineProvider = ViewOutlineProvider { setRoundRect(0, 0, it.width, it.height, radiusPixel) }
}
呼び出し方法も簡単で、app:corner="@{@dimen/corner_size}"
のように"@{...}"
でxmlに定義されたリソースを呼び出すと、databindingは自動的@dimen/corner_size
が表すdpの値をFloatのpxの値に変換することができます。
<Button
android:layout_width="match_parent"
android:layout_height="48dp"
android:foreground="?selectableItemBackground"
app:corner="@{@dimen/corner_size}" />
※直接dp値を指定する場合"@{}"
が16dp
、16sp
のような値を受け取れないので、直接dp値を入力したい場合、数字をdp値として入力し、コードの中でpixelに変換する
@JvmStatic
@BindingAdapter("corner")
fun View.corner(radiusDp: Float) {
clipToOutline = true
// dpをpixelに変換する
val radiusPixel = radiusDp * v.resources.displayMetrics.density
outlineProvider = ViewOutlineProvider { setRoundRect(0, 0, it.width, it.height, radiusPixel) }
}
<Button
android:layout_width="match_parent"
android:layout_height="48dp"
android:foreground="?selectableItemBackground"
app:corner="@{16f}" /> <!--ここで、16dp/16spのような値を受け取れない->
3.ボーダーを描画する機能を追加する
@JvmStatic
@BindingAdapter("corner", "borderColor", "borderSize")
fun View.corner(radius: Float, stroke: ColorStateList, size: Float) {
clipToOutline = true
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(v: View, outline: Outline) {
outline.setRoundRect(0, 0, v.width, v.height, radius * v.resources.displayMetrics.density)
}
}
post {
background = GradientDrawable().apply {
color = backgroundTintList
setStroke((size * resources.displayMetrics.density).roundToInt(), stroke)
cornerRadius = radius * resources.displayMetrics.density
}
backgroundTintList = null
}
}
<Button
android:layout_width="match_parent"
android:layout_height="48dp"
android:foreground="?selectableItemBackground"
app:corner="@{16f}"
app:borderSize="@{2f}"
app:borderColor="@{@color/border_color}"
android:backgroundTint="@color/background_color" />
※Styleをまとめたい場合style.xml
で設定できないので、まとめたい場合は新しいBindingAdapter
メソットを作ります。
@JvmStatic
@BindingAdapter("corner", "borderColor", "borderSize")
fun View.corner(radius: Float, stroke: ColorStateList, size: Float) {
// ...
}
@JvmStatic
@BindingAdapter("style1")
fun View.style1(radius: Float) {
backgroundTintList = ColorStateList.valueOf(context.getColor(R.color.background_color))
corner(radius, ColorStateList.valueOf(context.getColor(R.color.border_color)), 1f)
}
<Button
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="4dp"
android:foreground="?actionBarItemBackground"
app:style1="@{16f}" />
databindingを使うメリットとデメリット
- メリット
- 複雑な要件を処理する際に、コード実装の柔軟性とxmlレイアウトの簡潔性があるため、テンプレートコードを減らせること
- デメリット
- 入力値に関して、カスタムViewのカスタム属性に比べて自由度が低いこと
例えば"@{...}"
では16dp
、16sp
のような値を受け取れないことや、BindingAdapter
で拡張された属性はstyle.xml
で設定できない等
- 入力値に関して、カスタムViewのカスタム属性に比べて自由度が低いこと
まとめ
Viewを角丸にする方法はたくさんありますが、完璧な方法はないので、状況に応じて最適な方法を選択する必要があります。