*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

某次被问到如何实现一个滚筒状的控件,就是可以将一张很长的图片沿着Y轴无限旋转,如下图所示:

大概就是这个意思,当时还不知道图片可以裁剪,想不出整个流程怎么搞,后来得知Bitmap有裁剪功能,才想到这个功能怎么实现,花了一下午时间整了一下有了成果。
这是这张长图:

然后旋转起来就是这个样子:

上面这个效果在实际运行过程中是非常流畅的,这张图片是按照每秒几帧截的,所以看起来一顿一顿的。

先来说说如何实现:

第一次:先按照屏幕的宽度截取这张长图的起始部分。
第二次:以偏移量开始,重复第一次的行为。

最后:当这张图片的结尾部分不足以支撑整个屏幕的宽度时,先截取这张图片的末尾部分,绘制。然后再以剩余的宽度截取图片的头部部分,绘制。依次进行,直至重新回到第一次。

/*** Created by shangbin on 2016/6/16.* Email: sahadev@foxmail.com*/
public class CylinderImageView extends View {//用于裁剪的原始图片资源private Bitmap mSourceBitmap = null;// 图片的高宽private int mBitmapHeight, mBitmapWidth;// 移动单位,每次移动多少个单位private final int mMoveUnit = 1;// 图片整体移动的偏移量private int xOffset = 0;private Bitmap mPointerA, mPointerB;// 用于持有两张拼接图片的引用,并释放原先的图片资源/*** 循环滚动标志位*/private boolean mRunningFlag;private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what == 0) {invalidate();}}};public CylinderImageView(Context context, AttributeSet attrs) {super(context, attrs);initVideoView();}public CylinderImageView(Context context) {super(context);initVideoView();}public CylinderImageView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initVideoView();}private void initVideoView() {// 获取需要循环展示的图片的高宽mSourceBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.android_m_hero_1200);mBitmapHeight = mSourceBitmap.getHeight();mBitmapWidth = mSourceBitmap.getWidth();mRunningFlag = true;setFocusableInTouchMode(true);requestFocus();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);// 简单设置一下控件的宽高,这里的高度以图片的高度为准setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mBitmapHeight, MeasureSpec.getMode(heightMeasureSpec)));}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);recycleTmpBitmap();final int left = getLeft();final int top = getTop();final int right = getRight();final int bottom = getBottom();// 计算图片的高度int height = bottom - top;// 第一张图的宽带int tempWidth = right - left;// 如果一张图片轮播完,则从头开始if (xOffset >= mBitmapWidth) {xOffset = 0;}// 重新计算截取的图的宽度tempWidth = xOffset + tempWidth >= mBitmapWidth ? mBitmapWidth - xOffset : tempWidth;mPointerA = Bitmap.createBitmap(mSourceBitmap, xOffset, 0, tempWidth, height > mBitmapHeight ? mBitmapHeight : height);Paint bitmapPaint = new Paint();// 绘制这张图canvas.drawBitmap(mPointerA, getMatrix(), bitmapPaint);// 如果最后的图片已经不足以填充整个屏幕,则截取图片的头部以连接上尾部,形成一个闭环if (tempWidth < right - left) {Rect dst = new Rect(tempWidth, 0, right, mBitmapHeight);mPointerB = Bitmap.createBitmap(mSourceBitmap, 0, 0, right - left - tempWidth,height > mBitmapHeight ? mBitmapHeight : height);// 将另一张图片绘制在这张图片的后半部分canvas.drawBitmap(mPointerB, null, dst, bitmapPaint);}// 累计图片的偏移量xOffset += mMoveUnit;//由handler的延迟发送产生绘制间隔if (mRunningFlag) {mHandler.sendEmptyMessageDelayed(0, 1);}}/*** 回收临时图像*/private void recycleTmpBitmap() {if (mPointerA != null) {mPointerA.recycle();mPointerA = null;}if (mPointerB != null) {mPointerB.recycle();mPointerB = null;}}/*** 恢复*/public void resume() {mRunningFlag = true;invalidate();}/*** 暂停*/public void pause() {mRunningFlag = false;}/*** 回收清理工作*/@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();pause();recycleTmpBitmap();mSourceBitmap.recycle();}
}

以上是CylinderImageView的实现代码。

其中有两个公开方法:
resume() 用于在Activity的onResume()中调用,以便恢复旋转。
pause() 用于在Activiyt的onPause()中调用,以便暂停旋转。

下面是使用示例:

public class MainActivity extends AppCompatActivity {private CylinderImageView cylinderImageView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);cylinderImageView = (CylinderImageView) findViewById(R.id.cylinderImageView);}@Overrideprotected void onResume() {super.onResume();cylinderImageView.resume();}@Overrideprotected void onPause() {super.onPause();cylinderImageView.pause();}
}

因为这个控件内部涉及大量的图片操作,所以大伙一定很关心内存的使用。我为此专门做了内存测试,结果内存占用非常小:

这张图是没有使用CylinderImageView时应用程序所占用的内存:17.9MB:

我这里所使用的示例图片的长宽是1200x353,也就是说它被加载到内存中所占用的内存大小是1200x353x4/1024/1024=1.61MB.

再加上在屏幕上所显示的Bitmap所占用的内存为:1080x353x4/1024/1024=1.45MB.(这里的1080是我的屏幕宽度,在屏幕上显示的图片占了整个屏幕的宽度,所以是1080)

因为内存回收并不是实时的,所以在内存使用最高峰时,所使用的内存=17.9+1.61+1.45x2=22.43.

实际的运行占用内存为:


