Getting surfaceview's height and width while implementing runnable - android

I've read many threads here which discuss how to get view size at runtime yet none of the solutions have worked for me.
GameScreen.java
public class GameScreen extends AppCompatActivity{
// Declare an instance of SnakeView
GameView snakeView;
SurfaceHolder surface;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_screen);
snakeView = (GameView) findViewById(R.id.GameView);
surface = snakeView.getHolder();
snakeView = new GameView(this, surface);
}
where GameView is a view class extending surfaceview. The following is a simplified version of my code detailing the issue. I've omitted the run() method and many others to avoid confusion.
GameView.java
public class GameView extends SurfaceView implements Runnable {
public GameView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GameView(Context context, SurfaceHolder surfaceholder) {
super(context);
init();
m_context = context;
// Initialize the drawing objects
m_Holder = surfaceholder;
m_Paint = new Paint();
}
private void init(){
surfaceHolder = getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
}
#Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
m_Screenheight = height;
m_Screenwidth = width;
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
});
}
However calling getWidth() and getHeight() causes the app to crash.
I know that you have to wait for the view to layout but I've tried all the suggestions but I've tried all the suggestions from other threads to no avail.
My main lack of understanding comes from the fact that I am using implements Runnable in my custom class so I'm not sure where I am allowed to use getWidth or a similar method. I'm new to android in general so please be explicit in your solution.
EDIT:
I should mention that I am using the width and height to form a grid to draw in the surfaceview.
EDIT 2:
See revised code.

If your surface is full screen, you can get the screen sizes.
public GameView(Context context, SurfaceHolder surfaceholder) {
super(context);
init();
m_context = context;
// Initialize the drawing objects
m_Holder = surfaceholder;
m_Paint = new Paint();
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity)context).getWindowManager()
.getDefaultDisplay()
.getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels;
m_ScreenHeight= height;
m_ScreenWidth= width;
}

Related

How to render SurfaceView cropped to a circle?

I want to render a camera preview inside a circle, like this:
I have it working inside a fragment added to an activity. But I would like to show the view above other apps by adding it to the WindowManager.
So what I have working is showing views above other apps but the SurfaceView does not clip at all. And separately I have the SurfaceView clipping to circle, but in a fragment.
I have read many answers here and articles about this topic. There is very little info about cropping a SurfaceView, and no library that does what I want. Most of the info I found only explains how to crop it to a square or some other rectangle.
How it looks when added to WindowManager
I am trying to dra
How can I clip a SurfaceView that is shown above all views?
SurfaceView subclass
public class CircleSurface extends SurfaceView {
private Path clipPath;
public CircleSurface(Context context) {
super(context);
init();
}
public CircleSurface(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CircleSurface(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setZOrderMediaOverlay(true);
setWillNotDraw(false);
SurfaceHolder sfhTrackHolder = getHolder();
sfhTrackHolder.setFormat(PixelFormat.TRANSPARENT);
clipPath = new Path();
//TODO: define the circle you actually want
clipPath.addCircle(710, 330, 250, Path.Direction.CW);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.clipPath(clipPath);
super.dispatchDraw(canvas);
}
}
Service that adds views to the window manager
public class LiveStreamingService extends Service implements SurfaceHolder.Callback {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(startId, getNotification());
}
sharedInstance = this;
// android shouldn't auto restart this service
return START_NOT_STICKY;
}
#Override
public void onCreate() {
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
// camera view
CircleSurface cameraSurfaceView = new CircleSurface(this);
cameraSurfaceView.getHolder().addCallback(this);
wm.addView(cameraSurfaceView, getLayoutParams(360, 270));
}
private WindowManager.LayoutParams getLayoutParams(int width, int height) {
int overlayType = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O
? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
: WindowManager.LayoutParams.TYPE_PHONE;
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
width,
height,
overlayType,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
);
return layoutParams;
}
// ...
}
UPDATE
Seems like the camera rendering is somehow separate from the dispatchDraw method. I can do whatever in dispatchDraw and the camera keeps showing as a rectangle.
Created a drawing thread for the surface view that listens to the camera preview callback with OnPreviewFrame.
Inside the thread's drawing loop:
c.drawColor(0, PorterDuff.Mode.CLEAR);
c.drawColor(0x1000000); // magical color that removes everything (like transparency)
if (currentFrame == null) return;
c.clipPath(circleSurfaceView.path);
c.drawBitmap(currentFrame, 0, 0, paint);
I have no idea what the magical color is. I came across it by luck. Everything else I tried would just draw black or fuzzy noise.

