Paul
Paul

Reputation: 313

When invalidate method called on an array of Views, the first View is not redrawn

I have an app in which I display nine views within a 3x3 GridView using an adapter class. Within each of the nine views that are contained within the cells of the GridView, I use Canvas and Paint objects to display a 2-dimensional line graphic; these line graphics are subsequently modified and re-displayed by invoking the invalidate() method of each view.

The line graphics in all nine views are displayed correctly when the views are created in the Adapter class’s overridden getView() method, but when I attempt to modify and then redisplay the line graphics subsequently, all of the views are refreshed successfully except for the first view in the top-left-hand corner of the grid, which continues to show the original line drawing. I’ve stepped through my code to establish that the first view is definitely being invalidated, and it is, so I’m baffled as to why the call to the invalidate() method on the first view doesn’t cause it to be redrawn, while the same call on all of the remaining views causes them to be redrawn successfully. I’ve also logged calls to the view’s onDraw method, and this shows that the first view’s onDraw method is called each time, so I’m pretty sure this problem is not being caused by any bugs in the application code.

The code that modifies and refreshes the nine views is as follows:

void updateViews(int parentTestCanvas) {

    TestCanvasView testCanvas = testCanvass[parentTestCanvas];
    double[] parentGenome = testCanvas.getGenome();

    // Assign the parent genome to the testCanvass in the array
    // The inherited genome will be subject to mutation in all cases except the first testCanvas
    for(int testCanvasItem = 0; testCanvasItem < TestCanvasApp.geneCount; testCanvasItem++) {

        testCanvas = testCanvass[testCanvasItem];
        testCanvas.setGenome(parentGenome);

        // Invalidate the testCanvas view to force it to be redrawn using the new genome
        testCanvas.invalidate();
    }


}

The onDraw method in the TestCanvasView class is as follows:

protected void onDraw(Canvas canvas) {

    float xOrigin = getMeasuredWidth() / 2;
    float yOrigin = getMeasuredHeight() / 2;

    canvas.drawPaint(cellPaint);

    canvas.translate(xOrigin, yOrigin);
    drawBranch(canvas, linePaint, 0, 0, this.length, this.direction, this.xInc, this.yInc, this.scale);
    Log.d("TestCanvasView", "Drawing testCanvas " + mCellIndex);
}

private void drawBranch(Canvas canvas, Paint linePaint, double startX,
        double startY, double branchLen, int branchDir, double[] xInc,
        double[] yInc, double scale) {

    branchDir = (branchDir + 8) % 8;

    double newX = startX + branchLen * xInc[branchDir];
    double newY = startY + branchLen * yInc[branchDir];

    canvas.drawLine((float) (startX / scale), (float) (-startY / scale),
            (float) (newX / scale), (float) (-newY / scale), linePaint);

    if (branchLen > 1) {
        drawBranch(canvas, linePaint, newX, newY, branchLen - 1, branchDir + 1, xInc, yInc, scale);
        drawBranch(canvas, linePaint, newX, newY, branchLen - 1, branchDir - 1, xInc, yInc, scale);
    }

}

Anyone got any ideas as to why the first view is not being redrawn?

Upvotes: 1

Views: 2056

Answers (2)

Paul
Paul

Reputation: 313

OK, finally got to the bottom of this - it turns out that the getView method in the TestCanvasAdapter class was called twice for testCanvass[0], but only once for all the other elements. I had naively assumed that the Adapter class's getView method would be called exactly once for each element in the array, but this post confirms that getView may be called more than once for various obscure reasons that are unlikely to be readily apparent to inexperienced Android developers like me. Once I understood this, I was easily able to add the logic below to check that testCanvass[position] was not null before assigning the view reference to it within the TestCanvasAdapter.getView method, which resolved the problem.

            // Add the newly created TestCanvasView object to the array on the TestCanvasApp object
        if (position >= 0 && position < TestCanvasApp.viewCount 
                && mTestCanvasApp.testCanvass[position] == null) {
                    mTestCanvasApp.testCanvass[position] = testCanvasView;
        }

Many thanks to Romain Guy for taking the trouble to reply to this query.

Upvotes: 2

Romain Guy
Romain Guy

Reputation: 98501

If the first view's onDraw() method is invoked, then the invalidate worked. onDraw() is not called if the View doesn't intersect with the clipping rectangle. You can verify this by opening the Dev Tools app (on the emulator but you can also install it on a phone) and in the development settings screen, turn on "Show screen updates." It will flash regions of the screen that get invalidated/redrawn.

Upvotes: 0

Related Questions