Пользовательский вид в группе пользовательских видов не отображается

#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 наиболее важен, поскольку вы будете рисовать там свой фактический вид. А для группы представлений вам необходимо переопределить макет, измерить и рассчитать положение и размеры для дочерних элементов и самого родительского элемента.