Android开发过程中,想必都使用过PhotoView来实现图片展示的功能。在最新版的sdk(android-23)有了一个原生的photoView,并且代码实现也很简单,逻辑也很清晰。我们在实际的工作中,遇到的需求可能与这些photoview现有功能有些细微的差别,需要修改,或者重新开发。本文简单介绍下android-23中photoview涉及到的相关技术,相信读者看完后会发现,其实很简单。以下为实现思路和步骤

  • 1 自定义一个View

通过自定义视图,继承View,为外界提过public接口方法来设置drawable,或者提供自定义属性在layout文件中设置drawable。在onDraw方法中,将drawable画到canvas上。

  • 2 首次显示drawable

图片首次显示时,一般要居中全部显示在屏幕内,并且至少x轴或y轴方向占满屏幕。如何实现?

对于一个drawable,可以获取其原始的宽、高:

int drawableWidth = mDrawable.getIntrinsicWidth();
int drawableHeight = mDrawable.getIntrinsicHeight();

然后将原始的宽、高,保存到一个RectF对象中:

private RectF mTmpRectSrc = new Rect(); 
mTmpRectSrc.set(0, 0, drawableWidth, drawableHeight);

自定义的视图在measure完之后,会有一个宽、高:

int viewWidth = getWidth();
int viewHeight = getHeight();

将view的宽、高也保存到一个RectF对象中:

private RectF mTmpRectDst = new RectF();
mTmpRectDst.set(0, 0, viewWidth, viewHeight);

这时候,可以计算出一个从 mTmpRectSrc 到 mTmpRectDst的变换矩阵:

private Matrix mDrawMatrix = new Matrix();
mDrawMatrix.setRectToRect(mTmpRectSrc, mTmpRectDst, Matrix.ScaleToFit.CENTER);

Matrix.scaleToFit.CENTER参数确保图片最后可以:居中全部显示在屏幕内,并且至少x轴或y轴方向占满屏幕。

最后,在onDraw方法中,结合上面到到的mDrawMatrix,即可将drawable画到canvas上:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawable != null) {
int saveCount = canvas.getSaveCount();
canvas.save();
canvas.concat(mDrawMatrix);
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
  • 3 如何获取drawable的实现显示区域

上面讲到了两个RectF区域,和一个matrix。分别是:

mTmpRectSrc: drawable原始矩形区域

mTmpRectDst: view显示矩形区域

mDrawMatrix: 从mTmpRectSrc到mTmpRectDst的变换矩阵

如何的到矩阵变换后,图片的实际显示区域?这里要注意,mTmpRectDst并非图片的显示区域,这个矩形区域仅仅是用来限定图片的显示区域,而最终的显示区域并不是他。方法很简单:

private RectF mTranslateRect = new Rect();
mDrawMatrix.mapRect(mTranslateRect, mTmpRectSrc);

然后通过mTranslateRect,就可以获取最终显示图片矩形区域的letf/top/right/bottom

  • 4 识别缩放手势和滑动手势

我们可以自己在onTouchEvent方法中识别出滑动、缩放等手势。但朋友们是否有感觉,这些判断逻辑自己实现起来很繁琐,很难保证逻辑的完美无缺。其实android系统提供了两个很好用的辅助类来帮我们做这些事情:

GestureDetector
ScaleGestureDetector

GestureDetector可以用来识别滑动(scroll),ScaleGestureDetector可以用来识别缩放(scale)

我们来看看如何处理scroll。

首先定一个GestureDetector:

mGestureDetector = new GestureDetector(context, this);

this是指GestureDetector.OnGestureListener的实现类,这里可以用view自己来实现。实现的onScroll方法如下:

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mDrawMatrix.mapRect(mTranslateRect, mTmpRectSrc);
distanceX = -distanceX;
distanceY = -distanceY;
mDrawMatrix.postTranslate(distanceX, distanceY);
invalidate();
return true;
}

直接将接口传给我们的x,y方向偏移量设置到matrix中,然后invalidate,view重新draw出来的drawable就移动了。实际应用中,我们可以先获取drawable的实际显示矩形区域,再根据x,y方向偏移量,计算得出最后的偏移量,来限制不可以将图片划出view显示区域。

还差一步,需要在view的onTouchEvent方法中,将MotionEvent事件传给GestureDetector,这样就可以用手指来移动图片了:

@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
return true;
}

如果少了这步,GestureDetector根据接触不到MotionEvent,也就无法帮我们识别相关手势。

处理缩放几本一样,首先定义detector:

private ScaleGestureDetector mScaleGestureDetector = new ScaleGestureDetector(context, this);

this是ScaleGestureDetector.OnScaleGestureListener的实现类,这里可以用view自己来实现。实现的方法为:

@Override
public boolean onScale(ScaleGestureDetector detector) {
float factor = detector.getScaleFactor(); mDrawMatrix.mapRect(mTranslateRect, mTmpRectSrc); int width = getWidth();
float centerX;
if (factor > 1 && mTranslateRect.right - mTranslateRect.left < width) {
centerX = mTranslateRect.left * width / (mTranslateRect.left + width - mTranslateRect.right);
} else {
centerX = detector.getFocusX();
} int height = getHeight();
float centerY;
if (factor > 1 && mTranslateRect.bottom - mTranslateRect.top < height) {
centerY = mTranslateRect.top * height / (mTranslateRect.top + height - mTranslateRect.bottom);
} else {
centerY = detector.getFocusY();
}
mDrawMatrix.postScale(factor, factor, centerX, centerY);
invalidate();
return true;
} @Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
} @Override
public void onScaleEnd(ScaleGestureDetector detector) {
}

首先,onScaleBegin方法要返回true,否则不会回调onScale。在onScale中,可以获取缩放中心点、缩放比例,以及当前图片的实际显示区域,根据实际需求,计算出一个最终的缩放比例、中心点。最后将结果postScale到matrix中,invalidate,view重新draw之后,就可看到缩放的效果

最后,需要在view的onTouchEvent方法中,将触摸时间告知scaleDetector。否则,scaleDetector根本接触不到相关事件,不会回调相关方法:

@Override
public boolean onTouchEvent(MotionEvent event) {
mScaleGestureDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
return true;
}
  • 5 总结

通过以上讲解,可以看出只要我们熟悉了matrix、rect的相关用法,Photoviewer的实现技术其实很简单。常见的fling、snap动画效果,photoview中也有实现,读者感兴趣的话,可以查看源码自行研究。

Android PhotoView基本功能实现的更多相关文章

  1. Android Studio调试功能使用总结【转】

    Android Studio调试功能使用总结[转]   这段时间一直在使用Intellij IDEA, 今天把调试区工具的使用方法记录于此. 先编译好要调试的程序. 1.设置断点 选定要设置断点的代码 ...

  2. Android 实现闹钟功能

      原文地址:Android 实现闹钟功能作者:Android_Learners 一.手机闹钟主要用到了AlarmManager类,AlarmManager类提供了访问系统定时服务的途径,开发人员可以 ...

  3. Android Studio调试功能使用总结---转

    Android Studio调试功能使用总结[转]   这段时间一直在使用Intellij IDEA, 今天把调试区工具的使用方法记录于此. 先编译好要调试的程序. 1.设置断点 选定要设置断点的代码 ...

  4. 集成Android免费语音合成功能(在线、离线、离在线融合)

    集成Android免费语音合成功能(在线.离线.离在线融合),有这一篇文章就够了(离线)集成Android免费语音合成功能(在线.离线.离在线融合),有这一篇文章就够了(离在线融合) 转眼间,大半年没 ...

  5. Android中选项卡功能的实现

    Android中选项卡功能的实现 Android中使用TabHost和TabWidget来实现选项卡功能.TabHost必须是布局的根节点,它包含两个子节点: TabWidget,显示选项卡: Fra ...

  6. Android社会化分享功能的实现步骤

    众所周知,互联网是一个资源共享的地方,在网络上,我们可以分享我们所有认为好的资源.而随着互联网信息爆发式的增长,我们习惯了一键分享功能,比如:微博分享.微信分享.QQ空间分享.人人网分享等等.由此可见 ...

  7. Android微信分享功能实例+demo

    Android微信分享功能实例 1 微信开放平台注册 2 获得appId,添加到程序中,并运行程序 3 使用应用签名apk生成签名,添加到微信开放平台应用签名,完成注册 4 测试分享功能. 有问题请留 ...

  8. (转载) Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框

    Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框 标签: Android清除功能EditText仿IOS的输入框 2013-09-04 17:33 70865人阅读  ...

  9. Android 调用摄像头功能【拍照与视频】

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/ma_hoking/article/details/28292973 应用场景: 在Android开发 ...

随机推荐

  1. Google Tensorflow 迁移学习 Inception-v3

    附上代码加数据地址 https://github.com/Liuyubao/transfer-learning ,欢迎参考. 一.Inception-V3模型 1.1 详细了解模型可参考以下论文: [ ...

  2. Windows 10 中CPU虚拟化已开启,但是docker无法运行

    在管理员模式下的PowerShell中执行: bcdedit /set hypervisorlaunchtype Auto 然后重启电脑即可

  3. 深入理解.NET Core的基元(二) - 共享框架

    原文:Deep-dive into .NET Core primitives, part 2: the shared framework 作者:Nate McMaster 译文:深入理解.NET Co ...

  4. 使用Windows Powershell卸载和安装Win10 原生应用的方法

    新装的Win10带有大量的实际工作和生活中不怎么常用的APP,如果觉得这些APP占用磁盘空间或者想要卸载这些应用(APP),Win10下并不能使用Windows 应用管理器直接图形化地卸载这些应用,而 ...

  5. 7.25 NOIP模拟8

    这次考试前面状态还行,后两个小时真是一言难尽,打了个T3的n^2暴力就懵逼了,不知道怎么优化. T1.匹配 看了一边题发现不太懂(这不是考试的难度啊),然后水完T2后回来5分钟水过,非常愉快的一道题. ...

  6. Android so 文件

    要点 不同的 CPU 架构需要不同的 so 文件 NDK平台不是后向兼容的,而是前向兼容的. ABI 的概念,每一个 Cpu架构对应一个 ABI(Application Binary Interfac ...

  7. GO 基础学习笔记(1) | 简介

    一.官网查看系列 Golang官网:https://golang.org/ Golang中文网:https://studygolang.com/(推荐) 项目学习网站: https://gowalke ...

  8. idea用hibernate创建一个表两个主键时遇到的问题

    1>> idea功能简单,最大化的实现傻瓜式操作,不需要像eclipse一样手敲代码,尤其是在创建主键多个或者主键映射时. 2>> (1).首先,idea创建复合主键映射时,需 ...

  9. sqlite修改表、表字段等与sql server的不同之处

    sqlite中只支持 ALTER TABLE 命令的 RENAME TABLE 和 ADD COLUMN. 其他类型的 ALTER TABLE 操作如 DROP COLUMN,ALTER COLUMN ...

  10. jquery正确获取iframe里元素的方法

    <iframe id="_ae_frame" width="100%" height="100%" frameborder=" ...