Final variables in onDraw() method - java

I have a class that extends View and overrides the onDraw(Canvas canvas) method. This view runs animations, so onDraw will be called many times per second. Consider the following example...
#Override
protected void onDraw(Canvas canvas) {
final int width = getWidth();
final int height = getHeight();
final int padLeft = getPaddingLeft();
final int padTop = getPaddingTop();
final int padRight = getPaddingRight();
final int padBottom = getPaddingBottom();
final RectF oval = new RectF(padLeft, padTop, width - padRight, height - padBottom);
...
}
Should I be worried that there are this many function calls happening each time onDraw is called? Does final tell the compiler that it doesn't need to call these functions each time? Would these variables be better off as member variables so that the functions are only called once?
P.S. I know from running my program that performance is not affected. I am asking this question from a learning standpoint. It makes me feel better when I know exactly what I'm doing.

by saying final here you just saying that this local variable will not be changed in this function and functions will be called each time you call onDraw.
If it is possible it's better to compose all this variables to another class like DisplayProperties and initialise it only once.

Related

When trying to make a gradient from the background, the background is filled with one color

I'm creating an Android application and I needed to create a Drawable with a gradient background and text inside, but for some reason I don't have a gradient, and the entire background is filled with solid color
Class code:
class TestDrawable(textSize: Int = 16) : Drawable() {
private val rect = RectF()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
private val textWidth: Int
private val text: String
private var backgroundGradient: LinearGradient = LinearGradient(
0f, 0f, intrinsicWidth.toFloat(), 0f,
intArrayOf(-0xb73320, -0xafa523, -0x41bf40, -0x457d5),
floatArrayOf(0.06f, 0.34f, 0.73f, 1f),
Shader.TileMode.CLAMP
)
override fun draw(canvas: Canvas) {
rect.set(bounds)
canvas.drawRoundRect(rect,
AndroidUtilities.dp(2f).toFloat(),
AndroidUtilities.dp(2f).toFloat(), paint)
canvas.drawText(
text,
rect.left + AndroidUtilities.dp(5f),
rect.top + AndroidUtilities.dp(12f),
textPaint
)
}
override fun getIntrinsicWidth(): Int {
return textWidth + AndroidUtilities.dp((5 * 2).toFloat())
}
override fun getIntrinsicHeight(): Int {
return AndroidUtilities.dp(16f)
}
init {
textPaint.textSize = AndroidUtilities.dp(textSize.toFloat()).toFloat()
textPaint.typeface = AndroidUtilities.getTypeface("fonts/rmedium.ttf")
textPaint.color = -0x1000000
//paint.style = Paint.Style.FILL
paint.color = -0x1
paint.shader = backgroundGradient
backgroundGradient.setLocalMatrix(Matrix())
text = "plus".uppercase()
textWidth = ceil(textPaint.measureText(text).toDouble()).toInt()
}
}
You're initialising backgroundGradient when you declare the variable, and that sets the gradient width with a call to getIntrinsicWidth, which itself relies on textWidth having been initialised. But that initialisation happens in the init block, which is below backgroundGradient, so it hasn't run yet.
I haven't tested it but I'm guessing textWidth is still zero (they behave like Java objects/primitives in this situation) so you're getting a very tiny gradient and the rest of your background is just the end colour. Try initialising your gradient in init, after textWidth has been set
This is the kind of thing I'm talking about in the comments - you get your metrics in draw(), so that's when you should initialise/update your stuff that depends on those metrics:
// keep a record of the previous bounds values for comparison
private var previousBounds: Rect? = null
override fun draw(canvas: Canvas) {
// check if the dimensions have changed - if so, update everything
if (bounds != previousBounds) {
updateStuff(bounds)
previousBounds = bounds
}
// draw stuff
canvas.drawRoundRect(bounds,
AndroidUtilities.dp(2f).toFloat(),
AndroidUtilities.dp(2f).toFloat(), paint
)
canvas.drawText(
text,
rect.left + AndroidUtilities.dp(5f),
rect.top + AndroidUtilities.dp(12f),
textPaint
)
}
private fun updateStuff(area: Rect) {
// update all your stuff that changes when the dimensions change
paint.shader = LinearGradient(
0f, 0f, area.width, 0f,
intArrayOf(-0xb73320, -0xafa523, -0x41bf40, -0x457d5),
floatArrayOf(0.06f, 0.34f, 0.73f, 1f),
Shader.TileMode.CLAMP
)
}
So the basic idea here is there's stuff you can initialise during construction - basic Paints, colours etc. Then there's some stuff that you can only initialise during draw, when you finally have the drawable's dimensions. If you split those out, you can initialise/update the stuff that needs it directly from the draw function, when you have the info needed.
For example, you don't actually need to set a gradient shader on your paint during construction - you just need it before you try to draw anything. That's simple enough - set it inside the draw call. By keeping a copy of the most recent set of dimensions, you can compare and see if anything's changed, and avoid unnecessarily recreating the same LinearGradient every time (I don't know how often draw is called, but it's a good habit either way). By making it null at the start, the comparison fails so it always updates the first time draw is called (i.e. it initialises)
I don't know if you're still using that getIntrinsicWidth call, but if you are, since it relies on textSize being set, just make sure that's set before you reference it. And since it looks like it doesn't change after being set during init, and the draw call (and any updates it triggers) comes later, it's all good. If any of that stuff does need to update, just put it in the update function, and make sure things come after anything they rely on
(I haven't tested this code, it's just to give you the general idea)

