Imageview自適應顯示圖片(不管任何圖片任何尺寸都自適應View大小顯示)
主要使用了一下知識點:
Matrix:矩陣變換(縮放,平移)
ScaleGestureDetector:縮放手勢檢測
GestureDetector:手勢檢測
- ImageView 縮放功能
縮放實現重寫:
//如果返回true,則會重置detector對放大比例的計算。默認爲1.0
//如果返回false,則持續計算放大比例
public boolean onScale(ScaleGestureDetector detector);
實現步驟:
- 獲取Image本身的縮放比例
//因爲是等比縮放,所以X軸的縮放等於y軸縮放
private float getScale(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MSCALE_X];
}
- 獲取當前用戶操作的縮放比例的增量
float scaleFactor = detector.getScaleFactor();
scaleFactor = scaleFactor - 1.0f;
- 使用Matrix進行縮放
float now_scale = getScale();
//當前放大倍數+當次放大的比例,放大倍數不能爲負數,負數的話則圖片上下顛倒了
now_scale = now_scale + (scaleFactor - 1.0f);
now_scale = Math.abs(now_scale);
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
- ImageView 平移功能
平移重寫:
public boolean onTouch(View v, MotionEvent event);
實現步驟:
- OnTouch 處理ACTION_MOVE事件,計算Move的偏移量
float dx = event.getX() - mLastX;
float dy = event.getY() - mLastY;
//計算完之後更新最後的X,Y位置
mLastX = event.getX();
mLastY = event.getY();
- 獲取Img已經發生的平移量
private float getTranclateX(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MTRANS_X];
}
private float getTranclateY(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MTRANS_Y];
}
- 使用Matrix進行平移
Matrix matrix = new Matrix();
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
- 平移+縮放綜合處理
上述只是單獨的實現了縮放或者平移某一功能,setImageMatrix的Matrix是New的新對象會重置Scale或Translate因子。但是圖片縮放和平移可能是同步進行的,所以我們需要同時處理Scale和Translate。
例如:圖片目前平移量是(-150,-200),放大比例是(2.0,2.0);如果我們將圖片平移到(-300,-400),那麼圖片的放大比例依然是(2.0,2.0)。如果我們將圖片放大到(3.0,3.0),因爲圖片放大了,所以尺寸發生變化導致平移量也將發生變化,不在保持爲(-300,-400)。具體偏移量的更新算法,可以參考Matrix 變換原理。
所以平移的時候我們需要保持縮放因子
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
縮放的時候需要更新平移量爲縮放之後的平移量
//先進行縮放
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
//縮放成功之後,獲取最新平移量,更新平移量
matrix = new Matrix();
matrix.postScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(),getTranclateY());
setImageMatrix(matrix);
此時就可以對圖片進行正常的縮放和平移了。
爲什麼我們不使用getImageMatrix來獲取image的Matrix,然後在通過Matrix來進行變換?
因爲矩陣變換的算法很複雜,如果操作不當,那麼結果就和我們預期的有差別了。所以爲了簡單計算,控制變量因子,每次都重置Matrix,保證Matrix是我們想象中的模樣。
- 實現圖片的Fling功能
基本監聽GestureDetector的onFling中的垂直方向投遞速度,然後在定時器裏面進行變速移動
當前實現最基本的算法。
1.投擲之後再1s中內完成垂直平移。
2.因爲投擲之後是變速的先快後慢,所以打算在1s中完成50次的變速移動
3.變速算法使用垂直速度(velocityY)/5000獲得單步移動量。然後50次的移動中,第一次移動50個單位,第二次移動49個單位,逐次遞減,直至最後一次移動一個單位,完成Fling過程。
4.如果在移動的過程中用戶點擊了屏幕,此時應該停止移動。
(以上算法僅僅爲最基本的模擬算法,用戶可以實現自己的更完善的算法)
mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, final float velocityY) {
Log.i(TAG, "onFling: y0:"+e1.getRawY()+",y1:"+e2.getRawY()+",velocityY:"+velocityY);
//投擲距離過短或者速度很慢不作爲Fling處理
if( Math.abs(e1.getY() - e2.getY()) < 30 || Math.abs(velocityY) < 5000)
return false;
//啓動定時器20ms移動一次
try {
mTimer = new Timer();
mTimerCount = 0;
mTimer.schedule(new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
message.arg1 = (int) velocityY;
mHandler.sendMessage(message);
}
},0,20);
}
catch (Exception e){
e.printStackTrace();
}
return super.onFling(e1, e2, velocityX, velocityY);
}
});
//處理定時器
mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1: {
Log.i(TAG, "handleMessage: arg1:"+msg.arg1);
float fStep = Math.max(1,Math.abs(msg.arg1)/5000.0f);
float fOffset = fStep * (MAX_COUNT - mTimerCount);
if(msg.arg1 < 0)
fOffset = -fOffset;
++mTimerCount;
//如果滑動結束
if(scrollByY(fOffset) ||mTimerCount >= MAX_COUNT){
mTimer.cancel();
mTimer = null;
}
break;
}
}
super.handleMessage(msg);
}
};
private boolean scrollByY(float dOffset){
float dy = getTranclateY();
dy += dOffset;
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(), dy);
setImageMatrix(matrix);
return false;
}
- 優化
1.圖片如果已經可以完全展示在視圖內,則應該不允許在縮小,而且上下居中顯示
Drawable d = getDrawable();
if (null == d) return;
//計算最小的縮放比例
mMinScale = getWidth()/d.getIntrinsicWidth();
ow_scale = now_scale + (scaleFactor - 1.0f);
now_scale = Math.abs(now_scale);
now_scale = Math.max(mMinScale,now_scale);
if(now_scale < SCALE_MAX ){
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
//縮放之後TranslX和TranslateY會發生變化,如果縮放之後圖片小於視圖則居中顯示
RectF rtMatrixf = getMatrixRectF();
//檢查上下左右邊界
float dx =Math.min(0, getTranclateX()),dy = Math.min(0,getTranclateY());
//如果圖片小於視圖則左右居中顯示
if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
}
//製圖片右邊界顯示在視圖內,導致白邊
else if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
}
//如果圖片小於視圖則垂直居中顯示
if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
}
//控制圖片下邊界顯示在視圖內,導致白邊
else if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
}
matrix = new Matrix();
matrix.postScale(getScale(),getScale());
matrix.postTranslate(dx,dy);
setImageMatrix(matrix);
}
2.圖片移動的過程中,上下左右邊框不應該出現在視圖範圍之內,造成白邊
case MotionEvent.ACTION_MOVE:{
Drawable drawable = getDrawable();
if(drawable == null)
return true;
RectF rtMatrixf = getMatrixRectF();
//檢查左右邊界
float dx = 0;
//如果圖片小於視圖則左右居中顯示
if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
}
else if(rtMatrixf.right - rtMatrixf.left > getWidth()){
//控制圖片左邊界顯示在視圖內,導致白邊,dx只允許 < 0
dx = getTranclateX() + (event.getX() - mLastX);
dx = Math.min(0,dx);
//製圖片右邊界顯示在視圖內,導致白邊
if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
}
}
//檢查上下邊界
float dy = 0;
//如果圖片小於視圖則垂直居中顯示
if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
}
else if(rtMatrixf.bottom - rtMatrixf.top > getHeight()){
//控制圖片上邊界顯示在視圖內,導致白邊,dy只允許 < 0
dy = getTranclateY() + (event.getY() - mLastY);
dy = Math.min(0,dy);
//控制圖片下邊界顯示在視圖內,導致白邊
if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
}
}
mLastX = event.getX();
mLastY = event.getY();
Log.i(TAG, "ACTION_MOVE,dx: "+dx + ",dy:"+dy);
//計算邊界,
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
break;
}
- Fling的時候,如果已經到底或者到頂部則應該結束
private boolean scrollByY(float dOffset){
boolean bEnd = false;
RectF rtMatrixf = getMatrixRectF();
//檢查上下邊界
float dy = getTranclateY();
dy += dOffset;
if(dy > 0) {
dy = 0;
bEnd = true;
}
if(rtMatrixf.bottom - rtMatrixf.top + dy < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
bEnd = true;
}
//計算邊界,
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(), dy);
setImageMatrix(matrix);
return bEnd;
}
Fling:
縮放和拖拽
最終源碼:
public class ZoomImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener,
View.OnTouchListener {
private static final String TAG = ZoomImageView.class.getName();
private ScaleGestureDetector mScaleGestureDetector;
private GestureDetector mGestureDetector;
private static final float SCALE_MAX = 4.0f;
private final float[] matrixValues = new float[9];
//最終點擊的位置
private float mLastX = 0,mLastY = 0;
//定時器滑動,滑動1s中,即50次
private Timer mTimer = null;
private final int MAX_COUNT = 50;
int mTimerCount = 0;
private Handler mHandler;
public ZoomImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, final float velocityY) {
Log.i(TAG, "onFling: y0:"+e1.getRawY()+",y1:"+e2.getRawY()+",velocityY:"+velocityY);
//投擲距離過短或者速度很慢不作爲Fling處理
if( Math.abs(e1.getY() - e2.getY()) < 30 || Math.abs(velocityY) < 5000)
return false;
//只有當前顯示不下才可以進行Fling
RectF rtF = getMatrixRectF();
if(rtF.bottom - rtF.top > getHeight())
{
try {
mTimer = new Timer();
mTimerCount = 0;
mTimer.schedule(new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
message.arg1 = (int) velocityY;
mHandler.sendMessage(message);
}
},0,20);
}
catch (Exception e){
e.printStackTrace();
}
}
return super.onFling(e1, e2, velocityX, velocityY);
}
});
this.setOnTouchListener(this);
mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1: {
Log.i(TAG, "handleMessage: arg1:"+msg.arg1);
float fStep = Math.max(1,Math.abs(msg.arg1)/5000.0f);
float fOffset = fStep * (MAX_COUNT - mTimerCount);
if(msg.arg1 < 0)
fOffset = -fOffset;
++mTimerCount;
//如果滑動結束
if(scrollByY(fOffset) ||mTimerCount >= MAX_COUNT){
mTimer.cancel();
mTimer = null;
}
break;
}
}
super.handleMessage(msg);
}
};
}
//返回true會重新計算getScale的返回值,默認爲1
public boolean onScale(ScaleGestureDetector detector){
float now_scale = getScale();
float scaleFactor = detector.getScaleFactor();
Drawable d = getDrawable();
if( d == null)
return true;
Log.i(TAG, "Scale: "+scaleFactor + ",Scale:"+now_scale + ",TranslateX:"+getTranclateX()+",TranslateY:"+getTranclateY()+
",width:"+getWidth()+",height:"+getHeight());
//當前放大倍數+當次放大的比例,放大倍數不能爲負數,負數的話則圖片上下顛倒了
float fMinScale = (getWidth()*1.0f)/d.getIntrinsicWidth();
now_scale = now_scale + (scaleFactor - 1.0f);
now_scale = Math.abs(now_scale);
now_scale = Math.max(fMinScale,now_scale);
if(now_scale < SCALE_MAX ){
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
//縮放之後TranslX和TranslateY會發生變化,如果縮放之後圖片小於視圖則居中顯示
RectF rtMatrixf = getMatrixRectF();
//檢查上下左右邊界
float dx =Math.min(0, getTranclateX()),dy = Math.min(0,getTranclateY());
//如果圖片小於視圖則左右居中顯示
if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
}
//製圖片右邊界顯示在視圖內,導致白邊
else if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
}
//如果圖片小於視圖則垂直居中顯示
if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
}
//控制圖片下邊界顯示在視圖內,導致白邊
else if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
}
matrix = new Matrix();
matrix.postScale(getScale(),getScale());
matrix.postTranslate(dx,dy);
setImageMatrix(matrix);
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector){
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector){
}
@Override
public boolean onTouch(View v, MotionEvent event)
{
mGestureDetector.onTouchEvent(event);
mScaleGestureDetector.onTouchEvent(event);
int nTouchCount = event.getPointerCount();
if(nTouchCount > 1)
return true;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
mLastX = event.getX();
mLastY = event.getY();
if(mTimer != null)
mTimer.cancel();
mTimer = null;
break;
}
case MotionEvent.ACTION_MOVE:{
Drawable drawable = getDrawable();
if(drawable == null)
return true;
RectF rtMatrixf = getMatrixRectF();
//檢查左右邊界
float dx = 0;
//如果圖片小於視圖則左右居中顯示
if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
}
else if(rtMatrixf.right - rtMatrixf.left > getWidth()){
//控制圖片左邊界顯示在視圖內,導致白邊,dx只允許 < 0
dx = getTranclateX() + (event.getX() - mLastX);
dx = Math.min(0,dx);
//製圖片右邊界顯示在視圖內,導致白邊
if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
}
}
//檢查上下邊界
float dy = 0;
//如果圖片小於視圖則垂直居中顯示
if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
}
else if(rtMatrixf.bottom - rtMatrixf.top > getHeight()){
//控制圖片上邊界顯示在視圖內,導致白邊,dy只允許 < 0
dy = getTranclateY() + (event.getY() - mLastY);
dy = Math.min(0,dy);
//控制圖片下邊界顯示在視圖內,導致白邊
if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
}
}
mLastX = event.getX();
mLastY = event.getY();
Log.i(TAG, "ACTION_MOVE,dx: "+dx + ",dy:"+dy);
//計算邊界,
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
break;
}
case MotionEvent.ACTION_UP:{
mLastX = event.getX();
mLastY = event.getY();
}
}
return true;
}
private float getScale(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MSCALE_X];
}
private float getTranclateX(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MTRANS_X];
}
private float getTranclateY(){
Matrix matrix = getImageMatrix();
matrix.getValues(matrixValues);
return matrixValues[Matrix.MTRANS_Y];
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(mTimer != null)
mTimer.cancel();
mTimer = null;
}
//獲取變換之後的圖片尺寸
private RectF getMatrixRectF()
{
Matrix matrix = getImageMatrix();
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d)
{
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect);
}
return rect;
}
private boolean scrollByY(float dOffset){
boolean bEnd = false;
RectF rtMatrixf = getMatrixRectF();
//檢查上下邊界
float dy = getTranclateY();
dy += dOffset;
if(dy > 0) {
dy = 0;
bEnd = true;
}
if(rtMatrixf.bottom - rtMatrixf.top + dy < getHeight()){
dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
bEnd = true;
}
//計算邊界,
Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(), dy);
setImageMatrix(matrix);
return bEnd;
}
}