2

I created a custom view called CanvasView. I can use this view to draw stuff on it outside of the onDraw method. Which means I can add lines, circles, whatever I want to the canvas anywhere in my code.

I want to draw a random line on the canvas view in the onCreate method. I want the starting point to be in the left half of the view and the ending point in the right half. However, when I measure the width of the view using getWidth, it returns 0! And then I pass this number into Random.nextInt with some more calculations. And resulting in a negative number. An IllegalArgumentException occurs as expected.

I just want to ask why my view's width is 0? And how can I get the correct width?

Here is the CanvasView class: (methods other than addShape and onDraw are irrelevant, I think)

public class CanvasView extends View {
    /**
     * Stores all the shapes that the view will draw in its {@code onDraw()}
     * method
     */
    private ArrayList<Shape> shapes;

    /**
     * Represents a standard {@code Paint} object that should be used when
     * drawing on this {@code CanvasView}.
     */
    private Paint paint;

    public CanvasView(Context c) {
        super (c);
        init ();
    }

    public CanvasView(Context context, AttributeSet attrs) {
        super (context, attrs);
        init ();
    }

    public CanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
        super (context, attrs, defStyleAttr);
        init ();
    }

    /**
     * Initializes the view, called by the constructor.
     */
    private void init() {
        shapes = new ArrayList<> ();
        paint = new Paint ();
        paint.setStrokeWidth (5);
        paint.setColor (Color.BLACK);
    }

    @Override
    public void setOnTouchListener(final OnTouchListener listener) {
        final OnTouchListener baseListener = new OnTouchListener () {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction () == MotionEvent.ACTION_DOWN) {

                    float x = event.getX ();
                    float y = event.getY ();

                    Log.d ("My App", "X: " + x);
                    Log.d ("My App", "Y: " + y);

                    //check whether the point touched is in bounds
                    if (x < 18 || x > getWidth () - 18 || y < 18 ||
                            y > getHeight () - 18)
                        return false;
                    else
                        return true;
                }
                return false;
            }
        };

        super.setOnTouchListener (new OnTouchListener () {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (baseListener.onTouch (v, event)) {
                    if (listener != null) {
                        return listener.onTouch (v, event);
                    } else {
                        return true;
                    }
                }
                return false;
            }
        });
    }

    @Override
    protected void onDraw(Canvas c) {
        super.onDraw (c);
        for (Shape s : shapes) {
            s.draw (c);
        }
        //draw the border.
        c.drawLine (3, 3, getWidth () - 3, 3, paint);
        c.drawLine (3, getHeight () - 3, getWidth () - 3, getHeight () - 3, paint);
        c.drawLine (3, 3, 3, getHeight () - 3, paint);
        c.drawLine (getWidth () - 3, 3, getWidth () - 3, getHeight () - 3, paint);

        //draw the inner border
        c.drawLine (18, 18, getWidth () - 18, 18, paint);
        c.drawLine (18, getHeight () - 18, getWidth () - 18, getHeight () - 18, paint);
        c.drawLine (18, 18, 18, getHeight () - 18, paint);
        c.drawLine (getWidth () - 18, 18, getWidth () - 18, getHeight () - 18, paint);
    }

    /**
     * Adds a shape to the {@code CanvasView} so that it can be drawn
     * in its {@code onDraw()} method.
     *
     * @param s
     */
    public void addShape(Shape s) {
        shapes.add (s);
        invalidate ();
    }

    /**
     * Clears all the shapes on the {@code CanvasView}.
     */
    public void clear() {
        shapes.clear ();
    }
}

There is a Shape interface that I created, which only has this method:

void draw (Canvas c);

Here is how I draw a random line, this snippt is in a method that is called in onCreate:

Random r = new Random ();
final int width = canvas.getWidth () - 30;
final int height = canvas.getHeight () - 30;
final Paint p = paint;

final int startX = r.nextInt ((width - 30) / 2) + 30;
final int startY = r.nextInt (height - 30) + 30;
final int stopX = r.nextInt ((width - 30) / 2) + width / 2 + 30;
final int stopY = r.nextInt (height - 30) + 30;

float adjSide = Math.abs (stopX - startX);
float oppSide = Math.abs (stopY - startY);
lineAngle = (float)Math.atan (oppSide / adjSide);

canvas.addShape (new Shape () {
    @Override
    public void draw(Canvas c) {
        c.drawLine (startX, startY, stopX, stopY, p);
    }
});

EDIT:

I changed my code a little bit as the comments suggested.

I first inflated the parent layout like this:

getLayoutInflater ().inflate (R.layout.activity_parallel_lines_level,
    parent);

The variable parent is the parent layout of the CanvasView as you may have already guessed. Then I measure the dimensions of the parent of the CanvasView:

parent.measure (View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);

Now something really strange happens, it worked out the width successfully, but not the height! I don't know whether this has anything to do with my LinearLayout being a vertical one. What should I do?

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 2
    The object has no width until it is inflated which will occur after the onCreate method. – Tony Ruth Oct 04 '15 at 00:43
  • @TonyRuth how do I know when it is inflated? – Sweeper Oct 04 '15 at 00:44
  • 1
    @Sweeper You can measure a `View` at any point by doing `view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);`. Then you can get the dimensions by doing `view.getMeasuredWidth()` and `view.getMeasuredHeight()`. `getWidth()` and `getHeight()` will give `0` until the `View` is actually laid out. – Paul Boddington Oct 04 '15 at 00:54
  • @PaulBoddington It still returns 0 though – Sweeper Oct 04 '15 at 01:04
  • Ah Ok. Have you tried measuring the space that the view will go in instead? – Paul Boddington Oct 04 '15 at 01:06
  • Measuring the parent view? @PaulBoddington – Sweeper Oct 04 '15 at 01:10
  • Yes, or the screen. How do you use a `CanvasView`? Is it added to a layout or are you doing `setContentView(canvasView);`? – Paul Boddington Oct 04 '15 at 01:24
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/91276/discussion-between-sweeper-and-paul-boddington). @PaulBoddington – Sweeper Oct 04 '15 at 01:28
  • 2
    @Sweeper I believe the earliest you can get the correct size for the view is when it calls its builtin function onSizeChanged. You can override this function and perform the necessary tasks that require the width inside onSizeChanged. If you need to access that information sooner you either need to measure the parent as Paul suggested, or inflate the view inside onCreate before proceeding. – Tony Ruth Oct 04 '15 at 01:59
  • Maybe use ongloballayoutlistener – JRowan Oct 04 '15 at 02:03

0 Answers0