Android从零开始实现可以双指缩放,自由移动,双击放大的自定义ImageView
之前一直都是用别人的轮子,总感觉心里不踏实,所以自己也尝试这造一些别人造过的轮子,提升自己,也可以在平时开发中工作中更熟练的使用。
这一次从零开始,撸一个可单指移动,双击放大,双指缩放,双指移动的ImageView
这篇博文较长,如果向直接看完整代码,请直接拉到最后。
我们先来看看效果吧
第一步:继承ImageView,并让图片能等比缩放后居中显示
public class ZoomImageView extends ImageView {
private Matrix matrix;
public ZoomImageView(Context context) {
super(context);
init();
}
public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ZoomImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
setScaleType(ScaleType.MATRIX);
matrix = new Matrix();
}
}
布局时这样的
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.zoomimagetest.view.ZoomImageView
android:id="@+id/my_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/test5"/>
</androidx.constraintlayout.widget.ConstraintLayout>
运行后是这样的
图片没有显示全,那么接下来就让图片默认等比缩放然后居中显示
如果要实现等比缩放,需要知道ImageView 的宽高,以及图片的原始宽高,ImageView 的宽高这些信息可以再onMeasure方法中获取,而图片的宽高可以通过Drawable这个类拿到,下面代码中,获取图片的宽高时还有一个坑,这个后面再说。
//imageView的大小
private PointF viewSize;
//图片的大小
private PointF imageSize;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
viewSize = new PointF(width,height);
Drawable drawable = getDrawable();
imageSize = new PointF(drawable.getMinimumWidth(),drawable.getMinimumHeight());
}
然后我们ImageView的宽与图片的宽的比例,以及ImageView的高与图片的高的比例,对比宽和高的缩放比例,取较小的那个做为图片的缩放比例。
这里解释下为什么要用较小的那个做为图片的缩放比例,因为我们需要图片等比缩放,并且显示全,这里在同一部手机上,相同的这个自定义ZoomImageView中,可以认为viewSize的x,y是固定的,也就是在下面的式子中,分子是固定的,所以,如果整个值就越小,说明分母就越大,也就说明图片的这条边与这个view的这条边的差距越大,所以按照相差较大的这条边的缩放比例来缩放,肯定能保证在view中显示全。
float scalex = viewSize.x/imageSize.x;
float scaley = viewSize.y/imageSize.y;
float scale = scalex<scaley?scalex:scaley;
得到我们的缩放比例后就可以对图片进行缩放了
//缩放后图片的大小
private PointF scaleSize = new PointF();
public void scaleImage(PointF scaleXY){
matrix.setScale(scaleXY.x,scaleXY.y);
//这里将缩放后的图片大小保存一下
scaleSize.set(scaleXY.x * imageSize.x,scaleXY.y * imageSize.y);
setImageMatrix(matrix);
}
到这一步的完整代码如下
@SuppressLint("AppCompatCustomView")
public class ZoomImageView extends ImageView {
private Matrix matrix;
//imageView的大小
private PointF viewSize;
//图片的大小
private PointF imageSize;
//缩放后图片的大小
private PointF scaleSize = new PointF();
//最初的宽高的缩放比例
private PointF originScale = new PointF();
public ZoomImageView(Context context) {
super(context);
init();
}
public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ZoomImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
setScaleType(ScaleType.MATRIX);
matrix = new Matrix();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
viewSize = new PointF(width,height);
Drawable drawable = getDrawable();
imageSize = new PointF(drawable.getMinimumWidth(),drawable.getMinimumHeight());
showCenter();
}
/**
* 设置图片居中等比显示
*/
private void showCenter(){
float scalex = viewSize.x/imageSize.x;
float scaley = viewSize.y/imageSize.y;
float scale = scalex<scaley?scalex:scaley;
scaleImage(new PointF(scale,scale));
}
/**
* 将图片按照比例缩放,这里宽高缩放比例相等,所以PoinF 里面的x,y是一样的
* @param scaleXY
*/
public void scaleImage(PointF scaleXY){
matrix.setScale(scaleXY.x,scaleXY.y);
scaleSize.set(scaleXY.x * imageSize.x,scaleXY.y * imageSize.y);
setImageMatrix(matrix);
}
运行效果如下
到这里,图片是显示全了,但是并没有居中显示,那么接下来就来处理图片居中显示的问题。
/**
* 对图片进行x和y轴方向的平移
* @param pointF
*/
public void translationImage(PointF pointF){
matrix.postTranslate(pointF.x,pointF.y);
setImageMatrix(matrix);
}
上面是对图片进行平移的方法
接下来需要计算,如果要图片在ImageView中居中显示,需要的平移量
if (scalex<scaley){
translationImage(new PointF(0,viewSize.y/2 - scaleSize.y/2));
}else {
translationImage(new PointF(viewSize.x/2 - scaleSize.x/2,0));
}
前面讲过,两个方向的缩放值越小,说明那个方向的view的值与图片的值差距就越大,按照这个较小缩放值,缩放后,这个方向就正好充满view,那么此方向就不需要平移,平移另外一个方向即可。这里如果没有理解的,可以自己在纸上画个图。
那么到这里,第一步就等比缩放,居中显示就完成了。此时的全部代码如下
@SuppressLint("AppCompatCustomView")
public class ZoomImageView extends ImageView {
private Matrix matrix;
//imageView的大小
private PointF viewSize;
//图片的大小
private PointF imageSize;
//缩放后图片的大小
private PointF scaleSize = new PointF();
//最初的宽高的缩放比例
private PointF originScale = new PointF();
//imageview中bitmap的xy实时坐标
private PointF bitmapOriginPoint = new PointF();
public ZoomImageView(Context context) {
super(context);
init();
}
public ZoomImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ZoomImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private