如何实现一个循环显示超长图片的控件
*本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨(dú)家發(fā)布
某次被問到如何實現(xiàn)一個滾筒狀的控件,就是可以將一張很長的圖片沿著Y軸無限旋轉(zhuǎn),如下圖所示:
大概就是這個意思,當(dāng)時還不知道圖片可以裁剪,想不出整個流程怎么搞,后來得知Bitmap有裁剪功能,才想到這個功能怎么實現(xiàn),花了一下午時間整了一下有了成果。
這是這張長圖:
然后旋轉(zhuǎn)起來就是這個樣子:
上面這個效果在實際運(yùn)行過程中是非常流暢的,這張圖片是按照每秒幾幀截的,所以看起來一頓一頓的。
先來說說如何實現(xiàn):
第一次:先按照屏幕的寬度截取這張長圖的起始部分。
第二次:以偏移量開始,重復(fù)第一次的行為。
…
最后:當(dāng)這張圖片的結(jié)尾部分不足以支撐整個屏幕的寬度時,先截取這張圖片的末尾部分,繪制。然后再以剩余的寬度截取圖片的頭部部分,繪制。依次進(jìn)行,直至重新回到第一次。
/*** 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;// 用于持有兩張拼接圖片的引用,并釋放原先的圖片資源/*** 循環(huán)滾動標(biāo)志位*/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() {// 獲取需要循環(huán)展示的圖片的高寬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);// 簡單設(shè)置一下控件的寬高,這里的高度以圖片的高度為準(zhǔn)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);// 如果最后的圖片已經(jīng)不足以填充整個屏幕,則截取圖片的頭部以連接上尾部,形成一個閉環(huán)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的延遲發(fā)送產(chǎn)生繪制間隔if (mRunningFlag) {mHandler.sendEmptyMessageDelayed(0, 1);}}/*** 回收臨時圖像*/private void recycleTmpBitmap() {if (mPointerA != null) {mPointerA.recycle();mPointerA = null;}if (mPointerB != null) {mPointerB.recycle();mPointerB = null;}}/*** 恢復(fù)*/public void resume() {mRunningFlag = true;invalidate();}/*** 暫停*/public void pause() {mRunningFlag = false;}/*** 回收清理工作*/@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();pause();recycleTmpBitmap();mSourceBitmap.recycle();} }以上是CylinderImageView的實現(xiàn)代碼。
其中有兩個公開方法:
resume() 用于在Activity的onResume()中調(diào)用,以便恢復(fù)旋轉(zhuǎn)。
pause() 用于在Activiyt的onPause()中調(diào)用,以便暫停旋轉(zhuǎn)。
下面是使用示例:
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();} }因為這個控件內(nèi)部涉及大量的圖片操作,所以大伙一定很關(guān)心內(nèi)存的使用。我為此專門做了內(nèi)存測試,結(jié)果內(nèi)存占用非常小:
這張圖是沒有使用CylinderImageView時應(yīng)用程序所占用的內(nèi)存:17.9MB:
我這里所使用的示例圖片的長寬是1200x353,也就是說它被加載到內(nèi)存中所占用的內(nèi)存大小是1200x353x4/1024/1024=1.61MB.
再加上在屏幕上所顯示的Bitmap所占用的內(nèi)存為:1080x353x4/1024/1024=1.45MB.(這里的1080是我的屏幕寬度,在屏幕上顯示的圖片占了整個屏幕的寬度,所以是1080)
因為內(nèi)存回收并不是實時的,所以在內(nèi)存使用最高峰時,所使用的內(nèi)存=17.9+1.61+1.45x2=22.43.
實際的運(yùn)行占用內(nèi)存為:
與
上面兩張圖片的差距是圖片內(nèi)存回收的差值,但是這里的高峰內(nèi)存值與我們計算的內(nèi)存值有些差距,這是因為除了內(nèi)存之外,我們還在XML布局文件中聲明了控件以及加載控件也占用了一定的內(nèi)存空間。
調(diào)用pause()方法的內(nèi)存狀況:
調(diào)用resume()方法的內(nèi)存狀況:
與
Activity銷毀之后所占用的內(nèi)存:
通過上面一系列圖示說明這個控件將內(nèi)存的消耗控制在了合理的范圍之內(nèi),沒有濫用內(nèi)存。
最后,大功告成,不知道是否明白我說的呢?
相關(guān)Demo演示請參見:https://github.com/sahadev/CylinderImageView
總結(jié)
以上是生活随笔為你收集整理的如何实现一个循环显示超长图片的控件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android官方开发文档Trainin
- 下一篇: Pytorch数据读取(Dataset,