Android: view layout resetting when adding/removing fragments

I am a bit new to android, but I know java fairly well.
As a simple means of scalability, i use these simple functions:
public void scaleMyView(float s) {
scaleFactor = scaleFactor + s;
// s will usually be like 0.1 or -0.1 = 10% size gain/loss
View myMainView = findViewById(R.id.MyMainView);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int x = size.x;
int y = size.y;
myMainView.setScaleX(scaleFactor);
myMainView.setScaleY(scaleFactor);
myMainView.layout(0, 0, ((int)(x/scaleFactor)), ((int)(y/scaleFactor)))
myMainView.setPivotX(0);
myMainView.setPivotY(getActionBar().getHeight());
}
Which does exactly what I want, changing the main view and all within it in size and keeping it in place.
Now comes the problem:
Whenever I add a fragment to a sub-view of that main-view, the "layout" part is reset, and though scale and position remain, size shrinks back to default.
(The fragments are very simple, basically simple buttons.)
I tried quite a few things, and I still have no idea where or how to fix that.
Any help is highly appreciated!
Try to use "addOnLayoutChangeListener" method on your mainView:
myMainView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(
View view, int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8)
{
// set view properties here
}
});
myMainView.requestLayout();

Importing a class in Java?

In order to make this program run, I have to import the "derp" method out of the Shape class and into the rectangle class. But no matter what myself or the tutors try, we cannot make it work. What I actually need to accomplish is that the "derp" method from Shape needs to be able to work in Rectangle. However, the task of importing the method has us stumped.
public abstract class shape{
shape(){
}
shape(int length, int width, int thing ){
length = 0;
width = 0;
}
public int derp(int thing, int length) {
thing = (int) Math.random() * 9 ;
length = thing;
return length;
}
}
public class Rectangle extends shape {
public static void main(String args[])
{
shape.getLength(Length, Width);
//r1 will take all default value
Rectangle r1 = Rectangle();
//r2 will take all supplied value
Rectangle r2 = Rectangle(4, 5);
//r3 will take supplied length. width will take default value
Rectangle r3 = Rectangle(10);
//r4 will take the same value of r2
Rectangle r4= r2;
//the rest of the code
}
private static void Rectangle(int width2, int length2) {
// TODO Auto-generated method stub
}
}
It seems to me like you should reverse the inheritance. It should be Rectangle extending shape, since a rectangle is a shape
EDIT 1
When you reverse the inheritance you should be able to call the derp method on Rectangle.
RectAngle r = new Rectangle();
r.derp(thing, length);
EDIT 2
You shouldn't really hardcode your variables into your Shape instance either. Thats not a really usefull way to do it. There is two ways you can do it. Either let The shape class have variables which is protected (means it will be be inherited). Then you shape class should look like:
public abstract class shape{
protected int length;
protected int width;
shape(){
}
shape(int length, int width){
this.lenght = length;
this.width = width;
}
public int derp() {
int thing = (int) Math.random() * 9 ;
length = thing;
return length;
}
}
Or if you dont wanna make this big change to your class you can just pass the parameters directly into the method like
r.derp(1, 100);
But I do agree with tieTYT that you should spend some time learning some more java syntax. Since this is a very wierd way of doing your calls :).
Rectangle should be extending Shape, not the other way around.
Also remove this line
Rectangle.derp(int thing, int length, int width);
You don't import methods out of classes into other classes. Logically you'd want Rectangle to extend shape (also should be Shape in accordance with naming conventions), but you're doing the other way around.
However there are many things that don't make sense in your code, so you might want to explain in clear English what you're trying to accomplish.
There is no way, this could work, since you are trying to access a Method of the child-class from the superclass. the superclass dont know its children. You either can use the Methods of the superclass from the subclass or place the method from the subclass into the superclass to use it there.
Rectangle.derp(int thing, int length, int width);
This is not a method declaration. This syntax is all wrong. Are you trying to declare derp as a method you can use, or are you trying to call derp? As a reader, that is unclear.
Logically, Rectangle should extend Shape, not the other way around.
This doesn't do anything:
shape(int length, int width, int thing ){
length = 0;
width = 0;
}
You're just assigning parameters to different values.
Take a break and spend some time learning the syntax of Java.
Ok..so your class Shape extends Rectangle...we will ignore that unusual logic.
Rectangle.derp(int thing, int length, int width);// wrong
First of all you cannot call method derp by declaring parameters inside call. You could have a call like this for example:
int thing = 1, length = 2, width = 3;
Rectangle.derp(thing, length, width);//with condition that derp method
//declaration to be public static
Rectangle r1 = Rectangle();//wrong
That is the class constructor. You declare it as public unless you want to make your class a Singleton ..but I doubt. An you instantiate it with the 'new' keyord like this:
Rectangle r1 = new Rectangle();
Same for the one with 1 and 2 parameters.
Complete code for the Rectangle class here:
public class Rectangle {
public Rectangle(int width2, int length2) {
// TODO: Process width and length here
}
public Rectangle() {
// TODO: process default values here
}
public Rectangle(int value) {
// TODO: Process value here
}
public static void derp(int thing, int length, int width) {
}
public static void main(String args[]) {
int thing = 1, length = 2, width = 3;
Rectangle.derp(thing, length, width);
// r1 will take all default value
Rectangle r1 = new Rectangle();
// r2 will take all supplied value
Rectangle r2 = new Rectangle(4, 5);
// r3 will take supplied length. width will take default value
Rectangle r3 = new Rectangle(10);
// r4 will take the same value of r2
Rectangle r4 = r2;
// the rest of the code
}
}

