例如,在下面的屏幕截图中,用户最初只能看到小狗的一部分.但是在用户点击图像后,整个小狗都被揭露了.在第一个后面褪色的图像显示了动画的结果.
最初,ImageView在X和Y中缩放到50%.当用户点击图像时,ImageView将缩放回100%,并重新计算ImageView矩阵.
我尝试了各种方法来计算矩阵.但我似乎无法找到适用于所有类型的作物和图像的景观:裁剪景观到纵向,裁剪景观到景观,裁剪纵向到纵向和裁剪纵向到景观.这有可能吗?
这是我现在的代码.我正在尝试找到setImageCrop()中的内容.
public class MainActivity extends Activity { private ImageView img; private float translateCropX; private float translateCropY; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); img = (ImageView) findViewById(R.id.img); Drawable drawable = img.getDrawable(); translateCropX = -drawable.getIntrinsicWidth() / 2F; translateCropY = -drawable.getIntrinsicHeight() / 2F; img.setScaleX(0.5F); img.setScaleY(0.5F); img.setScaleType(ScaleType.MATRIX); Matrix matrix = new Matrix(); matrix.postScale(2F,2F); //zoom in 2X matrix.postTranslate(translateCropX,translateCropY); //translate to the center of the image img.setImageMatrix(matrix); img.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final PropertyValuesHolder animScaleX = PropertyValuesHolder.ofFloat(View.SCALE_X,1F); final PropertyValuesHolder animScaleY = PropertyValuesHolder.ofFloat(View.SCALE_Y,1F); final ObjectAnimator objectAnim = ObjectAnimator.ofPropertyValuesHolder(img,animScaleX,animScaleY); final PropertyValuesHolder animMatrixCrop = PropertyValuesHolder.ofFloat("imageCrop",0F,1F); final ObjectAnimator cropAnim = ObjectAnimator.ofPropertyValuesHolder(MainActivity.this,animMatrixCrop); final AnimatorSet animatorSet = new AnimatorSet(); animatorSet.play(objectAnim).with(cropAnim); animatorSet.start(); } }); } public void setImageCrop(float value) { // No idea how to calculate the matrix depending on the scale Matrix matrix = new Matrix(); matrix.postScale(2F,2F); matrix.postTranslate(translateCropX,translateCropY); img.setImageMatrix(matrix); } }
编辑:值得一提的是,线性缩放矩阵是行不通的. ImageView按比例线性缩放(0.5到1).但是如果我在动画期间线性缩放矩阵,则在动画期间视图会变窄.最终结果看起来很好,但在动画期间图像看起来很丑.
解决方法
而不是使用矩阵来操纵图像的可见区域(视图),为什么不在剪切方面定义这个可见区域?这是一个相当简单的问题需要解决:我们需要做的就是定义可见的矩形,并忽略任何超出其边界的内容.如果我们然后为这些边界设置动画,则视觉效果就像裁剪边界向上和向下缩放一样.
幸运的是,Canvas
支持剪切各种clip *()方法来帮助我们在这里.动画剪辑边界很简单,可以使用与您自己的代码片段类似的方式完成.
如果你把所有东西放在一个简单的常规ImageView扩展中(为了封装),你会得到一些看起来像这样的东西:
public class ClippingImageView extends ImageView { private final Rect mClipRect = new Rect(); public ClippingImageView(Context context,AttributeSet attrs,int defStyle) { super(context,attrs,defStyle); initClip(); } public ClippingImageView(Context context,AttributeSet attrs) { super(context,attrs); initClip(); } public ClippingImageView(Context context) { super(context); initClip(); } private void initClip() { // post to message queue,so it gets run after measuring & layout // sets initial crop area to half of the view's width & height post(new Runnable() { @Override public void run() { setImageCrop(0.5f); } }); } @Override protected void onDraw(Canvas canvas) { // clip if needed and let super take care of the drawing if (clip()) canvas.clipRect(mClipRect); super.onDraw(canvas); } private boolean clip() { // true if clip bounds have been set aren't equal to the view's bounds return !mClipRect.isEmpty() && !clipEqualsBounds(); } private boolean clipEqualsBounds() { final int width = getWidth(); final int height = getHeight(); // whether the clip bounds are identical to this view's bounds (which effectively means no clip) return mClipRect.width() == width && mClipRect.height() == height; } public void toggle() { // toggle between [0...0.5] and [0.5...0] final float[] values = clipEqualsBounds() ? new float[] { 0f,0.5f } : new float[] { 0.5f,0f }; ObjectAnimator.ofFloat(this,"imageCrop",values).start(); } public void setImageCrop(float value) { // nothing to do if there's no drawable set final Drawable drawable = getDrawable(); if (drawable == null) return; // nothing to do if no dimensions are known yet final int width = getWidth(); final int height = getHeight(); if (width <= 0 || height <= 0) return; // construct the clip bounds based on the supplied 'value' (which is assumed to be within the range [0...1]) final int clipWidth = (int) (value * width); final int clipHeight = (int) (value * height); final int left = clipWidth / 2; final int top = clipHeight / 2; final int right = width - left; final int bottom = height - top; // set clipping bounds mClipRect.set(left,top,right,bottom); // schedule a draw pass for the new clipping bounds to take effect visually invalidate(); } }
真正的“魔法”是重写的onDraw()方法的附加行,其中给定的Canvas被剪切为由mClipRect定义的矩形区域.所有其他代码和方法主要用于帮助计算剪辑边界,确定裁剪是否合理以及动画.
从Activity中使用它现在减少到以下内容:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.image_activity); final ClippingImageView img = (ClippingImageView) findViewById(R.id.img); img.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { img.toggle(); } }); } }
布局文件将指向我们的自定义ClippingImageView,如下所示:
<mh.so.img.ClippingImageView android:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/dog" />
为了让您了解视觉过渡: