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. 替换word中的数据,并给导入word的图片添加水印

    public static void ExportWord(string tempFilePath, string outPath, Dictionary<string, string> ...

  2. 关于RocketMQ消息消费与重平衡的一些问题探讨

    其实最好的学习方式就是互相交流,最近也有跟网友讨论了一些关于 RocketMQ 消息拉取与重平衡的问题,我姑且在这里写下我的一些总结. ## 关于 push 模式下的消息循环拉取问题 之前发表了一篇关 ...

  3. .NET Core 3.1 编写混合 C++ 程序

    前言 随着 .NET Core 3.1 的第二个预览版本发布,微软正式将 C++/CLI 移植到 .NET Core 上,从此可以使用 C++ 编写 .NET Core 的程序了. 由于目前仅有 MS ...

  4. SpringBoot 整合 Elasticsearch深度分页查询

    es 查询共有4种查询类型 QUERY_AND_FETCH: 主节点将查询请求分发到所有的分片中,各个分片按照自己的查询规则即词频文档频率进行打分排序,然后将结果返回给主节点,主节点对所有数据进行汇总 ...

  5. QHDYZ模拟赛20191027 提前透题

    你们想的美 我给你们透一下题目名称 别刷博客了快去做题

  6. 深度学习tensorflow实战笔记 用预训练好的VGG-16模型提取图像特征

    1.首先就要下载模型结构 首先要做的就是下载训练好的模型结构和预训练好的模型,结构地址是:点击打开链接 模型结构如下: 文件test_vgg16.py可以用于提取特征.其中vgg16.npy是需要单独 ...

  7. 「每日五分钟,玩转 JVM」:GC 概览

    前言 GC(Garbage Collection)是我们在学习 JVM 的过程中不可避免的一道坎,接下来,我们就来系统的学习一下 GC. 做一件事情之前,我们一定要去知道我们为什么要去做,这里不仅仅指 ...

  8. mysql General error: 1366 Incorrect string value: '\xF0\x9F\x91\x8D\xF0\x9F...' for column 'dianpumiaoshu' at row 1 解决方法

    mysql General error: 1366 Incorrect string value: '\xF0\x9F\x91\x8D\xF0\x9F...' for column 'dianpumi ...

  9. vmware centos共享文件夹

    vmware centos共享文件夹 查看VMware Tools 是否安装成功 <pre>vmware-hgfsclient</pre> 出现共享文件夹<pre> ...

  10. Git如何fork别人的仓库并作为贡献者提交代码

    例如 要fork一份google的MLperf/inference代码,下面介绍具体做法:预备知识git里的参考有几种表示,分别是上游仓库,远程仓库和本地仓库,逻辑关系如下拉取代码的顺序:别的大牛的代 ...