Reputation: 8153
I am using https://github.com/chrisbanes/PhotoView and trying to animate its height.
I use ValueAnimator
and update the layout height, so that triggers the internal PhotoViewAttacher
and onGlobalLayout
which transforms the matrix.
Is there any workaround to prevent scale and y position to be unchanged, like could I somehow update the matrix myself to keep the image Y position and scaleX/scaleY unchanged? Now those are reset to scale 1.0 and y position center of image.
Animation code:
ValueAnimator animator = ValueAnimator.ofInt(start, end).setDuration(300);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mImageView.getLayoutParams().height = (int) animation.getAnimatedValue();
mImageView.requestLayout();
}
});
animator.start();
Upvotes: 6
Views: 541
Reputation: 8153
This was more tricky than first thought. The library doesn't seem to have public API's to get this to work, so here is a workaround using private methods and fields. Following solution seems to work with my cases where I can change the actual height of PhotoView
and scale + y position keeps unchanged during the animations.
First extend PhotoView
and write following code:
// Private fields and methods
private Matrix mBaseMatrix;
private Field mAttacher;
private Method mGetDrawMatrix;
private Method mSetImageViewMatrix;
private Method mCheckMatrixBounds;
...
@Override
protected void init() {
super.init();
getViewTreeObserver().removeOnGlobalLayoutListener(
(ViewTreeObserver.OnGlobalLayoutListener) getIPhotoViewImplementation());
getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
updateBaseMatrix();
}
});
}
private void updateBaseMatrix() {
try {
if (mBaseMatrix == null) {
mBaseMatrix = getMatrix("mBaseMatrix");
}
if (mBaseMatrix != null) {
mBaseMatrix.setValues(new float[]{
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
});
}
if (mAttacher == null) {
mAttacher = PhotoView.class.getDeclaredField("mAttacher");
mAttacher.setAccessible(true);
}
if (mGetDrawMatrix == null) {
mGetDrawMatrix = PhotoViewAttacher.class.getDeclaredMethod("getDrawMatrix");
mGetDrawMatrix.setAccessible(true);
}
Matrix drawMatrix = (Matrix) mGetDrawMatrix.invoke(mAttacher.get(this));
if (mSetImageViewMatrix == null) {
mSetImageViewMatrix = PhotoViewAttacher.class
.getDeclaredMethod("setImageViewMatrix", Matrix.class);
mSetImageViewMatrix.setAccessible(true);
}
mSetImageViewMatrix.invoke(mAttacher.get(this), drawMatrix);
if (mCheckMatrixBounds == null) {
mCheckMatrixBounds = PhotoViewAttacher.class.getDeclaredMethod("checkMatrixBounds");
mCheckMatrixBounds.setAccessible(true);
}
mCheckMatrixBounds.invoke(mAttacher.get(this));
} catch (Exception e) {
e.printStackTrace();
}
}
private Matrix getMatrix(String fieldName) {
try {
Field f = PhotoView.class.getDeclaredField("mAttacher");
f.setAccessible(true);
PhotoViewAttacher a = (PhotoViewAttacher) f.get(this);
f = PhotoViewAttacher.class.getDeclaredField(fieldName);
f.setAccessible(true);
return (Matrix) f.get(a);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Upvotes: 1
Reputation: 51571
I went through the source code, but haven't tested the following code. From what I can tell, you want to block the call to onGlobalLayout()
for the duration of the animation. The following should achieve that:
onAnimationStart():
mPhotoView.getViewTreeObserver()
.removeOnGlobalLayoutListener((PhotoViewAttacher)mPhotoView.getIPhotoViewImplementation());
onAnimationEnd():
mPhotoView.getViewTreeObserver()
.addOnGlobalLayoutListener((PhotoViewAttacher)mPhotoView.getIPhotoViewImplementation());
Note thatremoveOnGlobalLayoutListener(OnGlobalLayoutListener)
is available for API versions >=16. Before this, you'll use removeGlobalOnLayoutListener(OnGlobalLayoutListener)
.
onAnimationStart()
and onAnimationEnd()
callbacks are available by adding a ValueAnimator.AnimatorUpdateListener
to your ValueAnimator
.
Again, I don't know if this will work - looks like it should.
Edit:
Following is independent of the code above.
Instead of animating the height
of PhotoView
, you could animate its top
& bottom
properties. In my tests, animating these properties did not reset the y
position, or change the scaleX/scaleY values:
int mOrigImageViewTop, mOrigImageViewBottom;
void crunchImageView() {
// Hold on to original values
if (mOrigImageViewTop == 0) {
mOrigImageViewTop = mImageView.getTop();
mOrigImageViewBottom = mImageView.getBottom();
}
// Top
ObjectAnimator objectAnimatorTop = ObjectAnimator.ofInt(mImageView,
"top", mOrigImageViewTop,
mOrigImageViewTop + 200 /*should be calculated dynamically*/);
// Bottom
ObjectAnimator objectAnimatorBottom = ObjectAnimator.ofInt(mImageView,
"bottom", mOrigImageViewBottom,
mOrigImageViewBottom - 200 /*should be calculated dynamically*/);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(objectAnimatorTop, objectAnimatorBottom);
animatorSet.setDuration(5000L);
animatorSet.start();
}
If you are animating the height
to make room for other views above or below the PhotoView
, animating top
/bottom
will not help. In this case, using a FrameLayout
to host the PhotoView
& other Views
, and controlling their visibility may be an option.
Upvotes: 3