Android Developing: Canvas not displayed correctly

i'm new to android developing and tried to implement a simple game realized with a surface view and a canvas. I used the LunarView sample as a draft.
The idea is to have a human (movement controlled by touch event) and a zombie that follows him.
So far I have the following classes (i removed the from my point of view useless code):
1) MainActivity
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
2) ZombieView with the innner class ZombieThread and Person (Person is only used to save data needly)
public class ZombieView extends SurfaceView implements SurfaceHolder.Callback {
//Thread Class that performs drawing on Canvas
class ZombieThread extends Thread {
class Person {
...
}
private final SurfaceHolder surfaceHolder;
private ZombieView zombieView;
private Context context;
private boolean running = false;
//Canvas info
private int mCanvasWidth = 1;
private int mCanvasHeight = 1;
private Bitmap mBackgroundImage;
private final Drawable mHumanImage;
private final Drawable mZombieImage;
private Resources res;
//Game Variables
double hSpeed = 1;
double zSpeed = 0.8;
Person zombie;
Person human;
public ZombieThread(SurfaceHolder holder, ZombieView zombie, Context context) {
this.surfaceHolder = holder;
this.zombieView = zombie;
this.context = context;
this.zombie = new Person(100, 100, hSpeed);
this.zombie.setRunning(true); //zombie always walks
this.human = new Person(100, 200, zSpeed);
res = context.getResources();
mHumanImage = context.getResources().getDrawable(R.drawable.green_circle);
mZombieImage = context.getResources().getDrawable(R.drawable.red_circle);
mBackgroundImage = BitmapFactory.decodeResource(res,R.drawable.background);
}
#Override
public void run() {
Canvas c = null;
while (this.running) {
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
play();
onDraw(c);
checkCollision();
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void checkCollision() {
//do something
}
public void play() {
...
}
public void onDraw(Canvas c) {
//Do your drawing here...
// c.drawColor(Color.rgb(10, 0, 0));
c.drawBitmap(mBackgroundImage, 0, 0, null);
c.save();
//draw human and zombie
int width = mHumanImage.getIntrinsicWidth();
int height = mHumanImage.getIntrinsicHeight();
mHumanImage.setBounds((int) human.getX() - (width / 2), (int) human.getY() - (height / 2),
(int) human.getX() + (width / 2), (int) human.getY() + (height / 2));
mHumanImage.draw(c);
width = mZombieImage.getIntrinsicWidth();
height = mZombieImage.getIntrinsicHeight();
mZombieImage.setBounds((int) zombie.getX() - (width / 2), (int) zombie.getY() - (height / 2),
(int) zombie.getX() + (width / 2), (int) zombie.getY() + (height / 2));
mZombieImage.draw(c);
c.restore();
}
public boolean doTouchEvent(MotionEvent e) {
//do something
}
}
//ZombieView variables
private ZombieThread thread;
private final SurfaceHolder surfaceHolder;
private Context context;
public ZombieView(Context context) {
super(context);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
this.context = context;
setFocusable(true); //makes sure we receive key-events
}
public ZombieView(Context context, AttributeSet attrs) {
super(context, attrs);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
this.context = context;
setFocusable(true); //makes sure we receive key-events
}
public ZombieView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
this.context = context;
setFocusable(true); //makes sure we receive key-events
}
public ZombieThread getThread() {
return thread;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new ZombieThread(surfaceHolder, this, context);
thread.setRunning(true);
thread.start();
}
}
XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin" tools:context=".MainActivity">
<com.example.zombierun.zombierun.ZombieView android:background="#ccc"
android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="20dp"
android:paddingBottom="40dp"
android:focusable="true"
android:visibility="visible"
android:id="#+id/customView" />
So my question now is the following:
When I start the App I see the Activity and a grey area which should be the canvas / surfaceview. Unfortunately that's all. But when I minimize the app by pushing the home button I see the canvas (with the images and background color) for the time the app minimizes. There are no errors displayed in logcat.
I assume that the canvas somehow stays in the background or that there is an overlay between different vies? Is this possible?
Hope someone can help me displaying the canvas correctly.
Thanks in advance
You cannot use android:background="x" when working with a SurfaceView. You have to manually draw the color, in this case canvas.drawColor(Color.GREY) in your onDraw(Canvas c) call. Otherwise what you are doing is having the background color you set draw over your the surface of your surface view, rendering it an opaque grey. When onPause is called, the view is torn down and you momentarily are able to see the surface underneath the view's window overlay which painted #ccc.
Also: why are you sleeping the thread for 1/10 of a second every frame? You should remove this line. Or if you are trying to target a frame rate: realize 1000 ms / 100 ms = 10 frames, that is, you are drawing at ten fps.

Why onDraw is not called after invalidate()?

I have found many posts on stackoverflow but I still cannot solve my problem. Here is my code piece:
public class MyView extends RelativeLayout {
Button b1;
Button b2;
Context sContext;
public static int i = 0;
private int w = 400;
private int h = 400;
private int w2 = 100;
private int h2 = 100;
public MyView(Context context) {
super(context);
sContext = context;
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
sContext = context;
init();
}
private void init() {
b1 = new Button(sContext);
addView(b1);
b1.setBackgroundColor(Color.YELLOW);
b1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (w >= 600) {
MyView.this.setBackgroundColor(Color.GREEN);
//b1.setBackgroundColor(Color.RED);
} else {
MyView.this.setX(100);
}
MyView.this.invalidate();
w += 100;
w2 += 20;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//b1.setBackgroundColor(Color.RED);
Toast.makeText(sContext, ""+i, Toast.LENGTH_SHORT).show();
++i;
}
}
Would you please explain why onDraw is not called the first three times I press b1? Because I called invalidate everytime I press b1. Thank you very much!
By default all ViewGroup sub-classes do not call their onDraw method, you should enable it by calling setWillNotDraw(false) link
Borrowing from the link #
Android Custom Layout - onDraw() never gets called
Try the below it works
1.If you're extending a ViewGroup (in your case a RelativeLayout) you should override dispatchDraw() instead of onDraw().
Discussion on the topic #
https://groups.google.com/forum/?fromgroups=#!topic/android-developers/oLccWfszuUo
protected void dispatchDraw (Canvas canvas)
Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn (but after its own view has been drawn).
Parameters
canvas the canvas on which to draw the view
Example
public class Hello extends Activity {
private MyView myView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
Log.e("hello", "hello");
this.myView = new MyView(this);
setContentView(this.myView);
}
public class MyView extends RelativeLayout
{
private Paint myPaint = new Paint();
private int[] numbers;
public MyView(Context paramContext)
{
super(paramContext);
Log.e("MyView", "MyView");
setFocusable(true);
setBackgroundResource(R.drawable.ic_launcher);
}
#Override
protected void dispatchDraw(Canvas canvas){
super.dispatchDraw(canvas);
Log.i("...............","drawing");
}
}
}
2.If you Override onDraw in the constructor call setWillNotDraw(false) then it should work.
http://developer.android.com/reference/android/view/View.html#setWillNotDraw(boolean)
public MyView(Context context) {
super(context);
sContext = context;
init();
setWillNotDraw(false);
}
You need to call setWillNotDraw(false); in order for your onDraw method to be called:
private void init() {
setWillNotDraw(false);
...
}
I was having OnDraw not called in C# using 4.1 Jelly Bean API 16. I switched from compiling with API 16 SDK to compiling with 7.1 Nougat SDK. It solved the problem completely. I still have in manifest min sdk of API 16 and it runs fine in my Jelly Beam avd.
In my case onDraw was not called because I returned 0 as width or height of view.

