在 ConstraintLayout 中放置自定义视图
Placing a Custom View inside a ConstraintLayout
我不熟悉在 Android 中实现自定义视图。我已经实现了一个自定义视图(一个简单的矩形)。这里的代码:
class CustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
// Paint styles used for rendering are initialized here. This
// is a performance optimization, since onDraw() is called
// for every screen refresh.
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create("", Typeface.BOLD)
color = Color.GREEN
}
private val rectangle = RectF(100f, 100f, 500f, 120f)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawRect(rectangle, paint)
}
}
由于我的布局文件,它应该放在屏幕中间:
<layout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CustomViewFragment">
<com.example.learningcustomviewimplementation.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
但它位于左上角附近。从我在输出中看到的,没有使用约束。我需要更改什么才能根据我在布局文件中使用的约束放置自定义视图?
据此https://developer.android.com/training/custom-views/custom-drawing#layouteevent。您需要覆盖 onSizeChanged() 或 onMeasure() 来定义您的视图的父级希望您的视图有多大。在你的代码中,你只是重写了 onDraw(),这个函数是在你的视图中绘制一个矩形,而不是定义你的视图大小。
如您所说,您的视图未正确放置到布局中,因为未指定其尺寸。当视图的 android:layout_width
and/or android:layout_height
被定义 wrap_content
时,视图本身应该计算其尺寸。这样,容器布局就知道了 child 的宽度和高度。但是,您可以通过覆盖 onMeasure
来实现此目的,如下所示。
CustomView.kt
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var viewWidth = 0
var text: String = ""
set(value) {
field = value
calculateSize()
invalidate()
}
var textSize = 20f
set(value) {
field = value
paint.textSize = value
calculateSize()
invalidate()
}
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).also {
// Paint styles used for rendering are initialized here. This
// is a performance optimization, since onDraw() is called
// for every screen refresh.
it.style = Paint.Style.FILL
it.textAlign = Paint.Align.CENTER
it.textSize = textSize
it.typeface = Typeface.create("", Typeface.BOLD)
it.color = Color.GREEN
}
init {
calculateSize()
if (isInEditMode) {
invalidate()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = paddingLeft + viewWidth + paddingRight
val height = paddingTop + textSize.toInt() + paddingBottom
setMeasuredDimension(width, height)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawText(canvas)
}
private fun calculateSize() {
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
viewWidth = bounds.width()
}
private fun drawText(canvas: Canvas) {
val x = paddingLeft + viewWidth / 2f
var y = paddingTop + textSize / 2f
paint.run {
y -= ((descent() + ascent()) / 2)
canvas.drawText(text, x, y, this)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
customView.text = "Hello World!"
customView.textSize = 80f
}
}
activity_main.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">
<com.example.learningcustomviewimplementation.CustomView
android:id="@+id/customView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#AEAEAE"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
结果:
我不熟悉在 Android 中实现自定义视图。我已经实现了一个自定义视图(一个简单的矩形)。这里的代码:
class CustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
// Paint styles used for rendering are initialized here. This
// is a performance optimization, since onDraw() is called
// for every screen refresh.
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create("", Typeface.BOLD)
color = Color.GREEN
}
private val rectangle = RectF(100f, 100f, 500f, 120f)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawRect(rectangle, paint)
}
}
由于我的布局文件,它应该放在屏幕中间:
<layout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CustomViewFragment">
<com.example.learningcustomviewimplementation.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
但它位于左上角附近。从我在输出中看到的,没有使用约束。我需要更改什么才能根据我在布局文件中使用的约束放置自定义视图?
据此https://developer.android.com/training/custom-views/custom-drawing#layouteevent。您需要覆盖 onSizeChanged() 或 onMeasure() 来定义您的视图的父级希望您的视图有多大。在你的代码中,你只是重写了 onDraw(),这个函数是在你的视图中绘制一个矩形,而不是定义你的视图大小。
如您所说,您的视图未正确放置到布局中,因为未指定其尺寸。当视图的 android:layout_width
and/or android:layout_height
被定义 wrap_content
时,视图本身应该计算其尺寸。这样,容器布局就知道了 child 的宽度和高度。但是,您可以通过覆盖 onMeasure
来实现此目的,如下所示。
CustomView.kt
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var viewWidth = 0
var text: String = ""
set(value) {
field = value
calculateSize()
invalidate()
}
var textSize = 20f
set(value) {
field = value
paint.textSize = value
calculateSize()
invalidate()
}
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).also {
// Paint styles used for rendering are initialized here. This
// is a performance optimization, since onDraw() is called
// for every screen refresh.
it.style = Paint.Style.FILL
it.textAlign = Paint.Align.CENTER
it.textSize = textSize
it.typeface = Typeface.create("", Typeface.BOLD)
it.color = Color.GREEN
}
init {
calculateSize()
if (isInEditMode) {
invalidate()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = paddingLeft + viewWidth + paddingRight
val height = paddingTop + textSize.toInt() + paddingBottom
setMeasuredDimension(width, height)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawText(canvas)
}
private fun calculateSize() {
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
viewWidth = bounds.width()
}
private fun drawText(canvas: Canvas) {
val x = paddingLeft + viewWidth / 2f
var y = paddingTop + textSize / 2f
paint.run {
y -= ((descent() + ascent()) / 2)
canvas.drawText(text, x, y, this)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
customView.text = "Hello World!"
customView.textSize = 80f
}
}
activity_main.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">
<com.example.learningcustomviewimplementation.CustomView
android:id="@+id/customView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#AEAEAE"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>