GitHub源码地址:https://github.com/604982372/PhotoZoomChoose
效果显示:
1.PhotoView简单介绍
PhotoView是一个常用的图片预览控件,可缩放图片。
图片放大缩小部分PhotoView已经帮我们实现了,这里我们只需要记录单击所选择的坐标点,并在图片放大缩小的时候根据当前图片的缩放公式来转换所选择坐标并用标注显示出来。
在PhotoView中,双指放大,普通拖动等操作都要通过Matrix来实现,即把ImageView的ScaleType设置为Matrix。我们一般都是希望图片初始化的时候就出现在屏幕中间,对于普通的ImageView来说,我们可以通过ImageView的ScaleType来控制,而对于PhotoView,ScaleType只能为Matrix,我们就只能在显示的时候做一些转化了。
private void updateBaseMatrix(Drawable d) {
ImageView imageView = getImageView();
if (null == imageView || null == d) {
return;
}
final float viewWidth = imageView.getWidth();
final float viewHeight = imageView.getHeight();
final int drawableWidth = d.getIntrinsicWidth();
final int drawableHeight = d.getIntrinsicHeight();
mBaseMatrix.reset();
final float widthScale = viewWidth / drawableWidth;
final float heightScale = viewHeight / drawableHeight;
if (mScaleType == ScaleType.CENTER) {
mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
(viewHeight - drawableHeight) / 2F);
} else if (mScaleType == ScaleType.CENTER_CROP) {
float scale = Math.max(widthScale, heightScale);
mBaseMatrix.postScale(scale, scale);
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
(viewHeight - drawableHeight * scale) / 2F);
} else if (mScaleType == ScaleType.CENTER_INSIDE) {
float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
mBaseMatrix.postScale(scale, scale);
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
(viewHeight - drawableHeight * scale) / 2F);
} else {
RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
switch (mScaleType) {
case FIT_CENTER:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
break;
case FIT_START:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
break;
case FIT_END:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
break;
case FIT_XY:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
break;
default:
break;
}
}
resetMatrix();
}
根据自己设置的ScaleType来计算图片需要缩放的比例和移动的距离,ScaleType为Matrix的时候,不改变原图大小从控件的左上角开始绘制,超过ImageView部分不再显示。updateBaseMatrix方法的转化就导致了加载到内存里面的图片跟显示出来的图片不一样了。如下图,从左到右一次为UI图片、加载到手机里面得图片(ScaleType为Matrix显示效果)、实际ScaleType设置的效果(默认为FIT_CENTER)photoview处理后显示的图片。
2.图片放大和缩小选择的坐标点介绍
缩放过程中为了减少坐标多次转换导致丢失精度,这里将选择的坐标转换成ui图片的坐标保存下来,缩放过程中根据ui图片已选择的点坐标转换成当前显示图片中的坐标。分为保存坐标和显示坐标两部分。
保存坐标的时候(上图从右向左):界面显示出来的图片-->加载到手机里面的图片-->ui图片。
显示坐标的时候(上图从左向右):ui图片-->加载到手机里面的图片-->界面显示出来的图片。
提示:加载到内存中的图片可以再布局文具文件中看到。
2.1保存坐标
这里我们只保存落在图片上的坐标,对于落在图片外面的坐标我们选择过滤掉,选择图片的时候将图片外面的区域设置为透明。
/**
* @param x
* @param y
* @return 判断点击区域是否在透明区域
*/
private boolean isTouchPointInTransparent(float x, float y)
{
Drawable drawable = this.getDrawable();
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
int pixel = 0;
if (y > 0 && y < mBitHeight)
{
pixel = bitmap.getPixel((int) x, (int) y);//获取像素值
}
return pixel == 0;
}
在OnGestureListener中添加下面注释的方法
public interface OnGestureListener
{
public void onDrag(float dx, float dy);
public void onScale(float scaleFactor, float focusX, float focusY);
public void setUpXY(float x, float y);//手离开屏幕
public void setDownXY(float x, float y);//手点击屏幕
public void setMoveXY(float x, float y);//手拖动界面
public void setTwofingerTonch(boolean b);//双手操作
public void setpostInvalidate();//刷新界面,刷新坐标点
}
在setUpXY里面保存坐标
@Override
public void setUpXY(float x, float y)
{
pointX = pointX + (moveX - pointX);
pointY = pointY + (moveY - pointY);
if (!isDraw)
{
isDraw = true;
return;
}
float touchWidth, touchHeight;
if (!isWidthMoreHeight)
{
//左边距
float dx = (getViewDisWidth() - (getDisWidth())) / 2;
touchWidth = (pointX - mLeftX - dx) * mBitWidth / (getDisWidth());
touchHeight = (pointY - mLeftY) * mBitHeight * 1.0000f / (getDisHight());
}
else
{
//上边距
float dy = (getViewDisHeight() - (getDisHight())) / 2;
Log.v("3699上边距", dy + "");
touchWidth = (pointX - mLeftX) * mBitWidth * 1.0000f / (getDisWidth());
touchHeight = (pointY - mLeftY - dy) * mBitHeight * 1.0000f / (getDisHight());
}
//筛选点击坐标,当点击的坐标落在图片区域外面的时候,不绘制红点
boolean touchPointInTransparent;
touchPointInTransparent = isTouchPointInTransparent(touchWidth, touchHeight);
if (!touchPointInTransparent)
{
if (isTouch && !mTwofingerTonch)
{//如果是可以点击修改,则保存更新坐标
onSelectedDrawX = touchWidth * mOriginalWidth * 1.00f / mBitWidth;
onSelectedDrawY = touchHeight * mOriginalHeight * 1.00f / mBitHeight;
postInvalidate();
}
}
}
onSelectedDrawX ,onSelectedDrawY为UI图片中对应的坐标;mOriginalWidth,mOriginalHeight 为UI图片的宽高(右键属性里面的宽高)。这里有个问题,在手滑动图片和双向缩放图片操作后抬起手指会执行setUpXY方法,这时候并不是选择坐标操作。所以在手移动操作和双手操作的时候禁止绘制。
public void setMoveXY(float x, float y)
{
moveX = x;
moveY = y;
isDraw = false;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector)
{
Log.w("3699双手向外侧", "onScaleBegin");
mListener.extendedImg();
mListener.setTwofingerTonch(true);
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector)
{
Log.w("3699双手向外侧", "onScaleEnd");
mListener.setTwofingerTonch(false);
}
2.2显示坐标
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (!isDraw || mTwofingerTonch)
{
return;
}
if (!isWidthMoreHeight)
{
onDrawCurrentX = (onSelectedDrawX * getDisWidth() * 1.00f) / mOriginalWidth + getLeftDx() + mLeftX - (mWhiteBitmap.getWidth() / 2);
onDrawCurrentY = (onSelectedDrawY * getDisHight() * 1.00f) / mOriginalHeight + mLeftY - (mWhiteBitmap.getHeight() / (2));//-bitmapWhiteY;
}
else
{
onDrawCurrentX = (onSelectedDrawX * getDisWidth() * 1.00f) / mOriginalWidth + mLeftX - (mWhiteBitmap.getWidth() / 2);
onDrawCurrentY = (onSelectedDrawY * getDisHight() * 1.00f) / mOriginalHeight + getTopDy() + mLeftY - (mWhiteBitmap.getHeight() / (2));//-bitmapWhiteY;
}
canvas.drawBitmap(mWhiteBitmap, onDrawCurrentX, onDrawCurrentY, whitePaint);//绘制坐标
}
图片缩放过程中,图片大小是实时变化的,为了跟图片缩放一致,这里想象控件宽高也是跟着变化的,图片是正方形的时候,控件大小与图片大小相等,其他情况下控件大小大于图片大小。图片缩放过程中执行OnGestureListener里面的onDrag方法在实时返回图片当前的大小和当前图片左上角的位置getLeftPointF(imageView)。图片左上角的位置会随着图片的拖拽而不断变化,当图片左上角的位置已经移除屏幕的时候,X轴坐标或者Y轴坐标为负数。
//获取图片的上坐标
private PointF getLeftPointF(ImageView mImgPic)
{
Rect rectTemp = mImgPic.getDrawable().getBounds();
float[] values = new float[9];
mSuppMatrix.getValues(values);
float leftX = values[2];
float leftY = values[5];
float width = rectTemp.width() * values[0];
float height = rectTemp.height() * values[4];
setImgPointF(width, height, leftX, leftY);
return new PointF(leftX, leftY);
}
PhotoViewZoom中实时接收当前的大小和左上角的位置。
protected void setImgPointF(float scaleWidth, float scalehight, float leftX, float leftY)
{
mScaleWidth = scaleWidth;
mScalehight = scalehight;
mLeftX = leftX;
mLeftY = leftY;
}
图片在缩放或拖拽的过程中图片宽高,控件对应宽高,图片左上角的坐标(即左边距和上边距),当前图片缩放比例也会跟着实时发生变化。
/**
* 图片缩放后宽
*/
public float getDisWidth()
{
return mScaleWidth * mScaleWMode;
}
/**
* 图片缩放后高
*/
public float getDisHight()
{
return mScalehight * mScaleWMode;
}
/**
*图片缩放过程中,图片大小是实时变化的,为了跟图片缩放一致,这里想象控件宽高也是跟着变化的
* 缩放过程中控件对应的宽
*/
public float getViewDisWidth()
{
return mMeasureWidth * getScaleMultiple();
}
/**
* 缩放过程中控件对应的高
*/
public float getViewDisHeight()
{
return mMeasureHeight * getScaleMultiple();
}
/**
* 当图片高大于宽的时候,实际显示图片的左边距
*/
public float getLeftDx()
{
return (getViewDisWidth() - getDisWidth()) * 1.00f / 2;
}
/**
* 当图片宽大于高的时候,实际显示图片的上边距
*/
public float getTopDy()
{
return (getViewDisHeight() - getDisHight()) * 1.00f / 2;
}
//图片当前缩放倍数
public float getScaleMultiple()
{
return mScaleWidth * 1.00f / mBitWidth;
}
3.布局
<cn.xiwu.photozoomchoose.myview.PhotoViewZoom
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/zoom1"/>
ScaleType.FIT_CENTER模式是ImageView
的默认模式,如果没有设置ScaleType
时,将采用这种模式展示图片。在该模式下,图片会被等比缩放到能够填充控件大小,并居中展示。图片宽高显示分两种情况:
a.如果图片的宽大于高,则图片的宽为控件测量的宽,图片的高通过图片宽缩放倍数等比计算得出;
private static final float mOriginalWidth = 600f;
private static final float mOriginalHeight = 505f;
if (mBitHeight > mBitWidth)//高大于宽
{
mScaleWMode = mDisplayHeight * 1.000f / mBitHeight;
}
else
{
mScaleWMode = mDisplayWidth * 1.000f / mBitWidth;
}
GitHub源码地址:https://github.com/604982372/PhotoZoomChoose