#android #kotlin
#Android #котлин
Вопрос:
Я хочу нарисовать лудодек поверх ЛудоБорда. Я создал пользовательскую группу просмотра и отключил willNotDraw
и настроил положение и размер дочернего представления, но это несколько не отображается на экране. Я видел журнал для LudoDeck onDraw
в logcat, но я не уверен, почему он не отображается, это потому, что я неправильно установил размер представления?
Может ли кто-нибудь помочь мне понять, где мои ошибки? Спасибо.
LudoBoard.kt
package io.github.andraantariksa.ludo
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
const val RATIO = 1.0f
class LudoBoard(context: Context, attributeSet: AttributeSet):
ViewGroup(context, attributeSet) {
private val ludoPawnsInLane = arrayOf<LudoPawn>()
private val totalGridToTarget = 6
private var gridSideSize = 0F
private val colors = arrayOf(Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN)
private val deck = arrayOf<LudoDeck>(
LudoDeck(context))
init {
setWillNotDraw(false)
deck.forEach {
addView(it)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
var width = measuredWidth
var height = measuredHeight
val widthWithoutPadding = width - paddingLeft - paddingRight
val heightWithoutPadding = height - paddingTop - paddingBottom
val maxWidth = (heightWithoutPadding * RATIO).toInt()
val maxHeight = (widthWithoutPadding / RATIO).toInt()
if (widthWithoutPadding > maxWidth) {
width = maxWidth paddingLeft paddingRight
} else {
height = maxHeight paddingTop paddingBottom
}
gridSideSize = width / (totalGridToTarget * 2 3).toFloat()
val deckSideSize = gridSideSize * 6F
deck.forEach {
it.measure(deckSideSize.toInt(), deckSideSize.toInt())
it.x = 0F
it.y = 0F
}
setMeasuredDimension(width, height)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// some code to draw the board
}
}
Людодек.кт
package io.github.andraantariksa.ludo
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
class LudoDeck(context: Context): View(context) {
private var totalPawn = 4
init {
setWillNotDraw(false)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas) {
Log.d("zzzzz", "Draw")
val p = Paint()
p.color = Color.BLACK
val rect = Rect(0, 0, measuredWidth, measuredHeight)
canvas.drawRect(rect, p)
}
}
Ответ №1:
Итак, в основном я немного изменил ваш код и в процессе также узнал о ViewGroups. Спасибо за это!
Я прокомментировал объяснения изменений, внесенных в код, на которые вы можете ссылаться, и если есть какие-либо сомнения, пожалуйста, не стесняйтесь спрашивать..
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.ViewGroup
const val RATIO = 1.0f
class LudoBoard1(context: Context, attributeSet: AttributeSet):
ViewGroup(context, attributeSet) {
// private val ludoPawnsInLane = arrayOf<LudoPawn>()
private val totalGridToTarget = 6
private var gridSideSize = 0F
private val colors = arrayOf(Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN)
private val deck = arrayOf<LudoDeck>(
LudoDeck(context, attributeSet), LudoDeck(context, attributeSet))
init {
//setting this will call the onDraw method of the viewGroup. If we just want to treat this as a container
//then set this as 'true'. This way it will not draw anything.
setWillNotDraw(false)
//Add all your custom views here in the beginning.
deck.forEach {
addView(it)
}
}
/**
* This method is used to measure the size of the view itself and also the size of the children.
* Here we calculate the size and allocate it to the children also by calling their "measure()"
* method.
* It's extremely important to call the "setMeasuredDimension()" at the end as this method will
* allocated the measured width and the height.
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
var width = measuredWidth
var height = measuredHeight
val widthWithoutPadding = width - paddingLeft - paddingRight
val heightWithoutPadding = height - paddingTop - paddingBottom
val maxWidth = (heightWithoutPadding * RATIO).toInt()
val maxHeight = (widthWithoutPadding / RATIO).toInt()
if (widthWithoutPadding > maxWidth) {
width = maxWidth paddingLeft paddingRight
} else {
height = maxHeight paddingTop paddingBottom
}
gridSideSize = width / (totalGridToTarget * 2 3).toFloat()
val deckSideSize = gridSideSize * 6F
deck.forEach {
it.measure(deckSideSize.toInt(), deckSideSize.toInt())
}
setMeasuredDimension(width, height)
}
/**
* For a viewGroup, its better if we don't draw anything, but still if we have to, then we can.
* The view group is designed as a container where it determines it's own size, it's children's size
* and their positions.
*/
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// some code to draw the board
}
/**
* This is the method where we calculate the positions for every child. Here we determine the
* starting and ending point for every child element of this viewGroup.
*/
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
/*
Here you will determine the position for every child. Calculate the left top right bottom for every child
and allocate it to the layout.
For now, i am just positioning the ludo deck besides each other.
*/
var previousXStartPoint = 0
deck.forEachIndexed { index, it ->
it.layout(previousXStartPoint , 0, previousXStartPoint.plus(it.measuredWidth), (it.measuredHeight))
previousXStartPoint = it.right 20
}
}
}
И это класс LudoDeck:
class LudoDeck(context: Context, attrs: AttributeSet?): View(context, attrs) {
private var totalPawn = 4
private val rect = Rect(0, 0, 50, 50)
private val p = Paint()
/*
As we are drawing something in this view, it's appropriate to set call this method in the beginning.
*/
init {
setWillNotDraw(false)
}
/**
* Its advised not to initialize anything in the onMeasure() method. So, we have already initialized
* the rect in the beginning and now we will just update its params.
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
rect.right = widthMeasureSpec
rect.bottom = heightMeasureSpec
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
}
/**
* Its also advised not to initialize anything in onDraw() method, so we have already created the paint
* object. Here we simply draw!
*/
override fun onDraw(canvas: Canvas) {
p.color = Color.BLACK
canvas.drawRect(rect, p)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
//Uncomment to check whether the dims set in onLayout of Ludo Board are properly allocated. :)
//Log.e("ludoDeck", "left: $left, top: $top, right: $right, bottom: $bottom")
}
Наконец, источник, в котором я смог понять несколько вещей и заставить это работать: https://academy.realm.io/posts/360andev-huyen-tue-dao-measure-layout-draw-repeat-custom-views-and-viewgroups-android/
Дайте мне знать, как это работает!
Комментарии:
1. Спасибо. Итак, по сути, мне нужно вызывать оба
measure
иonLayout
в представлении?2. @Andra Для пользовательского представления вы можете вызвать onMeasure -> (если вы хотите изменить вычисляемый размер представления по умолчанию) и onLayout -> (если вы хотите изменить положение представления). В принципе, для пользовательского представления onDraw наиболее важен, поскольку вы будете рисовать там свой фактический вид. А для группы представлений вам необходимо переопределить макет, измерить и рассчитать положение и размеры для дочерних элементов и самого родительского элемента.