上面两张图片的差距是图片内存回收的差值,但是这里的高峰内存值与我们计算的内存值有些差距,这是因为除了内存之外,我们还在XML布局文件中声明了控件以及加载控件也占用了一定的内存空间。

调用pause()方法的内存状况:

调用resume()方法的内存状况:


Activity销毁之后所占用的内存:

通过上面一系列图示说明这个控件将内存的消耗控制在了合理的范围之内,没有滥用内存。

最后,大功告成,不知道是否明白我说的呢?

相关Demo演示请参见:https://github.com/sahadev/CylinderImageView

如何实现一个循环显示超长图片的控件相关推荐

  1. MFC入门(三)-- MFC图片/文字控件(循环显示文字和图片的小程序)

    惯例附上前几个博客的链接: MFC入门(一)简单配置:http://blog.csdn.net/zmdsjtu/article/details/52311107 MFC入门(二)读取输入字符:http ...

  2. wpf加载上千张图片部分图片不显示_开源WPF控件库MaterialDesignInXAML推荐

    (给DotNet加星标,提升.Net技能) 转自:沙漠之狐耶dotnet9.com/?p=2180 前言 介绍一个开源的C# WPF开源控件库,非常漂亮,重点是开源哦 WPF做桌面开发是很有优势的,除 ...

  3. CropImageView android上的一个图片裁剪控件

    CropImageView **文前:**本文非常容易让读者看的云里雾里,建议直接看效果图,觉得有用就去看源码吧. CropImageView的原型来自Cropimage_demo,是android上 ...

  4. java的显示图片的控件_java Swing GUI 入门-图片和控件可视化

    java Swing GUI 入门-图片和控件可视化 觉得有用的话,欢迎一起讨论相互学习~ 加入一张图片 一个Imagelabel 设置居中,命名为logoLabel,Custom Create 打勾 ...

  5. Android学习笔记26:图片切换控件ImageSwitcher的使用

    在Windows操作系统中,要查看多张图片,可以通过使用"Windows照片查看器"在"上一张"和"下一张"之间切换,进行多张图片的浏览. ...

  6. Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来

    转载请注明出处:http://blog.csdn.net/sinyu890807/article/details/8769904 首先题外话,今天早上起床的时候,手滑一下把我的手机甩了出去,结果陪伴我 ...

  7. 原创“.NET研究”企业级控件库之图片浏览控件

    在上两篇:我介绍了原创企业级控件库之组合查询控件 和原创企业级控件库之大数据量分页控件,受到了很多朋友的支持,给了我很大的动力,在此我特表感谢.有的朋友要求把源码提供上来,我在第一篇就讲了,源码会在我 ...

  8. android从九宫格全屏预览,仿微信朋友圈展示图片的九宫格图片展示控件,支持点击图片全屏预览大图...

    AssNineGridView 仿微信朋友圈展示图片的九宫格图片展示控件,支持点击图片全屏预览大图(可自定义). 写在前面 这是一个九宫格控件,本来是很久之前就写好了,现在才开源出来,也是看了很多优秀 ...

  9. Android开发技巧——定制仿微信图片裁剪控件

    拍照--裁剪,或者是选择图片--裁剪,是我们设置头像或上传图片时经常需要的一组操作.上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现. 背景 下面的需求都来自产品. 裁剪图片要像微信那样,拖动 ...

最新文章

  1. opencv感兴趣区域ROI的图像混合操作
  2. OpenCV-Python:实现人脸、人眼、嘴巴识别
  3. Windows快捷键集锦
  4. Android面试题目之二:整形转换为字符串
  5. 【深度学习入门到精通系列】Recurrent和Residual解释
  6. SpringBoot使用Gradle构建war包
  7. java学习路线_java学习路线_我的入坑路
  8. css结构和重叠之选择器的特殊性
  9. PAT1055 集体照 (25 分)【3/6通过】
  10. Spark 性能调优-内存设置-GC设置
  11. leetcode76:最小覆盖字串(滑动窗口)
  12. 转:如何在 LoadRunner 脚本中做关联 (Correlation)
  13. modbus从站模拟软件_作为工控电气人,你知道我们必备的软件有哪些吗?
  14. android 监听本机网络请求_fiddler如何抓取https请求实现fiddler手机抓包-证书安装失败100%解决...
  15. LeetCode 1199. 建造街区的最短时间(优先队列贪心)
  16. 编译内核模块找不到内核头文件解决办法
  17. 排序算法与常见数据结构
  18. Atitit mybatis spring整合。读取spring、yml、文件的mysql url 步骤,读取yml,文件,使用ongl定位到url pwd usr 读取mybatis模板配置,
  19. 网格交易法——震荡行情的必杀技交易策略
  20. 用例图之间的几种关系

热门文章

  1. 做技术知道了哪些事情代表自己成熟了?
  2. SPI、I2C、UART三种串行总线的原理、区别及应用
  3. semihost/ITM机制浅析以及使用JLINK通过ITM调试stm32单片机
  4. linux下usb调试工具,LINUX USB调试
  5. div为空的时候 浮动没有效果_3种CSS清除浮动的方法
  6. python获取返回值_python 调用 shell ,获取返回值和返回信息
  7. python 存储图片 alpha_保存时Matplotlib图形面颜色alpha(背景色、透明度)
  8. 九、一篇文章帮助你读懂CSS属性:vertical-align 垂直对齐
  9. LeetCode 2190. 数组中紧跟 key 之后出现最频繁的数字
  10. 程序员面试金典 - 面试题 04.12. 求和路径(二叉树递归)