1

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.

abtdw
  • 165
  • 2
  • 12

2 Answers2

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();

       }
 });
Community
  • 1
  • 1
super-qua
  • 3,148
  • 1
  • 23
  • 30
0

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.

Gerhard Wesp
  • 319
  • 3
  • 6