6

I recently upgraded to Android 4.4 and someone of the features of my app have surprisingly stopped working.

I have this code for initializing and then drawing my custom view. The basic idea is it adjusts the zoom level so the entire view fits on the screen.

private void initAtZoomLevel(float zoomLevel){
    ....
    Matrix transformMatrix = new Matrix();
    transformMatrix.setScale(initialZoomLevel, initialZoomLevel);
    float yTransCenter = (screenHeight - mapHeight)/2.0f;
    setImageMatrix(transformMatrix);
}

protected void onDraw(Canvas canvas){

    super.onDraw(canvas);
    float[] values = new float[9];
    getImageMatrix().getValues(values);
    scaleFactor = values[0];
    ....
}

THIS WORKS ON ANDROID 4.1.2, and 4.2.2 DEVICES I HAVE

But on Android 4.4/4.3 getImageMatrix().getValues(values) stopped working! It returns an identity matrix instead of the transform matrix I expect on app start up!

DEBUG PRINT-OUT:

4.1.2: @setImageMatrix(transformMatrix): transformMatrix = Matrix{[0.025122833, 0.0, 0.0][0.0, 0.025122833, 566.5][0.0, 0.0, 1.0]}

@getImageMatrix().getValues(values): transformMatrix = Matrix{[0.025122833, 0.0, 0.0][0.0, 0.025122833, 566.5][0.0, 0.0, 1.0]}

4.4: @setImageMatrix(transformMatrix): transformMatrix = Matrix{[0.025122833, 0.0, 0.0][0.0, 0.025122833, 553.0][0.0, 0.0, 1.0]}

@getImageMatrix().getValues(values): transformMatrix = Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}

I've looked around and I can't seem to find any documentation on this. Somehow the image matrix for my view is being reset; has Android 4.4 changed the way we are supposed to do this? Has anyone else run in to this problem?

note: the problem appears to have originated on Android 4.3 - the same problem occurs running on an emulator

UPDATE: I have checked the change log from 4.2 to 4.3, but there is nothing on there I can see above the Matrix Class, or anything relevant to the View class.

UPDATE 2: My pinch-to-zoom is also not working, which uses the same setImageMatrix() method - and it's clearly not sticking because nothing happens in getImageMatrix().getValues()

kburbach
  • 761
  • 10
  • 26
  • 1
    Try diffing the actual source code of getImageMatrix between Android versions – Jon Willis Dec 06 '13 at 18:59
  • @GenericHolidayName if you look closely, it's actually not saving/returning the same matrix (the field names are different between `getImageMatrix` and `setImageMatrix`) – kburbach Dec 09 '13 at 17:38
  • http://stackoverflow.com/questions/30446099/how-to-extend-imageview-correctly-in-android-the-following-code-is-not-renderin Anyone who know the issue for this? – maya May 26 '15 at 12:35

2 Answers2

2

I have found what I believe to be the problem. I took a look at the source code for ImageView and discovered the setImageMatrix(Matrix matrix) is saving the matrix in a different field than getImageMatrix() is returning...

Android 4.4 ImageView

public void setImageMatrix(Matrix matrix) {
    // collaps null and identity to just null
    if (matrix != null && matrix.isIdentity()) {
        matrix = null;
    }

    // don't invalidate unless we're actually changing our matrix
    if (matrix == null && !mMatrix.isIdentity() ||
            matrix != null && !mMatrix.equals(matrix)) {
        mMatrix.set(matrix);
        configureBounds();
        invalidate();
    }
}

Here the matrix is being stored in the field mMatrix

public Matrix getImageMatrix() {
    if (mDrawMatrix == null) { //<-- should be mMatrix == null
        return new Matrix(Matrix.IDENTITY_MATRIX);
    }
    return mDrawMatrix; //<-- NOT THE RIGHT FIELD TO RETURN
}

While getImageMatrix() returns mDrawMatrix...

Android 4.1.2 ImageView

public Matrix getImageMatrix() {
    return mMatrix;
}

public void setImageMatrix(Matrix matrix) {
    // collaps null and identity to just null
    if (matrix != null && matrix.isIdentity()) {
        matrix = null;
    }

    // don't invalidate unless we're actually changing our matrix
    if (matrix == null && !mMatrix.isIdentity() ||
            matrix != null && !mMatrix.equals(matrix)) {
        mMatrix.set(matrix);
        configureBounds();
        invalidate();
    }
}

