如何实现一个循环显示超长图片的控件
*本篇文章已授權微信公眾號 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
總結
以上是生活随笔為你收集整理的如何实现一个循环显示超长图片的控件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android官方开发文档Trainin
- 下一篇: Pytorch数据读取(Dataset,