一、效果图

二、支持功能

  • 本地与网络图片
  • 可用于各大图片加载框架(Fresco,Glide,Picasso)
  • 图片缩放
  • 放大后的图片惯性滑动
  • 下拉缩小退出

三、核心实现方法

3.1 缩放 Matrix.postScale(float sx, float sy, float px, float py)

参数解析:

  • sx: 目标宽度 / 现有宽度
  • sy: 目标高度 / 现有高度
  • (px,py): 缩放焦点坐标

使用示例:

/**
 * 缩放手势监听
 */
private ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {

  @Override
  public boolean onScale(ScaleGestureDetector detector) {
    float scaleFactor = detector.getScaleFactor();
    float wantScale = mScale * scaleFactor;
    if (wantScale >= MIN_SCALE) {
      mScale = wantScale;
      focusX = detector.getFocusX();
      focusY = detector.getFocusY();
      mMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
      invalidate();
    }
    return true;
  }
};

3.2 移动 Matrix.postTranslate(float dx, float dy)

参数解析:

  • dx: 目标位置X坐标 - 当前位置X坐标
  • sy: 目标位置Y坐标 - 当前位置Y坐标

使用示例:

/**
 * 简单手势监听
 */
private GestureDetector.SimpleOnGestureListener mOnGestureListener = new GestureDetector.SimpleOnGestureListener() {

  ...

  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    if (!isAlwaysSingleTouch) {
      return true;
    }
    mMatrix.postTranslate(-distanceX, -distanceY);
    invalidate();
    return false;
  }

  ...
};

3.3 将Matrix的操作关联到ImageView上

View提供onDraw的方法,可以操作到Canvas,Canvas提供concat的方法来关联Matrix。每次针对Matrix有操作之后调用invalidate()刷新一下onDraw()即可。这就是个操作配置,而且是View早就提供好了的配置。

调用示例:

@Override
protected void onDraw(Canvas canvas) {
  int saveCount = canvas.save();
  canvas.concat(mMatrix);
  super.onDraw(canvas);
  canvas.restoreToCount(saveCount);
}

3.4 惯性滑动 

OverScroller.fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY)

参数解析:

  • (startX, startY): 初始位置坐标
  • (velocityX, velocityY): XY方向的初始速度
  • (minX, maxX, minY, maxY): 限定了移动后的位置边界

使用示例:

/**
 * 惯性滑动工具类
 * 使用fling方法开始滑动
 * 使用stop方法停止滑动
 */
private class FlingUtil implements Runnable {
  private int mLastFlingX = 0;
  private int mLastFlingY = 0;
  private OverScroller mScroller;
  private boolean mEatRunOnAnimationRequest = false;
  private boolean mReSchedulePostAnimationCallback = false;

  /**
   * RecyclerView使用的惯性滑动插值器
   * f(x) = (x-1)^5   1
   */
  private final Interpolator sQuinticInterpolator = new Interpolator() {
    @Override
    public float getInterpolation(float t) {
      t -= 1.0f;
      return t * t * t * t * t   1.0f;
    }
  };

  public FlingUtil() {
    mScroller = new OverScroller(getContext(), sQuinticInterpolator);
  }

  @Override
  public void run() {
    disableRunOnAnimationRequests();
    final OverScroller scroller = mScroller;
    if (scroller.computeScrollOffset()) {
      final int y = scroller.getCurrY();
      int dy = y - mLastFlingY;
      final int x = scroller.getCurrX();
      int dx = x - mLastFlingX;
      mLastFlingY = y;
      mLastFlingX = x;
      constrainScrollBy(dx, dy);
      postOnAnimation();
    }
    enableRunOnAnimationRequests();
  }

