【问题标题】:Custom View onDraw is called multiple times自定义视图 onDraw 被多次调用
【发布时间】:2021-11-29 13:27:13
【问题描述】:

我正在创建一个自定义 TextView,我想要一个圆形背景,在中间我想要文本的首字母。代码如下

class CircleTextView(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {

    private lateinit var circlePaint: Paint
    private lateinit var strokePaint: Paint

    private fun initViews(context: Context, attrs: AttributeSet) {
        circlePaint = Paint()
        strokePaint = Paint()
        circlePaint.apply {
            color = Color.parseColor("#041421")
            style = Paint.Style.FILL
            flags = Paint.ANTI_ALIAS_FLAG
            isDither = true
        }

        strokePaint.apply {
            color = Color.parseColor("#fe440b")
            style = Paint.Style.FILL
            flags = Paint.ANTI_ALIAS_FLAG
            isDither = true
        }
    }

    override fun draw(canvas: Canvas) {
        val diameter: Int
        val h: Int = this.height
        val w: Int = this.width
        diameter = h.coerceAtLeast(w)
        val radius: Int = diameter / 2

        canvas.drawCircle(
            radius.toFloat(), radius.toFloat(), radius.toFloat(), strokePaint
        )
        canvas.drawCircle(
            radius.toFloat(), radius.toFloat(), (radius - 10).toFloat(), circlePaint
        )
        super.draw(canvas)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val dimension = widthMeasureSpec.coerceAtLeast(heightMeasureSpec)
        super.onMeasure(dimension, dimension)
    }

    init {
        initViews(context, attrs)
        setWillNotDraw(false)
    }
}

在我的活动 xml 中

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary">

    <com.example.ui.views.CircleTextView
        android:id="@+id/circleText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="@color/colorWhite"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" /> />

</androidx.constraintlayout.widget.ConstraintLayout>

在 Kotlin 文件中

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)

        circleText.apply {
            text = "II"
            setPadding(50, 50, 50, 50)
        }
    }

我的问题是视图的右侧似乎被裁剪了,而且带有字母的文本不在视图的中心。

我该如何解决?

【问题讨论】:

    标签: android android-canvas android-custom-view ondraw


    【解决方案1】:

    您的问题的主要答案是肯定的:您的onDraw(Canvas) 实现存在无限循环,您在TextView 上调用setWidth(Int)setHeight(Int),内部调用invalidate(),然后调用你的onDraw(Canvas) 再次继续循环。

    您应该将所有 TextView.setXxx(yyy) 调用 (this.xxx = yyy) 移到 onDraw 实现之外。

    例如,如果您想强制 View 具有相同的宽度和高度,则应改为覆盖 onMeasure

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // This example will always make the height equivalent to its width
        super.onMeasure(widthMeasureSpec, widthMeasureSpec); 
    }
    

    设置您的画图(即 circlePaint.apply { ... } 应移至您的 initViews 实现,因为它们是不变的。

    由于您也只使用 1 种文本颜色,因此您还应该在您的 initViews 实现中设置该值(或者,最好让它由用于构造视图的 AttributeSet 继承)。

    如果您仍然要使用 TextView 的默认 setText(String)(即 this.text = mText),您也可以直接设置它并完全删除您的自定义 mText

    fun setCustomText(value: String) {
        this.text = value.toSplitLetterCircle()
    }
    

    您的文本大小自定义也是如此:

    fun setCustomTextSize(value: Float) {
        this.textSize = value
    }
    

    上述两个函数随后都已过时,可以替换为标准的 TextView setTextsetTextSize 调用。

    最后,您希望向半径添加一些数字,这意味着您实际上希望视图的值大于当前正在呈现的值。由于您已经使用 TextView 作为基类,因此您可以使用 View 的 Padding 属性在 View 的边缘和内容的开头之间添加空间,例如

    // Order is left, top, right, bottom, units are in px
    this.setPadding(5, 5, 5, 5)
    

    剩下的就是:

    override fun draw(canvas: Canvas) {
        val h: Int = this.height
        val w: Int = this.width
        diameter = Math.max(h, w)
        val radius: Int = diameter / 2
    
        canvas.drawCircle(
                (diameter / 2).toFloat(), (diameter / 2).toFloat(), radius.toFloat(), strokePaint
        )
    
        canvas.drawCircle(
                (diameter / 2).toFloat(), (diameter / 2).toFloat(), (radius - 5).toFloat(), circlePaint
        )
    
        super.draw(canvas)
    }
    

    由于您的onDraw(Canvas) 中剩下的所有内容都是在背景中绘制 2 个圆圈的调用,因此您实际上可以完全替换它 with a custom background drawable,如下所示:

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="oval">
        <solid android:color="#041421"/>
        <stroke
            android:width="2dp"
            android:color="#FFFFFF"/>
    </shape>
    

    这意味着您可以完全删除该自定义 onDraw(Canvas) 实现

    编辑回答问题第 2 部分:

    我最初在 onMeasure 中使用填充而不考虑它的建议是不完整的,并且肯定会产生您所看到的效果。

    传递给onMeasure(int, int) 的单位也不是真正的宽度/高度单位,而是可以通过MeasureSpec 类访问的多个组件(大小和模式)。 This post 更详细地介绍了这一切是如何工作的。这很重要,因为这就是您的视图没有真正显示为完美正方形的原因,也是文本不在您绘制的圆圈内居中的原因。

    这会导致diameter = h.coerceAtLeast(w) 等于h,根据屏幕截图,它大于w,这就是为什么你的圆圈超出了水平面的界限,而不是垂直面的界限。

    另外请注意,您也可以使用android:padding-related 属性直接在 XML 布局中应用填充

    【讨论】:

    • 你给我的解释真的帮助了我,解决了我的很多问题。我听从了您的建议,现在更清楚我要做什么以及如何做。但是,我遇到了另一个问题,如果您能帮助我,我将不胜感激!我已经编辑了这个问题,所以你可以看看。谢谢
    • 很高兴我能帮上忙!我已经用一些额外的信息和一个链接更新了答案,我认为应该为您指明正确的方向。不过,一般来说,如果您有超出原始范围的新问题,创建一个更针对新问题的新问题会更简洁
    • 优秀。我几乎明白我需要什么。感谢您的宝贵意见和解释!
    猜你喜欢
    • 1970-01-01
    • 2012-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-10
    • 1970-01-01
    相关资源
    最近更新 更多