无法在 Android 中设置自定义图像视图的图像资源
Can not set image resource of a custom imageview in Android
我根据在 this and this codelab 中学到的经验创建了一个自定义视图项目。
在我的项目中,我尝试不仅在视图上绘制,而且在自定义 ImageView
上绘制。因此,我创建了一个自定义 ImageView
并执行了上述官方代码实验室中的所有步骤。
这是我的习惯 ImageView
class:
// Stroke width for the the paint.
private const val STROKE_WIDTH = 12f
class MyImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
private var path = Path()
private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)
private lateinit var extraCanvas : Canvas
private lateinit var extraBitmap : Bitmap
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop
private var currentX = 0f
private var currentY = 0f
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
// Set up the paint with which to draw.
private val paint = Paint().apply {
color = drawColor
// Smooths out edges of what is drawn without affecting shape.
isAntiAlias = true
// Dithering affects how colors with higher-precision than the device are down-sampled.
isDither = true
style = Paint.Style.STROKE // default: FILL
strokeJoin = Paint.Join.ROUND // default: MITER
strokeCap = Paint.Cap.ROUND // default: BUTT
strokeWidth = STROKE_WIDTH // default: Hairline-width (really thin)
}
init{
init()
}
private fun init(){
setOnTouchListener(OnTouchListener { _, event ->
event?.let {
motionTouchEventX = it.x
motionTouchEventY = it.y
when(it.action){
MotionEvent.ACTION_DOWN -> touchStart()
MotionEvent.ACTION_MOVE -> touchMove()
MotionEvent.ACTION_UP -> touchUp()
}
return@OnTouchListener true
}
false
})
}
override fun onDraw(canvas: Canvas?) {
canvas?.drawBitmap(extraBitmap, 0f, 0f, null)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (::extraBitmap.isInitialized) extraBitmap.recycle()
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(extraBitmap)
}
private fun touchStart() {
path.reset()
path.moveTo(motionTouchEventX, motionTouchEventY)
currentX = motionTouchEventX
currentY = motionTouchEventY
}
private fun touchMove() {
val dx = Math.abs(motionTouchEventX - currentX)
val dy = Math.abs(motionTouchEventY - currentY)
if (dx >= touchTolerance || dy >= touchTolerance) {
// QuadTo() adds a quadratic bezier from the last point,
// approaching control point (x1,y1), and ending at (x2,y2).
path.quadTo(currentX, currentY, (motionTouchEventX + currentX) / 2, (motionTouchEventY + currentY) / 2)
currentX = motionTouchEventX
currentY = motionTouchEventY
// Draw the path in the extra bitmap to save it.
extraCanvas.drawPath(path, paint)
}
// Invalidate() is inside the touchMove() under ACTION_MOVE because there are many other
// types of motion events passed into this listener, and we don't want to invalidate the
// view for those.
invalidate()
}
private fun touchUp() {
// Reset the path so it doesn't get drawn again.
path.reset()
}
}
这就是我的 XML 布局 MainActivity
的样子
(在代码实验室中没有人,他们只是以编程方式使用自定义视图):
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.celik.abdullah.drawingonimageview.MyImageView
android:id="@+id/myImageView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
这是我的 MainActivity.kt
class:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.myImageView.setImageResource(R.drawable.ic_launcher_foreground)
}
}
我的问题是:没有显示我要绘制的图像资源。
绘图部分的工作方式与代码实验室中的一样。但是我想仅用于测试目的的 R.drawable.ic_launcher_foreground
可绘制对象没有显示在屏幕上。为什么?
希望有人能帮忙。
在自定义视图中,canvas 仅用于绘制由 extraCanvas
更新的 extraBitmap
,因此 MyImageView
仅处理 extraBitmap
的绘制extraBitmap
.
setImageResource
属于 ImageView
class,它在内部处理资源到可绘制对象的转换并将其绘制在 canvas 内部 onDraw()
但是onDraw
被不处理接收到的位图绘图的自定义视图覆盖,因此解决方案是将 super.onDraw
调用为:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// ^^^^^^^^^^^^^^^^ add this to execute imageview's onDraw for image handling
canvas?.drawBitmap(extraBitmap, 0f, 0f, null)
}
或者,您可以 override
setImageResource
并添加代码以通过 setImageResource
方法在 onDraw
的 canvas
中绘制接收到的资源 MyImageView
.
您也可以使用 setBackgroundResource(..)
:
来实现
binding.myImageView.setBackgroundResource(R.drawable.ic_launcher_foreground)
所以您的 MainActivity 看起来像:
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.myImageView.setBackgroundResource(R.drawable.ic_launcher_foreground)
}
}
使用 purple
绘制颜色,稍微绘制一下并使用 ic_launcher_foreground
作为背景图像后,结果会是这样的:
不要忘记,正如@Pavneet_Singh所建议的:
call super on the onDraw method you override (as in
super.onDraw(canvas))
我根据在 this and this codelab 中学到的经验创建了一个自定义视图项目。
在我的项目中,我尝试不仅在视图上绘制,而且在自定义 ImageView
上绘制。因此,我创建了一个自定义 ImageView
并执行了上述官方代码实验室中的所有步骤。
这是我的习惯 ImageView
class:
// Stroke width for the the paint.
private const val STROKE_WIDTH = 12f
class MyImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
private var path = Path()
private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)
private lateinit var extraCanvas : Canvas
private lateinit var extraBitmap : Bitmap
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop
private var currentX = 0f
private var currentY = 0f
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
// Set up the paint with which to draw.
private val paint = Paint().apply {
color = drawColor
// Smooths out edges of what is drawn without affecting shape.
isAntiAlias = true
// Dithering affects how colors with higher-precision than the device are down-sampled.
isDither = true
style = Paint.Style.STROKE // default: FILL
strokeJoin = Paint.Join.ROUND // default: MITER
strokeCap = Paint.Cap.ROUND // default: BUTT
strokeWidth = STROKE_WIDTH // default: Hairline-width (really thin)
}
init{
init()
}
private fun init(){
setOnTouchListener(OnTouchListener { _, event ->
event?.let {
motionTouchEventX = it.x
motionTouchEventY = it.y
when(it.action){
MotionEvent.ACTION_DOWN -> touchStart()
MotionEvent.ACTION_MOVE -> touchMove()
MotionEvent.ACTION_UP -> touchUp()
}
return@OnTouchListener true
}
false
})
}
override fun onDraw(canvas: Canvas?) {
canvas?.drawBitmap(extraBitmap, 0f, 0f, null)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (::extraBitmap.isInitialized) extraBitmap.recycle()
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(extraBitmap)
}
private fun touchStart() {
path.reset()
path.moveTo(motionTouchEventX, motionTouchEventY)
currentX = motionTouchEventX
currentY = motionTouchEventY
}
private fun touchMove() {
val dx = Math.abs(motionTouchEventX - currentX)
val dy = Math.abs(motionTouchEventY - currentY)
if (dx >= touchTolerance || dy >= touchTolerance) {
// QuadTo() adds a quadratic bezier from the last point,
// approaching control point (x1,y1), and ending at (x2,y2).
path.quadTo(currentX, currentY, (motionTouchEventX + currentX) / 2, (motionTouchEventY + currentY) / 2)
currentX = motionTouchEventX
currentY = motionTouchEventY
// Draw the path in the extra bitmap to save it.
extraCanvas.drawPath(path, paint)
}
// Invalidate() is inside the touchMove() under ACTION_MOVE because there are many other
// types of motion events passed into this listener, and we don't want to invalidate the
// view for those.
invalidate()
}
private fun touchUp() {
// Reset the path so it doesn't get drawn again.
path.reset()
}
}
这就是我的 XML 布局 MainActivity
的样子
(在代码实验室中没有人,他们只是以编程方式使用自定义视图):
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.celik.abdullah.drawingonimageview.MyImageView
android:id="@+id/myImageView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
这是我的 MainActivity.kt
class:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.myImageView.setImageResource(R.drawable.ic_launcher_foreground)
}
}
我的问题是:没有显示我要绘制的图像资源。
绘图部分的工作方式与代码实验室中的一样。但是我想仅用于测试目的的 R.drawable.ic_launcher_foreground
可绘制对象没有显示在屏幕上。为什么?
希望有人能帮忙。
在自定义视图中,canvas 仅用于绘制由 extraCanvas
更新的 extraBitmap
,因此 MyImageView
仅处理 extraBitmap
的绘制extraBitmap
.
setImageResource
属于 ImageView
class,它在内部处理资源到可绘制对象的转换并将其绘制在 canvas 内部 onDraw()
但是onDraw
被不处理接收到的位图绘图的自定义视图覆盖,因此解决方案是将 super.onDraw
调用为:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// ^^^^^^^^^^^^^^^^ add this to execute imageview's onDraw for image handling
canvas?.drawBitmap(extraBitmap, 0f, 0f, null)
}
或者,您可以 override
setImageResource
并添加代码以通过 setImageResource
方法在 onDraw
的 canvas
中绘制接收到的资源 MyImageView
.
您也可以使用 setBackgroundResource(..)
:
binding.myImageView.setBackgroundResource(R.drawable.ic_launcher_foreground)
所以您的 MainActivity 看起来像:
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.myImageView.setBackgroundResource(R.drawable.ic_launcher_foreground)
}
}
使用 purple
绘制颜色,稍微绘制一下并使用 ic_launcher_foreground
作为背景图像后,结果会是这样的:
不要忘记,正如@Pavneet_Singh所建议的:
call super on the onDraw method you override (as in super.onDraw(canvas))