  public void fling(int velocityX, int velocityY) {
    mLastFlingX = 0;
    mLastFlingY = 0;
    mScroller.fling(0, 0, velocityX, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
    postOnAnimation();
  }

  public void stop() {
    removeCallbacks(this);
    mScroller.abortAnimation();
  }

  private void disableRunOnAnimationRequests() {
    mReSchedulePostAnimationCallback = false;
    mEatRunOnAnimationRequest = true;
  }

  private void enableRunOnAnimationRequests() {
    mEatRunOnAnimationRequest = false;
    if (mReSchedulePostAnimationCallback) {
      postOnAnimation();
    }
  }

  void postOnAnimation() {
    if (mEatRunOnAnimationRequest) {
      mReSchedulePostAnimationCallback = true;
    } else {
      removeCallbacks(this);
      ViewCompat.postOnAnimation(ZoomImageView.this, this);
    }
  }
}

Scroller只提供在基于已有位置和已有速度下的位置计算,需要主动调用scroller.getCurrY()和scroller.getCurrX()方法去获取位置信息。
这里使用的是RecyclerView中的惯性滑动的实现方式。

四、三个必要的细节处理

在有了上面的4个方法,基本上一个可缩放的ImageView所需要的功能都可以实现了。但是,一些细节方面的问题也不可忽视,比如说:

移动不能超过图片的边缘

在ImageView的ScaleType为FIT_CENTER时,获取真实的图片内容的宽高,毕竟需要缩放的是图片的内容

图片是否移动到最左侧或最右侧,用于ViewPager中的滑动判断

4.1 边缘处理

在移动前,校验此次的移动是否会造成图片内容是否会移动超出边界。Canvas关联的Matrix是针对整个ImageView的,我们需要知道ImageView中图片部分在ImageView中的初始位置信息,如图:

在得到图片内容在初始状态下的展示区域Rect(a,b,c,d)后,使用Matrix提供的Matrix.mapRect(Rect)方法,可以得到经历缩放后的展示区域。得到内容缩放后的展示区域后,与ImageView的展示区域Rect(0,0,W,H)作比较便可得出是否超出边界。

示例方法:

/**
 * 获得缩放移动后的图片内容的位置区域
 *
 * @param matrix
 * @return RectF
 */
private RectF getScaledRect(Matrix matrix) {
  RectF rectF = new RectF(mImageRectF);
  //转化为缩放后的相对位置
  matrix.mapRect(rectF);
  return rectF;
}

/**
 * 针对边缘问题,约束移动
 *
 * @param dx
 * @param dy
 */
private void constrainScrollBy(float dx, float dy) {
  RectF rectF = getScaledRect(mMatrix);
  float scaleImageWidth = rectF.width();
  float scaleImageHeight = rectF.height();

  if (scaleImageWidth > mWidth) {
    //right
    if (rectF.right   dx < mWidth) {
      dx = -rectF.right   mWidth;
    }
    //left
    if (rectF.left   dx > 0) {
      dx = -rectF.left;
    }
  } else {
    //center
    dx = -rectF.left   ((float) mWidth - scaleImageWidth) / 2;
  }

  if (scaleImageHeight > mHeight) {
    //bottom
    if (rectF.bottom   dy < mHeight) {
      dy = -rectF.bottom   mHeight;
    }
    //top
    if (rectF.top   dy > 0) {
      dy = -rectF.top;
    }
  } else {
    //center
    dy = -rectF.top   ((float) mHeight - scaleImageHeight) / 2;
  }

  mMatrix.postTranslate(dx, dy);
  invalidate();
  checkBorder();
}

4.2 获取ImageView中内容的宽高

针对不同的网络加载框架有不同的操作方式,这里一Fresco位示例:
PipelineDraweeControllerBuilder提供setControllerListener方法,可以设置一个图片加载的监听。

示例代码:

private ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
  @Override
  public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable anim) {
    if (imageInfo == null) {
      return;
    }
    int preWidth = imageInfo.getWidth();
    int preHeight = imageInfo.getHeight();
    if (preWidth != mWidth || preHeight != mHeight) {
      //获取到最新的图片内容的宽高
      mWidth = preWidth;
      mHeight = preHeight;
    }
  }

  @Override
  public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
    Log.d("zhufeng", "Intermediate image received");
  }

  @Override
  public void onFailure(String id, Throwable throwable) {
    throwable.printStackTrace();
  }
};

public void loadImage(int resizeX, int resizeY, Uri uri) {
  ImageRequest request = ImageRequestBuilder
      .newBuilderWithSource(uri)
      .setResizeOptions(new ResizeOptions(resizeX, resizeY))
      .build();
  PipelineDraweeController controller = (PipelineDraweeController) Fresco.newDraweeControllerBuilder().setControllerListener(controllerListener).setOldController(getController()).setImageRequest(request).build();
  setController(controller);
}

4.3 处理与ViewPager的滑动冲突

需要明确:

左滑时,当图片内容到达右侧边界,进行图片切换的处理(事件交由ViewPager处理)

右滑时,当图片内容到达左侧边界,进行图片切换的处理(事件交由ViewPager处理)

剩下的ImageView自己处理

ImageView中的处理:
在约束移动的时候标记图片是否已经触及左右边界。并提供方法:

/**
 * 用于ViewPager滑动拦截
 *
 * @param direction
 * @return
 */
public boolean canScroll(int direction) {
  return !((direction < 0 && isRightSide()) || (direction > 0 && isLeftSide()));
}

ViewPager中的处理:
在canScroll方法中进行状态判断。重写ViewPager:

/**
 * 相册ViewPager
 *
 * @author zhufeng on 2017/10/22
 */
public class AlbumViewPager extends ViewPager {

  ...

  @Override
  protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
    if (v instanceof ZoomImageView) {
      return ((ZoomImageView) v).canScroll(dx) || super.canScroll(v, checkV, dx, x, y);
    }
    return super.canScroll(v, checkV, dx, x, y);
  }

  ...

}

源码地址:

https://github.com/zhufeng1222/Gallery

到这里就结束啦.

以上就是Android实现图片查看功能的详细内容,更多关于Android 图片查看功能的资料请关注Devmax其它相关文章!

Android实现图片查看功能的更多相关文章

  1. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  2. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  4. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  5. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  6. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  7. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  8. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  9. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  10. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部