自定义View onDraw被多次调用
Custom View onDraw is called multiple times
我正在创建一个自定义 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)
}
}
在我的activityxml
里面
<?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)
}
}
我的问题是视图的右侧似乎被裁剪了,而且带有字母的文本不在视图的中心。
我该如何解决?
您的问题的主要答案是肯定的:在您的 onDraw(Canvas)
实现中有一个无限循环,您在 TextView
上调用 setWidth(Int)
和 setHeight(Int)
,它在内部调用 invalidate()
,然后再次调用您的 onDraw(Canvas)
以继续循环。
您应该将所有 TextView.setXxx(yyy)
调用 (this.xxx = yyy
) 移到 onDraw
实现之外。
例如,如果你想强制你的视图具有相同的宽度和高度,你应该覆盖 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
实现,因为它们没有变化。
由于您也只使用一种文本颜色,因此您还应该在 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 setText
和 setTextSize
调用替换。
最后,您想要向半径添加一些数字,这意味着您实际上希望视图的值大于当前渲染的值。因为您已经使用 TextView 作为基础 class,您可以使用视图的 Padding
属性在视图的边缘和内容的开始之间添加 space,例如
// 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)
的单位也不是真正的 width/height 单位,而是可以通过 MeasureSpec
class 访问的多个组件(大小和模式)在一起. This post 详细介绍了这一切的工作原理。这很重要,因为这是您的视图没有真正显示为完美正方形的原因,也是文本未在您绘制的圆圈内居中的原因。
然后导致 diameter = h.coerceAtLeast(w)
等于 h
,根据屏幕截图,h
大于 w
,这就是为什么你的圆超出了水平面的界限,但是不是垂直的。
另请注意,您也可以使用 android:padding
相关属性
直接在 XML 布局中应用内边距
我正在创建一个自定义 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)
}
}
在我的activityxml
里面<?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)
}
}
我的问题是视图的右侧似乎被裁剪了,而且带有字母的文本不在视图的中心。
我该如何解决?
您的问题的主要答案是肯定的:在您的 onDraw(Canvas)
实现中有一个无限循环,您在 TextView
上调用 setWidth(Int)
和 setHeight(Int)
,它在内部调用 invalidate()
,然后再次调用您的 onDraw(Canvas)
以继续循环。
您应该将所有 TextView.setXxx(yyy)
调用 (this.xxx = yyy
) 移到 onDraw
实现之外。
例如,如果你想强制你的视图具有相同的宽度和高度,你应该覆盖 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
实现,因为它们没有变化。
由于您也只使用一种文本颜色,因此您还应该在 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 setText
和 setTextSize
调用替换。
最后,您想要向半径添加一些数字,这意味着您实际上希望视图的值大于当前渲染的值。因为您已经使用 TextView 作为基础 class,您可以使用视图的 Padding
属性在视图的边缘和内容的开始之间添加 space,例如
// 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)
的单位也不是真正的 width/height 单位,而是可以通过 MeasureSpec
class 访问的多个组件(大小和模式)在一起. This post 详细介绍了这一切的工作原理。这很重要,因为这是您的视图没有真正显示为完美正方形的原因,也是文本未在您绘制的圆圈内居中的原因。
然后导致 diameter = h.coerceAtLeast(w)
等于 h
,根据屏幕截图,h
大于 w
,这就是为什么你的圆超出了水平面的界限,但是不是垂直的。
另请注意,您也可以使用 android:padding
相关属性