Recreate View in ADT

We have one MainActivity.
On this Main I would like to display my SurfaceView, but only if a button was clicked.
So when the main is started and the user clicks one button the SurfaceView should appear and everytim it appears it should excecute the onCreate-Method.
But it did not work.
All of my code is in this constructor:
public GameView(Context context, AttributeSet attrs)
So this one should be usesd, but i don't know what to do :/
EDIT:
Code in XML:
<de.apex.viewer.view.GameView
android:id="#+id/gameView1"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_centerInParent="true"
android:layout_marginLeft="94dp" />
Here is some Code from the main:
public void displayGameView(View view)
{ // here I need code to "recreate" my gameView
}
And here some Code from the GameView which should be displayed:
public class GameView extends SurfaceView {
public GameView(Context context, AttributeSet attrs)
{
super(context, attrs);
surfaceHolder = getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback()
{
public void surfaceDestroyed(SurfaceHolder holder)
{}
public void surfaceCreated(SurfaceHolder holder)
{
// here is my code I want to execute
Canvas theCanvas = surfaceHolder.lockCanvas(null);
HouseInRow = DefineRowCount(arraylength);
GesamtZahl = HouseInRow*2+HouseInColumn*2;
System.out.println("Houserow: " + HouseInRow);
public void surfaceChanged(SurfaceHolder holder, int format,int width, int height)
{}
});
}

Custom layout with WindowManager

For some reason, I keep getting an Error inflating Class message because I'm trying to implement
Caused by: java.lang.NullPointerException
at pap.crowslanding.GameView.(GameView.java:49)
Line 49 is this
WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
I am doing it this way because I am not in an Activity, any suggestions?
EDIT:
public class GameView extends SurfaceView {
static final long FPS = 10;
public Bitmap bmp;
public SurfaceHolder holder;
public LoopGameThread loopGameThread;
public static Sprite sprite;
public LayoutInflater inflater;
//maze variables
public int width;
public int height;
private float cellWidth;
private boolean[][] north; // is there a wall to north of cell i, j
private boolean[][] east;
private boolean[][] south;
private boolean[][] west;
private boolean[][] visited;
private double size;
boolean done = false;
Display display = new
Paint paint = new Paint();
//maze variables
public GameView(Context context) {
super(context);
WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point screenSize = new Point();
display.getSize(screenSize);
int width = screenSize.x;
int height = screenSize.y;
}
public GameView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
//maze variables
initVars(attrs);
initMaze();
generate(1, 1);
//maze variables
loopGameThread = new LoopGameThread(this);
holder = getHolder();
holder.addCallback(new Callback() {
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
loopGameThread.isStart(true);
loopGameThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.crow);
sprite = new Sprite(this, bmp);
}
//MAZE VARIABLES
private void initVars(AttributeSet attrs) {
//requires display variables
}
Then within my initVars, I need to use those display variables
EDIT 2 :
package pap.crowslanding;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
import android.widget.Button;
public class Game extends MainActivity{
static boolean pressedUp = false;
protected static GameView gameV;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.tester1);
Button moveLeft = (Button) findViewById(R.id.button1);
gameV = (GameView)findViewById(R.id.game_view);
moveLeft.setOnTouchListener(new View.OnTouchListener() {
private Handler mHandler;
#Override public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mHandler != null) return true;
mHandler = new Handler();
mHandler.postDelayed(mAction, 100);
break;
case MotionEvent.ACTION_UP:
if (mHandler == null) return true;
mHandler.removeCallbacks(mAction);
mHandler = null;
break;
}
return false;
}
Runnable mAction = new Runnable() {
#Override public void run() {
System.out.println("Performing action...");
mHandler.postDelayed(this, 100);
GameView.sprite.movementGo();
}
};
});
}
}
You need to pass your context down to whatever class is calling this function.
You could for example, keep local context value in your class which you can refer to in your functions. First, you'll need to make a constructor which will pass in a context to your class to use.
public class Example {
Context mContext;
public Example(Context mContext;){
this.mContext = mContext;
}
public someFunction(){
...
WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
...
}
}
In your Activity class that creates an instance of your class and uses it, you'll need to pass in the Activity's context like so:
public class SomeActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Example test = new Example(this); //You are passing in the Activity as the context here
test.someFunction();
}
}
EDIT to your edit:
Create a new local variable in your GameView class:
private Context mContext;
Then assign a value to mContext in your GameView constructor like so:
public GameView(Context context) {
super(context);
mContext = context;
...
}
You can now use mContext inside of your initVars() function

Categories

Resources