Android Studio中调用invalidate()刷新CanvasView时点击按钮崩溃
嘿,我来帮你搞定这个崩溃问题~从你提供的LogCat信息里,一眼就能看到关键错误:
java.lang.NullPointerException: Attempt to read from field 'boolean com.example.andreas.drawing_exp.CanvasView.colorRed' on a null object reference
说白了就是你点击按钮调用canvasView.invalidate()的时候,canvasView是个null对象,自然会崩溃!
问题根源
你在MainActivity里犯了一个新手常遇到的变量作用域错误:
- 类顶部声明了成员变量
CanvasView canvasView; - 但在
onCreate()里又重新声明了一个局部变量:CanvasView canvasView = new CanvasView(this, null);
这就导致成员变量canvasView根本没被初始化,一直是null;而你新创建的CanvasView只是个局部对象,既没添加到布局里显示,也没赋值给成员变量,点击按钮时调用的还是null的成员变量。
而且你移除canvasView.invalidate()后不崩溃,只是因为没去调用null对象的方法而已,本质问题(canvasView未正确初始化)还是存在的。
另外要明确一点:从其他类调用View的invalidate()是完全允许的,只要这个View对象是有效的(已初始化并添加到视图树中),你的问题和调用本身无关,只是对象为null。
修复方案
这里给你两种可行的修复方式:
方式1:动态添加CanvasView到布局
修改MainActivity的onCreate()方法,去掉局部变量的声明,把创建的CanvasView赋值给成员变量,再添加到布局中:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { CanvasView canvasView; Button btnChangeColor; static boolean colorRed; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 去掉局部变量声明,直接初始化成员变量 canvasView = new CanvasView(this, null); // 获取布局的根容器(假设你的activity_main根布局是LinearLayout,id为root_layout) LinearLayout rootLayout = findViewById(R.id.root_layout); // 把CanvasView添加到布局里 rootLayout.addView(canvasView); btnChangeColor = findViewById(R.id.button); btnChangeColor.setOnClickListener(this); colorRed = false; } @Override public void onClick(View v) { colorRed = !colorRed; // 简化写法,不用if-else canvasView.invalidate(); } }
记得在你的activity_main.xml里给根布局加上id:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Change Color"/> </LinearLayout>
方式2:在XML布局中直接定义自定义View
这种方式更符合Android开发的常规做法,新手也更容易理解:
- 修改
activity_main.xml,直接添加你的自定义CanvasView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Change Color"/> <!-- 添加自定义CanvasView --> <com.example.andreas.drawing_exp.CanvasView android:id="@+id/canvas_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> </LinearLayout>
- 修改MainActivity的
onCreate(),直接通过findViewById获取CanvasView:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { CanvasView canvasView; Button btnChangeColor; static boolean colorRed; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 直接从布局中获取CanvasView canvasView = findViewById(R.id.canvas_view); btnChangeColor = findViewById(R.id.button); btnChangeColor.setOnClickListener(this); colorRed = false; } @Override public void onClick(View v) { colorRed = !colorRed; canvasView.invalidate(); } }
额外小建议
- 尽量不要用静态变量
colorRed来传递状态,你可以给CanvasView添加一个公共方法,比如setCircleColor(boolean isRed),然后在方法里设置颜色并调用invalidate(),这样代码更解耦,也更符合面向对象的设计。 - 自定义View里的
onDraw()方法,尽量不要直接访问外部类的静态变量,避免耦合问题。
内容的提问来源于stack exchange,提问作者Andryuser