Keeping the rectangle within JFrame

I know I am being an idiot and that's why I can't figure it out but I am trying to paint a bunch of rectangles with randoms size and position using paintComponent. I am trying to make sure that all of them are painted within the frame. I am able to do it with the following code (snippet) but I am wondering if there is a better way to do it than me hardcoding numbers into the program. Is there a method that I should take a look at that might be what I'm looking for?
Here's the inner class that overrides the paintComponent() method:
class DrawPanel extends JPanel {
public void paintComponent(Graphics g) {
int red = (int)(Math.random()*256);
int blue = (int)(Math.random()*256);
int green = (int)(Math.random()*256);
g.setColor(new Color(red, blue, green));
//The following 4 lines keep the rects within the frame
//The frame is 500,500
int ht = (int)(Math.random()*400);
int wd = (int)(Math.random()*400);
int x = (int)(Math.random()*100);
int y = (int)(Math.random()*100);
g.fillRect(x,y,ht,wd);
}
}
You should base your co-ordinates & sizes on the DrawPanel component size. Also using Random.nextInt instead of Math.random() will make it easier to keep within range based on the current size of the panel:
Random random = new Random();
int ht = random.nextInt(getHeight());
int wd = random.nextInt(getWidth());
int x = random.nextInt(getWidth() - wd);
int y = random.nextInt(getHeight() - ht);
I am wondering if there is a better way to do it than me hardcoding numbers into the program.
Yes there is
call getSize() on the enclosing JPanel, the DrawPanel, so you can see the actual boundaries of the component that is being drawn upon. (edit: or getWidth() and getHeight() as recommended by Lucas -- 1+ to his answer!).
Also, you will usually want to call the super's paintComponent(...) method within the child's override.
Also you will usually want to do your randomization elsewhere, such as in the DrawPanel's constructor so as not to change your rectangles each time you re-size the GUI.
There is a method to get the length and width of the panel you are working in.
getHeight();
getWidth();
These will return the current size of the JPanel you are working in, meaning if you resize the window it'll actually still draw them inside.
http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#getWidth()

How can I pass the current SurfaceView into other object methods?

I'm trying to make my first android game, just a pong clone really, I have a "PongView" class that extends SurfaceView and is my only view. It has objects of my "Ball" and "Paddle" classes. I just started moving code related to things, like detecting wall collisions, to the Ball and Paddle classes to tidy up my main view a bit and realised that i'd need to give these classes a way to know the views width and height. At the moment my work around is just to intialise a global variable inside the surfaceviews surfaceChanged method that stores the width and height of the view, like so:
//at the top of my class
private int viewWidth;
private int viewHeight;
..
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
//INITIALISE viewWidth and viewHeight here
//so that they can be passed as parameters
viewWidth = getWidth();
viewHeight = getHeight();
//some other irrelevant code here
}
Then I pass them as parameters:
ball.handleWallCollision(viewWidth, viewHeight);
However i'm not sure this is the way to go about it, as i will need to pass them in quite often i imagine. I thought it would be better if i had a copy of the current PongView in each class? But i'm not sure if thats true or how/when to go about getting it.
What would you recommend? Thanks
use object of surface view class that was used to setting view in activity.That object is live and will contain all changes regarding that surface view
SurfaceView sur=new SurfaceView(this); /// you created object
setContentView(sur);
//you do your work
//surface view is running
Now you want to exit from that activity.Then in onDestroy() used
CommonClass.ObjectVraible=sur;
this line will save current state of the surfaceview

Categories