both methods use the same field - mMatrix

So there's the problem right there --- all of a sudden getImageMatrix() is returning the wrong field...

kburbach
  • 761
  • 10
  • 26
  • 1
    To try to resolve, make sure your `scaleType` is set to `ScaleType.MATRIX`. It looks like `mMatrix` should get assigned to `mDrawMatrix` in `configureBounds()` **if** the `scaleType` is set correctly. It looks like this is actually a *fix* to enforce the `scaleType` more than a new bug. – Geobits Dec 09 '13 at 17:53
  • I could have sworn I had that somewhere... time to go see if that's the problem – kburbach Dec 09 '13 at 17:57
  • You may be right. That's just what it looks like to me. Previously, it wouldn't matter if `mDrawMatrix` was assigned or not, because it wasn't returned. – Geobits Dec 09 '13 at 17:58
  • there is a line `if(ScaleType.MATRIX == mScaleType) {... mDrawMatrix = mMatrix}`, BUT - the first line of `configureBounds()` is `if (mDrawable == null || !mHaveFrame) { return; }`. So it's going to immediately return and bypass setting `mDrawMatrix = mMatrix`. – kburbach Dec 09 '13 at 18:25
  • Is your drawable `null`? That could definitely explain it. – Geobits Dec 09 '13 at 18:27
  • yeah, i never set a drawable - i need use the `ImageView` to draw hundreds of small rectangles, text, etc that change with zoom level so I was under the impression when I first started the project that I couldn't use a `Drawable`... it worked up until now, but maybe I made an incorrect assumption? – kburbach Dec 09 '13 at 18:34
  • You could try using a dummy drawable to force `configureBounds` to run. I can understand why they don't bother setting bounds on a null drawable, so I think that may be a good workaround. – Geobits Dec 09 '13 at 18:36
  • I wouldn't say it was an "incorrect assumption", though. Not knowing that it has to now run through `configureBounds` to return the "correct" matrix seems pretty reasonable, since it didn't have to before. – Geobits Dec 09 '13 at 18:37
  • setting a dummy drawable alone didn't work, but I did get it to work - making a separate answer. – kburbach Dec 09 '13 at 20:36
0

While my previous answer does outline the overall problem, it in fact might not be a bug. As pointed out by Generic Holiday Name, mDrawMatrix should be set to mMatrix in the configureBounds() method - which would eliminate this problem/bug/whatever.

HOWEVER, I had to add several lines of code to get configureBounds() to actually work:

private void configureBounds() {
    if (mDrawable == null || !mHaveFrame) {
        return;
    }

    int dwidth = mDrawableWidth;
    int dheight = mDrawableHeight;

    ...

    if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
        /* If the drawable has no intrinsic size, or we're told to
            scaletofit, then we just fill our entire view.
        */
        mDrawable.setBounds(0, 0, vwidth, vheight);
        mDrawMatrix = null;
    } else {
             //here's the where mDrawMatrix == mMatrix IF using scaleType.MATRIX
             ...
    }
}

So, in order to actually get this to work the way I expect with 4.2 and below you need to make sure:

  1. mDrawable != null. This wasn't a problem before, but for my case I wasn't using a drawable so everything was failing (the return statement was hit right away)
  2. 'dwidth >0 && dheight >0. This isn't a problem if you have a real drawable, but like I said, I didn't.
  3. mHaveFrame = true. I had no idea what this was - never used it. The only way to set this to true is by calling setFrame(int, int, int, int).

To get my scaling code to work again, I had to add the following:

 //potentially a fix for the "bug" that appears in Android 4.3+
 //mDrawable cannot be null anymore for getImageMatrix to work---stupid
 //therefore the imageview class MUST set a drawable for matrix scaling to work
 //so here I am using a "empty" drawable to get around this
 ShapeDrawable fakeDrawable = new ShapeDrawable(); //so mDrawable != null
 fakeDrawable.setIntrinsicHeight(1); //so dwidth and dheight are > 0
 fakeDrawable.setIntrinsicWidth(1);

 setImageDrawable(fakeDrawable);

 setFrame(0, 0, viewWidth, viewHeight); //setting a frame so mHaveFrame = true

YIKES

kburbach
  • 761
  • 10
  • 26