问题
I am using a custom view to draw a game content and i use a button from xml layout to enable or disable the drawing of a specific content(rectangle) using separate thread.I managed to have the thread running but postInvalidate() method that i use is being ignored.I tried using setWillNotDraw(false) too.It isn't working.I've condensed my code,to be specific on which part of the code i have this problem.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/frm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.temp.Cview
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/bMineDetector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:text="Lock" />
</RelativeLayout>
</FrameLayout>
And this is my MainActivity.java
package com.example.temp;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener{
Button b;
Boolean lock=false;
Cview v;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
v=new Cview(this,null);
setContentView(R.layout.activity_main);
initialize();
}
private void initialize() {
// TODO Auto-generated method stub
b=(Button) findViewById(R.id.bMineDetector);
lock=false;
b.setOnClickListener(this);
}
public void updateViewStart(){
v.onStart();
}
public void updateViewStop(){
v.onPause();
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
if(v.isRunning==true)
v.onPause();
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()){
case R.id.bMineDetector:
if(lock==false){
lock=true;
updateViewStart();
b.setText("UnLock");
}else{
lock=false;
updateViewStop();
b.setText("lock");
}
break;
}
}
}
In the above class i set a onClickListener that helps to start or stop a separate thread based on the state of the button.Here is my custom view where i handle touch events and also create this thread.
Cview.java
package com.example.temp;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
public class Cview extends View implements OnTouchListener,Runnable{
Boolean isRunning=false,isLockMode=false;
Context gameContext;
Thread myThread=null;
public Cview(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
gameContext=context;
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
Log.d("INVALIDATE", "Invalidating");
isLockMode=((MainActivity)gameContext).lock;
if(isLockMode==true){
canvas.drawRect(100, 100, 300, 300, null);
//invalidate();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
while(isRunning){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
isLockMode=((MainActivity)gameContext).lock;
this.postInvalidate();
Log.d("THREAD", "isRunning="+isRunning+";;isLockMode="+isLockMode);
}
}
void onStart(){
isRunning=true;
myThread=new Thread(this);
myThread.start();
//Log.d("INVALIDATE", "onStart");
//postInvalidate();
}
void onPause(){
isRunning=false;
while(true){
try {
myThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
myThread=null;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return true;
}
}
Please anyone help me with this.I need the canvas to be redrawn once the run method is being executed.Ive been thinking about this for months and I'm new to programming too.So please guide me and suggest me if there is any other better method to do what i need.Thank you.
回答1:
As explained in What is the difference between Android's invalidate() and postInvalidate() methods?, there might be some problems when postInvalidate is used from other threads.
I haven't tested this and it's not really nice, but it could help you (instead of this.postInvalidate();
)
((MainActivity) gameContext).runOnUiThread(new Runnable() {
@Override
public void run() {
Cview.this.invalidate();
}
});
回答2:
I just ran into the exact same issue.
The fix for me was to assign the custom view using findViewById() in onCreate(), instead of calling the constructor.
Add
android:id="@+id/custom_view"
to your XML, and
v = (Cview) findViewById(R.id.custom_view);
to your onCreate() function (and remove the constructor call).
Do this after calling setContentView().
Android has gazillions of silent failure modes like this.
来源:https://stackoverflow.com/questions/21731560/postinvalidate-not-working-with-non-ui-thread