利用圖片區域性解析技術,載入高清圖片,拒絕壓縮!!!
本篇部落格主要介紹如何使用BitmapRegionDecoder來載入大型圖片,這樣的好處就是不需要對圖片進行壓縮,不會降低圖片顯示的效果,缺點也很明顯,就是不能一次完全顯示出來,一次只能顯示一幅圖片的部分。
先看一下動態效果圖吧:
主佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id ="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.navigationview.CustomNavigationView
android:id="@+id/cnv"
android:layout_width="match_parent"
android:layout_height ="0dp"
android:layout_weight="1" />
<com.example.navigationview.DecoderView
android:id="@+id/dv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_below="@id/cnv"
android:layout_weight="2" />
</LinearLayout>
導航欄對應的View:
package com.example.navigationview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by zhuyuqiang on 2017/3/15.
*/
public class CustomNavigationView extends View {
private Context mContext;
private int mViewWidth, mViewHeight;
private int mSourceWidth, mSourceHeight;
private int BOUNDS_COLOR = Color.RED;
private int VIEW_DEFAULT_WIDTH = 400;
private int VIEW_DEFAULT_HEIGHT = 400;
private int sample = 1;
private Bitmap mNavigationMap;
private Paint mBitmapPaint, mBoundsPaint;
private int mSourceId;
private int mImageWidth, mImageHeight;
private float density = 1f;
private Point mViewPortPosition = new Point();
private Rect mBoundRect = new Rect();
private Rect mViewPortRect = null;
private int mBoundWidth, mBoundHeight;
private onBoundsChangedListener mListener;
public interface onBoundsChangedListener {
void deliveryViewAndBoundsRect(int width, int height, Rect mBoundsRect);
}
public CustomNavigationView(Context context) {
this(context, null);
}
public CustomNavigationView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomNavigationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public CustomNavigationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Log.i("zyq", "CustomNavigationView:construction");
initBitmapPaint();
mContext = context;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("zyq", "CustomNavigationView:onMeasure");
setMeasuredDimension(getCustomMeasureWidth(widthMeasureSpec), getCustomMeasureHeight(heightMeasureSpec));
initNavigationMap();
calculateBoundRect(mViewPortRect);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mNavigationMap != null) {
Log.i("zyq", "onDraw:mNavigationMap");
canvas.drawBitmap(mNavigationMap, mViewPortPosition.x, mViewPortPosition.y, mBitmapPaint);
drawBounds(canvas);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDetector.onTouchEvent(event);
return true;
}
public void setBoundsRect(Rect boundsRect) {
mViewPortRect = boundsRect;
calculateBoundRect(boundsRect);
}
public void setOnBoundsRectChangeListener(onBoundsChangedListener listener) {
this.mListener = listener;
}
private void calculateBoundRect(Rect mViewPortRect) {
if (mViewWidth > 0 && mViewPortRect != null) {
mBoundRect.left = (mViewPortRect.left * mImageWidth / mSourceWidth);
mBoundRect.right = (mViewPortRect.right * mImageWidth / mSourceWidth);
mBoundRect.top = (mViewPortRect.top * mImageHeight / mSourceHeight);
mBoundRect.bottom = (mViewPortRect.bottom * mImageHeight / mSourceHeight);
mBoundWidth = mBoundRect.width();
mBoundHeight = mBoundRect.height();
Log.i("zyq_bound", "reset rect:left" + mBoundRect.left + " top=" + mBoundRect.top + " right=" + mBoundRect.right + " bottom=" + mBoundRect.bottom + "\n" +
"viewPort:left=" + mViewPortRect.left + " top=" + mViewPortRect.top + " right=" + mViewPortRect.right + " bottom=" + mViewPortRect.bottom);
}
invalidate();
}
public void setDrawableId(int sourceId) {
this.mSourceId = sourceId;
}
private void initNavigationMap() {
density = getResources().getDisplayMetrics().density;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), mSourceId, options);
mSourceWidth = options.outWidth;
mSourceHeight = options.outHeight;
while (mSourceWidth / sample > (mViewWidth / density)
|| mSourceHeight / sample > (mViewHeight / density)) {
sample *= 2;
}
Log.i("zyq", "sample =" + sample);
options.inJustDecodeBounds = false;
options.inSampleSize = sample;
mNavigationMap = BitmapFactory.decodeResource(getResources(), mSourceId, options);
mImageWidth = mNavigationMap.getWidth();
mImageHeight = mNavigationMap.getHeight();
mViewPortPosition.x = getPaddingLeft() + (mViewWidth - mImageWidth) / 2;
mViewPortPosition.y = getPaddingTop() + (mViewHeight - mImageHeight) / 2;
Log.i("zyq", "mViewWidth=" + mViewWidth + " mViewHeight=" + mViewHeight + " mImageWidth=" + mImageWidth + " mImageHeight=" + mImageHeight);
}
private void initBitmapPaint() {
mBitmapPaint = new Paint();
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setDither(true);
mBitmapPaint.setFilterBitmap(true);
mBoundsPaint = new Paint();
mBoundsPaint.setAntiAlias(true);
mBoundsPaint.setDither(true);
mBoundsPaint.setColor(BOUNDS_COLOR);
mBoundsPaint.setStrokeWidth(2.0f);
mBoundsPaint.setStyle(Paint.Style.STROKE);
}
private int getCustomMeasureWidth(int measureWidth) {
int mode = MeasureSpec.getMode(measureWidth);
int size = MeasureSpec.getSize(measureWidth);
Log.i("zyq", "mode=" + mode + " EXACTLY=" + MeasureSpec.EXACTLY + " UNSPECIFIED=" + MeasureSpec.UNSPECIFIED + " AT_MOST=" + MeasureSpec.AT_MOST);
if (mode == MeasureSpec.EXACTLY) {
mViewWidth = size - getPaddingLeft() - getPaddingRight();
return size;
} else {
mViewWidth = VIEW_DEFAULT_WIDTH;
return VIEW_DEFAULT_WIDTH + getPaddingRight() + getPaddingLeft();
}
}
private int getCustomMeasureHeight(int measureHeight) {
int mode = MeasureSpec.getMode(measureHeight);
int size = MeasureSpec.getSize(measureHeight);
Log.i("zyq", "mode=" + mode + " EXACTLY=" + MeasureSpec.EXACTLY + " UNSPECIFIED=" + MeasureSpec.UNSPECIFIED + " AT_MOST=" + MeasureSpec.AT_MOST);
if (mode == MeasureSpec.EXACTLY) {
mViewHeight = size - getPaddingTop() - getPaddingBottom();
return size;
} else {
mViewHeight = VIEW_DEFAULT_HEIGHT;
return VIEW_DEFAULT_HEIGHT + getPaddingTop() + getPaddingBottom();
}
}
private void drawBounds(Canvas canvas) {
canvas.save();
if (mBoundRect != null) {
Log.i("zyq_bound", "drawBounds");
// canvas.drawRect(mBoundRect,mBoundsPaint);
canvas.drawRect(mBoundRect.left + mViewPortPosition.x, mBoundRect.top + mViewPortPosition.y, mBoundRect.right + mViewPortPosition.x,
mBoundRect.bottom + mViewPortPosition.y, mBoundsPaint);
}
canvas.restore();
}
private GestureDetector mDetector = new GestureDetector(mContext, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
updateBoundRect(-distanceX, -distanceY);
// postInvalidateDelayed(50);
invalidate();
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
private void updateBoundRect(float width, float height) {
Log.i("zyq", "width=" + width + " height=" + height);
mBoundRect.left = mBoundRect.left + (int) width;
mBoundRect.right = mBoundRect.right + (int) width;
mBoundRect.top = mBoundRect.top + (int) height;
mBoundRect.bottom = mBoundRect.bottom + (int) height;
if (mBoundRect.left < 0) {
mBoundRect.left = 0;
mBoundRect.right = mBoundWidth;
}
if (mBoundRect.top < 0) {
mBoundRect.top = 0;
mBoundRect.bottom = mBoundHeight;
}
if (mBoundRect.right > (mImageWidth)) {
mBoundRect.right = (mImageWidth);
mBoundRect.left = (mImageWidth) - mBoundWidth;
}
if (mBoundRect.bottom > (mImageHeight)) {
mBoundRect.bottom = (mImageHeight);
mBoundRect.top = (mImageHeight) - mBoundHeight;
}
if (mListener != null) {
mListener.deliveryViewAndBoundsRect(mImageWidth, mImageHeight, mBoundRect);
}
Log.i("zyq_bound", "reset rect:left" + mBoundRect.left + " top=" + mBoundRect.top + " right=" + mBoundRect.right + " bottom=" + mBoundRect.bottom + "\n" +
"mViewPortPosition.y+mImageHeight=" + (mViewPortPosition.y + mImageHeight) + " mViewPortPosition.x+mImageWidth" + (mViewPortPosition.x + mImageWidth));
}
}
圖片顯示View:
package com.example.navigationview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by zhuyuqiang on 2017/3/14.
*/
public class DecoderView extends View {
private Context mContext;
private Paint mBitmapPaint, mBoundPaint;
private int DEFAULT_BOUND_COLOR = Color.RED;
private final int DEFAULT_WIDTH = 600;
private final int DEFAULT_HEIGHT = 600;
private Rect mViewPort = new Rect();
private int mOriginWidth, mOriginHeight;
private BitmapFactory.Options mDecoderOption = new BitmapFactory.Options();
private InputStream mSourceInputStream = null;
private String mSourcePath;
private float mDownX, mDownY;
private BitmapRegionDecoder mDecoder = null;
private int mCurrentWidth, mCurrentHeight;
private onViewPortPositionChangeListener mListener;
public interface onViewPortPositionChangeListener {
void getViewPortRect(Rect mViewPortRect);
}
public DecoderView(Context context) {
this(context, null);
}
public DecoderView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DecoderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DecoderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
initPaints();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getCustomMeasuteWidth(widthMeasureSpec), getCustomMeasuteHeight(heightMeasureSpec));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawPartImage(canvas);
Log.i("zyq", "width =" + getWidth() + " height=" + getHeight());
}
private void drawPartImage(Canvas canvas) {
Log.i("zyq", "drawPartImage = " + (mSourceInputStream != null));
if (mSourceInputStream != null) {
try {
Log.i("zyq", "mSourceInputStream != null");
if (mDecoder == null) {
mDecoder = BitmapRegionDecoder.newInstance(mSourceInputStream, true);
}
} catch (IOException e) {
Log.i("zyq", "e=" + e.toString());
}
}
if (mSourcePath != null) {
try {
Log.i("zyq", "mSourceInputStream != null");
if (mDecoder == null) {
mDecoder = BitmapRegionDecoder.newInstance(mSourceInputStream, true);
}
} catch (IOException e) {
Log.i("zyq", "e=" + e.toString());
}
}
if (mDecoder != null) {
Bitmap b = mDecoder.decodeRegion(mViewPort, mDecoderOption);
canvas.drawBitmap(b, getPaddingLeft(), getPaddingTop(), mBitmapPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// Log.i("zyq", "MotionEvent.ACTION_DOWN: mDownX="+mDownX+" mDownY="+mDownY);
// mDownX = event.getX();
// mDownY = event.getY();
//// break;
// case MotionEvent.ACTION_MOVE:
// float mMoveX = event.getX();
// float mMoveY = event.getY();
// Log.i("zyq", "MotionEvent.ACTION_MOVE mMoveX="+mMoveX+" mMoveY="+mMoveY);
// Log.i("zyq", "mMoveX - mDownX="+(mMoveX - mDownX)+" mMoveY - mDownY="+(mMoveY - mDownY));
// updateViewPort(-(mMoveX - mDownX), -(mMoveY - mDownY));
// invalidate();
//// break;
// case MotionEvent.ACTION_UP:
// Log.i("zyq", "MotionEvent.ACTION_UP");
//// break;
// }
mDetector.onTouchEvent(event);
return true;
}
private void updateViewPort(float width, float height) {
// Log.i("zyq", "width=" + width + " height=" + height);
mViewPort.left = mViewPort.left + (int) width;
mViewPort.right = mViewPort.right + (int) width;
mViewPort.top = mViewPort.top + (int) height;
mViewPort.bottom = mViewPort.bottom + (int) height;
if (mViewPort.left < 0) {
mViewPort.left = 0;
mViewPort.right = mCurrentWidth;
}
if (mViewPort.top < 0) {
mViewPort.top = 0;
mViewPort.bottom = mCurrentHeight;
}
if (mViewPort.right > mOriginWidth) {
mViewPort.right = mOriginWidth;
mViewPort.left = mOriginWidth - mCurrentWidth;
}
if (mViewPort.bottom > mOriginHeight) {
mViewPort.bottom = mOriginHeight;
mViewPort.top = mOriginHeight - mCurrentHeight;
}
if (mListener != null) {
mListener.getViewPortRect(mViewPort);
}
}
public void setViewPortChangeListener(onViewPortPositionChangeListener listener) {
this.mListener = listener;
}
public void setImageResource(String filePath) {
if (mSourceInputStream != null) {
throw new SecurityException("已經設定過圖片資源");
}
mDecoderOption.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, mDecoderOption);
mOriginHeight = mDecoderOption.outHeight;
mOriginWidth = mDecoderOption.outWidth;
Log.i("qqq","width = "+mOriginWidth+" height = "+mOriginHeight);
mSourcePath = filePath;
mDecoderOption.inJustDecodeBounds = false;
mDecoderOption.inPreferredConfig = Bitmap.Config.ARGB_8888;
invalidate();
}
public void setImageResource(InputStream is) {
if (mSourcePath != null) {
throw new SecurityException("已經設定過圖片資源");
}
mDecoderOption.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mDecoderOption);
mOriginHeight = mDecoderOption.outHeight;
mOriginWidth = mDecoderOption.outWidth;
Log.i("qqq","width = "+mOriginWidth+" height = "+mOriginHeight);
mSourceInputStream = is;
mDecoderOption.inJustDecodeBounds = false;
mDecoderOption.inPreferredConfig = Bitmap.Config.ARGB_8888;
Log.i("zyq", "set Image source");
invalidate();
}
public Rect getViewPortRect() {
return mViewPort;
}
public void setViewPortRectPosition(float x, float y) {
int positionX = (int) (x * mOriginWidth);
int positionY = (int) (y * mOriginHeight);
int tran_X = positionX - mViewPort.left;
int tran_y = positionY - mViewPort.top;
updateViewPort(tran_X, tran_y);
invalidate();
}
private void initPaints() {
Log.i("zyq", "init Paints");
mBitmapPaint = new Paint();
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setDither(true);
mBitmapPaint.setFilterBitmap(true);
mBoundPaint = new Paint();
mBoundPaint.setAntiAlias(true);
mBoundPaint.setDither(true);
mBoundPaint.setColor(DEFAULT_BOUND_COLOR);
}
private int getCustomMeasuteWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
mViewPort.left = getPaddingLeft();
mViewPort.right = size - getPaddingRight();
mCurrentWidth = size - getPaddingRight() - getPaddingLeft();
Log.i("zyq", "getCustomMeasuteHeight: MeasureSpec.EXACTLY");
return size;
} else {
Log.i("zyq", "getCustomMeasuteHeight: MeasureSpec.*");
mViewPort.left = getPaddingLeft();
mViewPort.right = DEFAULT_WIDTH + getPaddingLeft();
return DEFAULT_WIDTH + getPaddingRight() + getPaddingRight();
}
}
private int getCustomMeasuteHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
Log.i("zyq", "getCustomMeasuteHeight: MeasureSpec.EXACTLY");
mViewPort.top = getPaddingTop();
mViewPort.bottom = size - getPaddingBottom();
mCurrentHeight = size - getPaddingBottom() - getPaddingTop();
return size;
} else {
Log.i("zyq", "getCustomMeasuteHeight: MeasureSpec.*");
mViewPort.top = getPaddingTop();
mViewPort.bottom = DEFAULT_HEIGHT + getPaddingTop();
return DEFAULT_HEIGHT + getPaddingTop() + getPaddingBottom();
}
}
private GestureDetector mDetector = new GestureDetector(mContext, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
updateViewPort(distanceX, distanceY);
// postInvalidateDelayed(50);
invalidate();
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
}
activity檔案:
package com.example.navigationview;
import android.graphics.Rect;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.io.IOException;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
private DecoderView dv;
private CustomNavigationView cnv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dv = (DecoderView) findViewById(R.id.dv);
cnv = (CustomNavigationView) findViewById(R.id.cnv);
InputStream is = null;
try {
is = getAssets().open("lol.jpg");
Log.i("zyq", "(is != null) =" + (is != null));
} catch (IOException e) {
Log.i("zyq", "exception = " + e.toString());
e.printStackTrace();
}
dv.setImageResource(is);
dv.setViewPortChangeListener(new DecoderView.onViewPortPositionChangeListener() {
@Override
public void getViewPortRect(Rect mViewPortRect) {
cnv.setBoundsRect(mViewPortRect);
}
});
cnv.setDrawableId(R.drawable.lol);
cnv.setOnBoundsRectChangeListener(new CustomNavigationView.onBoundsChangedListener() {
@Override
public void deliveryViewAndBoundsRect(int width, int height, Rect mBoundsRect) {
dv.setViewPortRectPosition(mBoundsRect.left / (float) width, mBoundsRect.top / (float) height);
}
});
}
@Override
protected void onResume() {
super.onResume();
cnv.setBoundsRect(dv.getViewPortRect());
}
}
有幾個知識點需要稍微提及一下:
1、一般大的檔案資源都在存放在assets目錄下的,在AndroidStudio中新建資源目錄,需要注意目錄的層級,如果目錄層級不對,可能出現無法載入資源情況!!
目錄所在層級如下圖所示:
2、在本Demo中可以通過拉動導航View中的紅框或者直接滑動圖片顯示View都可以更新顯示圖片,這一點可能在上面的效果上看的不是很明顯,有興趣的朋友可以自行下載的demo試一下!!!
3、BitmapRegionDecoder物件可以通過newInstance方法獲取,其中第一個引數為圖片資源來源!!至於第二個引數直接設定true就可以了!!其中圖片的資源可以設定為輸入流、路徑等,詳細的還是看一下原始碼吧,在這裡就不解釋了!!
4、導航View的圖片載入需要使用壓縮技術,不然肯定會拋異常的,解析圖片需要使用BitmapFactory,使用起來也很簡單,在這裡就不做更多的介紹了!!!
5、由於可以更過導航欄中的View和圖片顯示的View更新BitmapRegionDecoder解析的區域,需要對兩個view的顯示進行同步,具體的可以看一下程式碼,使用過使用回撥函式實現的,實現過程比較簡單,不詳細介紹來了!!!
6、區域性解析的方法decodeRegion,第一個引數為一個rect引數,代表的是解析的區域,更新的時候改變的就是這個rect的四個頂點的值,詳細的還是看一下程式碼吧,主要還是思路,真正實現起來其實沒有什麼難度的!!!!
好了,關於圖片的區域性解析就介紹到這裡,謝謝大家的關注!!!!
這是我的微信公眾號,如果可以的話,希望您可以幫忙關注一下,這將是對我最大的鼓勵了,謝謝!!
程式碼地址: