Android:Canvasで線をひく(drawLineする)ときにハマったポイント
はじめに
Androidアプリを作っている際に、Canvasをつかって線をひくときにハマってしまったポイントについて解説します。
ハマってしまったポイントとは、複数本の線を順番に引くときの座標系の指定方法です。
結論から述べると、中心線を意識して座標系を考える必要ありました。
サンプルコードでは、下記のような複数本の線を画面の幅いっぱいに、順々に、ひくことを考えてみます。
解説
サンプルコード
全体のサンプルコード全体は下記のとおりです。
MainActivity.kt
private lateinit var binding: ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
requestFullScreen()
val canvas = drawMultipleLines(this)
binding.mainActivityLayout.addView(canvas)
}
private fun requestFullScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
window.decorView.windowInsetsController?.show(
WindowInsets.Type.systemBars()
// WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()
)
}else{
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
}
}
}
class drawMultipleLines(context: Context) : View(context){
private val paint: Paint = Paint()
private val paint2: Paint = Paint()
private val paint3: Paint = Paint()
private val baseLineWidth = 100F
private val LineWidth1 = 3F * baseLineWidth
private val LineWidth2 = 1F * baseLineWidth
private val LineWidth3 = 2F * baseLineWidth
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
paint.setStrokeWidth(LineWidth1)
paint.setColor(ContextCompat.getColor(context, R.color.purple_200))
paint.setStyle(Paint.Style.STROKE)
canvas?.drawLine(0F, LineWidth1/2,
width.toFloat(), LineWidth1/2, paint);
paint2.setStrokeWidth(LineWidth2)
paint2.setColor(ContextCompat.getColor(context, R.color.purple_500))
paint2.setStyle(Paint.Style.STROKE)
canvas?.drawLine(0F, LineWidth1 + LineWidth2/2,
width.toFloat(), LineWidth1 + LineWidth2/2, paint2);
paint3.setStrokeWidth(LineWidth3)
paint3.setColor(ContextCompat.getColor(context, R.color.purple_700))
paint3.setStyle(Paint.Style.STROKE)
canvas?.drawLine(0F, LineWidth1 + LineWidth2 + LineWidth3/2,
width.toFloat(), LineWidth1 + LineWidth2 + LineWidth3/2, paint3);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mainActivityLayout"
tools:context=".MainActivity"/>
本サプルコードではviewBindingを使用しているので、build.gradle(Module)内でviewBindingを有効にしています。
build.gradle
android {
//...省略...
viewBinding {
enabled = true
}
//...省略...
}
viewBindingをつかってlayoutを表示
まずbindingクラスをlateinitで宣言しています。
private lateinit var binding: ActivityMainBinding
そして、onCreate内でActivityで使用するbindingクラスのインスタンスを作成します。
こちらの詳細な説明は、Android developesの「バインディングオブジェクトを作成する」に記載があります。
その後、binding.root
でルートビューを取得し、setContentView
にセットすることでlayoutをActivityに表示させます。
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
画面のフルスクリーン化
わかりやすくするため、ステータスバーやナビゲーションバーを非表示(フルスクリーン化)にします。
Android OSのバージョンによって、フルスクリーン化の処理を分けています。
private fun requestFullScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
window.decorView.windowInsetsController?.show(
WindowInsets.Type.systemBars()
// WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()
)
}else{
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
}
}
Android 11以上ではWindowInsetsController
を使い、Android 4.1 ~ 10ではsetSystemUiVisibility
をつかって実現しています。
Canvasで線をひく
Canvasで線をひくdrawMultipleLines関数について説明します。
線をひく際の流れは下記のとおりです。
- Paintクラスのインスタンスを作成
- 線の幅、カラー、描画スタイルをセット
-
drawLine
で描画
// Paintクラスのインスタンスを作成
private val paint: Paint = Paint()
// 線幅を設定
private val baseLineWidth = 100F
private val LineWidth1 = 3F * baseLineWidth
//...省略...
override fun onDraw(canvas: Canvas?){
super.onDraw(canvas)
// 線幅をセット
paint.setStrokeWidth(LineWidth1)
// 線の色をセット
paint.setColor(ContextCompat.getColor(context, R.color.purple_200))
/*
描画スタイルの定数を指定
Paint.Style.STROKE : ライン
Paint.Style.FILL_AND_STROKE : 塗りつぶしとライン
Paint.Style.FILL : 塗りつぶし
*/
paint.setStyle(Paint.Style.STROKE)
// 線をひく
// drawLine(float startX(X座標の開始点), float startY(Y座標の開始点), float stopX(X座標の終了点), float stopY(Y座標の終了点), Paint paint(Paintクラスのインスタンス))
canvas.drawLine(0F, LineWidth1/2,
width.toFloat(), LineWidth1/2, paint);
//...省略...
ここまで1本の線をひく手順です。
ここで注意が必要なのは、canvas.drawLine
の座標設定です。
下記の図のように、線の中心線を意識して座標を設定します。
それぞれの線の開始点は図中の緑点になります。
それを表現すると、サンプルコードでは下記のコードに相当します。
canvas.drawLine(0F, LineWidth1/2,width.toFloat(), LineWidth1/2, paint)
canvas.drawLine(0F, LineWidth1 + LineWidth2/2,width.toFloat(), LineWidth1 + LineWidth2/2, paint2)
canvas.drawLine(0F, LineWidth1 + LineWidth2 + LineWidth3/2,width.toFloat(), LineWidth1 + LineWidth2 + LineWidth3/2, paint3)
これで、複数線を順々にひくことができます。
おわりに
最初、Canvasを扱うとき座標系に戸惑いましたが、一度理解してしまえば簡単です。
Discussion