Android Kotlin:从MainActivity调用invalidate()未触发onDraw()问题
我来帮你排查这个问题!你遇到的情况其实挺常见的——把触摸逻辑从View移到Activity后,明明调用了更新坐标的方法和invalidate(),但onDraw()就是不执行,大概率是这几个核心原因,咱们一步步来解决:
1. 最常见的坑:Activity中引用的View实例不对
这是最容易犯的错误:你在Activity里获取的CustomView实例,和布局中实际显示的不是同一个!比如你可能自己new了一个CustomView但没加到布局里,或者findViewById找错了id,导致调用invalidate()的是个“无效”的实例。
解决方式:
确保在Activity的onCreate里正确绑定布局中的View:
class MainActivity : AppCompatActivity() { private lateinit var customView: YourCustomView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 这里的id必须和布局文件中你的CustomView的id一致 customView = findViewById(R.id.your_custom_view_id) } // 你的onTouchEvent逻辑 override fun onTouchEvent(event: MotionEvent): Boolean { when(event.action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { // 先确认这里调用的是上面初始化的customView实例 customView.updateCirclePos(event.x, event.y) return true } } return super.onTouchEvent(event) } }
2. 检查CustomView的方法是否正确更新成员变量并调用invalidate
要确保你的CustomView中,更新坐标的方法确实修改了类的成员变量(不是局部变量),并且调用的是View自身的invalidate():
class YourCustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) { // 用成员变量保存圆形坐标,onDraw时会用到 private var circleX = 0f private var circleY = 0f private val circlePaint = Paint().apply { color = Color.RED style = Paint.Style.FILL } // 对外提供的更新方法 fun updateCirclePos(x: Float, y: Float) { // 一定要修改成员变量! this.circleX = x this.circleY = y // 调用View的invalidate(),触发重绘 invalidate() // 加个日志确认方法被调用,同时查看View的状态 Log.d("CustomView", "updateCirclePos called, pos: ($circleX, $circleY), isVisible: $isVisible") } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // 加日志确认onDraw是否执行 Log.d("CustomView", "onDraw triggered") canvas.drawCircle(circleX, circleY, 50f, circlePaint) } }
3. 注意坐标转换的坑(虽然不影响onDraw触发,但会导致画错位置)
Activity的onTouchEvent中获取的event.x/event.y是相对于Activity的ContentView的,而View的onDraw用的是自身的局部坐标。如果你的View在布局中有margin、padding或者嵌套在其他容器里,直接传这个坐标会导致圆形位置不对,但如果只是onDraw不触发,这个不是核心原因,但也要注意:
// 如果需要转换坐标,在Activity的onTouchEvent里这样处理 val location = IntArray(2) customView.getLocationOnScreen(location) // 计算相对于CustomView的局部坐标 val viewLocalX = event.rawX - location[0] val viewLocalY = event.rawY - location[1] customView.updateCirclePos(viewLocalX, viewLocalY)
4. 检查是否在非UI线程调用了updateCirclePos
invalidate()必须在UI线程调用,如果你的updateCirclePos是在非UI线程被调用的,那invalidate()不会生效。这种情况下可以换成postInvalidate(),它可以在非UI线程触发重绘:
fun updateCirclePos(x: Float, y: Float) { this.circleX = x this.circleY = y // 非UI线程用这个代替invalidate() postInvalidate() }
5. 排查View的可见性和触摸拦截
- 如果你的CustomView的
visibility被设置为GONE或INVISIBLE,invalidate()也不会触发onDraw(),可以检查布局或代码中有没有误设置。 - 如果父布局的
onTouchEvent返回了true,会拦截触摸事件,导致Activity的onTouchEvent不被触发,但你说方法已经被调用了,这个可能性较低,但也可以排查下。
按照上面的步骤排查,应该就能解决invalidate()不触发onDraw()的问题啦!
内容的提问来源于stack exchange,提问作者Lord Imperator

