天天看點

android仿微信動畫效果,Android仿微信朋友圈的檢視圖檔ImageView,縮放,移動,回彈,下滑退出...

點選小圖檔轉到圖檔檢視的頁面在Android開發中很常用到,抱着學習和分享的心态,在這裡寫下自己自定義的一個ImageView,可以實作類似微信朋友圈中檢視圖檔的功能和效果。

android仿微信動畫效果,Android仿微信朋友圈的檢視圖檔ImageView,縮放,移動,回彈,下滑退出...

主要功能需求:

1.自由縮放,有最大和最小的縮放限制

2.若圖檔沒充滿整個ImageView,則縮放過程将圖檔居中

3.根據目前縮放的狀态,輕按兩下放大兩倍或縮小到原來

4.當圖檔尺寸超過ImageView所能展示,圖檔可以移動

5.移動以及縮放過程中,圖檔不可脫離ImageView邊緣

6.縮放到離開ImageView邊緣,自動回彈

7.沒有縮放狀态下,下滑圖檔,背景色逐漸透明,達到一段距離可以退出

8.若設定了退出動畫,那麼将圖檔縮回到原來打開的位置。

實作方式:

我們在這可以通過繼承ImageView,自己寫一個TouchImageView來實作,功能核心的實作涉及到Touch事件,是以需要重寫它的onTouchEvent方法,進行相應處理。宏觀上說,就是點選小圖檔,然後進到新的Activiy,該Activity建立這個TouchImageView,布局代碼和Activity代碼不再給出,可以根據需要自由使用。

對圖檔的移動、縮放、位置等的控制,我們用Matrix這個類來實作,這是一個功能強大的類,這裡簡單的介紹一下:

在這個功能裡,用的比較多的是它的這幾個方法,網上有很多解釋,這裡簡單解釋一下,新手更容易了解。

首先介紹一下Matrix内部的一個3x3矩陣,它用一個float[9]的數組來表示,這裡我們需要常用Matrix.getValues(float[] values)的方法來擷取最新的數組資料,進而知道現在圖檔的放大倍數和坐标位置等。如(values[MTRANS_X],values[MTRANS_Y])表示圖檔左上角的位移情況,可以當做是目前這個點的坐标,(0,0)時沒經過位移,在左上角。

android仿微信動畫效果,Android仿微信朋友圈的檢視圖檔ImageView,縮放,移動,回彈,下滑退出...

Matrix.postScale(float sx, float sy)

Matrix.postScale(float sx, float sy, float px, float py)

這兩個方法是用來進行縮放,第二個方法的後兩個參數是放大的中心點。每調用一次postScale,都是與之前的倍數相乘得到最終的放大倍數,如sx*Values[Matrix.MSCALE_X] ,是以在移動過程每次進入ACTION_MOVE體,先初始化一下matrix,確定是上一次操作後的prematrix,這樣得到的放大倍數才是正确的。這些地方讀代碼會了解更完整一點。另外,需要提一下的是,postScale會産生一定的位移,如果同時你要用postTranslate進行一些特定移動的話,可能需要消除postScale位移的影響,否則最終可能移動的位置不是你想要的,我在代碼中也有展現這點。

Matrix.postTranslate(float dx, float dy)

這個方法進行圖檔的位移,dx和dy是相對現在位置的位移距離,根據Android中的坐标表示,dx正值表示向右移,dy正值表示向下移。

另外給出一張簡單流程圖,希望有用。

android仿微信動畫效果,Android仿微信朋友圈的檢視圖檔ImageView,縮放,移動,回彈,下滑退出...

程式代碼:

下面貼出完整代碼,有點長

public class TouchImageView extends ImageView{

//圖檔的尺寸

private float imgHeight = 0;

private float imgWidth = 0;

private Context context;

//View的尺寸

private int viewHeight = 0;

private int viewWidth = 0;

//圖檔的縮放最大值

private float maxHeight;

private float maxWidth;

private float minHeight;

private float minWidth;

//移動前兩指直接的距離

private double beginDistance;

private boolean isOnePointer = true;

//下滑退出的控制變量

private boolean canQuit = false;

private boolean tryQuit = false;

//圖檔縮放時居中過程産生的位移量

private float tempdy = 0;

private float tempdx = 0;

//兩指的中點坐标,用于設定縮放的中心點

private float xMid,yMid;

//第一根手指按下的初始坐标

private float xDown,yDown;

//目前操作的Matrix對象

private Matrix matrix = new Matrix();

//上一次操作的Matrix對象

private Matrix preMatrix = new Matrix();

private boolean isMovePic = false;

private boolean isZoomPic = false;

//退出時需要縮放到的位置

private int preWidth = 0;

private int preHeight = 0;

private int xLocation = -1;

private int yLocation = -1;

public TouchImageView(Context context) {

super(context);

}

public TouchImageView(Context context, AttributeSet attrs) {

super(context, attrs);

this.context = context;

setBackgroundColor(Color.BLACK);

setScaleType(ScaleType.FIT_CENTER); //在沒有獲得View尺寸來進行initBitmap前,先通過這個進行居中顯示

}

GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {

@Override

public boolean onSingleTapConfirmed(MotionEvent e) {//單擊事件

return super.onSingleTapConfirmed(e);

}

@Override

public boolean onDoubleTap(MotionEvent e) {//輕按兩下事件

doubleClickZoom();

return super.onDoubleTap(e);

}

});

public void setViewSize(int h, int w){

System.out.println("setViewSize");

viewHeight = h;

viewWidth = w;

//獲得View尺寸後初始化圖檔

initBitmap();

}

//初始化圖檔

public void initBitmap(){

System.out.println("initBitmap");

preMatrix.reset(); //必須有,否則會受上一張圖檔的影響

//縮放到寬與控件平齊

float scaleX = (float) viewWidth / imgWidth;

float scaleY = (float) viewHeight / imgHeight;

float defaultScale = scaleX < scaleY ? scaleX : scaleY;

preMatrix.postScale(defaultScale, defaultScale);

//平移到居中

float tranY = (viewHeight - imgHeight*defaultScale)/2;

preMatrix.postTranslate(0, tranY);

//擷取最大最小縮放尺寸

maxHeight = imgHeight * defaultScale * 3;

maxWidth = imgWidth * defaultScale * 3;

minHeight = imgHeight* defaultScale / 2;

minWidth = imgWidth * defaultScale / 2;

setScaleType(ScaleType.MATRIX);

setImageMatrix(preMatrix);

}

@Override

public void setImageBitmap(Bitmap bm){ //先執行這個方法,再執行initBitmap

super.setImageBitmap(bm);

imgWidth = bm.getWidth();

imgHeight = bm.getHeight();

System.out.println("setImageBitmap: imgWidth="+imgWidth);

System.out.println("setImageBitmap: TimgHeightH="+imgHeight);

if(viewHeight!=0 && viewWidth!=0) {

initBitmap();

} else{

viewHeight = getHeight();

viewWidth = getWidth();

if(viewHeight!=0 && viewWidth!=0){

initBitmap();

}

}

}

@Override

public boolean onTouchEvent(MotionEvent event){

gestureDetector.onTouchEvent(event);

switch(event.getAction() & MotionEvent.ACTION_MASK){

case MotionEvent.ACTION_DOWN:

matrix.set(preMatrix); //時序問題,doubleClickZoom方法中設定該項時,前面會通過ACTION_UP将preMatrix設定成matrix,故提前設定該項

xDown = event.getX();

yDown = event.getY();

break;

case MotionEvent.ACTION_POINTER_DOWN:

isOnePointer = false;

if(!isMovePic){ // 第二根手指放下時可能已經進入了移動模式,已經進入移動模式則不激發縮放模式

isZoomPic = true;

}

if(event.getPointerCount() == 2) { // 兩根手指才進行測量與計算距離,避免縮放過程中第三根手指放下激發重新測量

float downX1 = event.getX(0);

float downX2 = event.getX(1);

float downY1 = event.getY(0);

float downY2 = event.getY(1);

float xDistance = Math.abs(downX1-downX2);

float yDistance = Math.abs(downY1-downY2);

xMid = (downX1+downX2)/2;

yMid = (downY1+downY2)/2;

beginDistance = Math.sqrt(xDistance * xDistance + yDistance * yDistance);

}

break;

case MotionEvent.ACTION_MOVE:

if(event.getPointerCount() == 1) {

if(!isZoomPic) {

float moveX = event.getX();

float moveY = event.getY();

if(Math.abs(moveX - xDown)>10 || Math.abs(moveY - yDown)>10){

isZoomPic = false;

isMovePic = true;

}

}

isOnePointer = true;

}

else if(event.getPointerCount() == 2){

if(!isMovePic){

isZoomPic = true;

isMovePic = false;

}

isOnePointer = false;

}

else {

isZoomPic = false;

isMovePic = false;

isOnePointer = false;

}

if(isZoomPic && !isOnePointer) { //雙指縮放圖檔

matrix.set(preMatrix);

float moveX1 = event.getX(0);

float moveX2 = event.getX(1);

float moveY1 = event.getY(0);

float moveY2 = event.getY(1);

float xDistance2 = Math.abs(moveX1-moveX2);

float yDistance2 = Math.abs(moveY1-moveY2);

//移動後兩指間的距離

double moveDistance = Math.sqrt(xDistance2 * xDistance2 + yDistance2 * yDistance2);

float zoomScale = (float) ((float)moveDistance / beginDistance);

matrix.postScale(zoomScale, zoomScale,xMid,yMid);

float[] values = new float[9];

//縮放最大最小的限制

limitZoomSize(values);

//放大或縮小時,若高或寬沒有填充完控件則過程需要居中

placeXCenter(values);

placeYCenter(values);

//檢查圖檔邊緣有沒離開邊界,傳回需要移動的位移量

float dx = checkXBorder(values, 0);

float dy = checkYBorder(values, 0);

matrix.postTranslate(dx, dy);

setImageMatrix(matrix);

}

if(isMovePic && isOnePointer){ //單手指移動圖檔

matrix.set(preMatrix);

float moveX = event.getX();

float moveY = event.getY();

//計算位移量

float dy = moveY - yDown;

float dx = moveX - xDown;

float[] values = new float[9];

quitViewPicture(values,dx,dy);

//檢查圖檔邊緣有沒離開邊界,傳回需要移動的位移量

dx = checkXBorder(values, dx);

dy = checkYBorder(values, dy);

matrix.postTranslate(dx, dy);

setImageMatrix(matrix);

}

break;

case MotionEvent.ACTION_UP:

float[] values = new float[9];

if(tryQuit) {

checkQuit(values);

} else {

//縮小離開螢幕邊緣時,松手自動放大到螢幕邊緣

autoMatchParent(values);

}

preMatrix.set(matrix);

isOnePointer = true;

isMovePic = false;

isZoomPic = false;

//将前面居中過程産生的位移量置0

tempdy = 0;

tempdx = 0;

tryQuit = false;

break;

}

return true;

}

//下滑退出操作,松手時是否達到可以退出的條件(位移量)

public void checkQuit(float[] values) {

matrix.getValues(values);

if(canQuit) {

if(preWidth != 0 && preHeight != 0 && xLocation != -1 && yLocation != -1) {

setBackgroundColor(Color.argb(0, 0, 0, 0));

float toScale;

if(preWidth > preHeight) {

toScale = preWidth / (values[Matrix.MSCALE_X]*imgWidth);

} else {

toScale = preHeight / (values[Matrix.MSCALE_Y]*imgHeight);

}

matrix.getValues(values);

float dx = xLocation - values[Matrix.MTRANS_X];

float dy = yLocation - values[Matrix.MTRANS_Y];

setMyAnimation(dx,dy,toScale,toScale);

}

else {

AcManager.getAcManager().popActivity((Activity)context);

}

}

else {

float scale = getWidth()/(values[Matrix.MSCALE_X] * imgWidth);

matrix.postScale(scale,scale);

placeXCenter(values);

placeYCenter(values);

setImageMatrix(matrix);

}

}

//當圖檔未縮放狀态時,向下滑可以退出圖檔浏覽

public void quitViewPicture(float[] values, float dx, float dy){

matrix.getValues(values);

float beforeZoom = values[Matrix.MTRANS_Y];

if((imgWidth * values[Matrix.MSCALE_X] <= getWidth()) && (imgHeight * values[Matrix.MSCALE_Y] <= getHeight()) ) {

if(dy > 0) {

if(dy>5) { //防止輕按兩下放大時被認為是想退出

tryQuit = true;

}

// 設定背景色透明程度

int alpha = 255- (int) ((255 * dy)/getHeight());

setBackgroundColor(Color.argb(alpha, 0, 0, 0));

//float scale = values[Matrix.MSCALE_X] - dy * values[Matrix.MSCALE_X] / getHeight(); //目标縮放尺寸

float scale = 1 - dy / getHeight(); //需要Post的縮放尺寸

matrix.postScale(scale, scale);

matrix.getValues(values);

float dyZoom = beforeZoom - values[Matrix.MTRANS_Y]; //縮小過程會産生一定回縮的位移

float dxZoom = getWidth()/2 -imgWidth * values[Matrix.MSCALE_X]/2 ;

matrix.postTranslate(dx + dxZoom, dy + dyZoom);

//placeXCenter(values);

if(dy > 200) {

canQuit = true;

} else {

canQuit = false;

}

}

}

}

//輕按兩下放大一倍

public void doubleClickZoom(){

matrix.set(preMatrix);

float[] values = new float[9];

matrix.getValues(values);

float currentWidth = values[Matrix.MSCALE_X] * imgWidth;

if(currentWidth > getWidth()+ 5) {

float scale = getWidth()/currentWidth;

matrix.postScale(scale,scale);

placeXCenter(values);

placeYCenter(values);

setImageMatrix(matrix);

preMatrix.set(matrix);

} else {

matrix.postScale(2.0f,2.0f);

matrix.getValues(values);

float dx = getWidth()/2 - (imgWidth*values[Matrix.MSCALE_X]/2 + values[Matrix.MTRANS_X]);

float dy = getHeight()/2 - (imgHeight*values[Matrix.MSCALE_Y]/2 + values[Matrix.MTRANS_Y]);

matrix.postTranslate(dx, dy);

setImageMatrix(matrix);

preMatrix.set(matrix);

}

}

//縮小離開螢幕邊緣時,松手自動放大到螢幕邊緣

public void autoMatchParent(float[] values) {

matrix.getValues(values);

float currentHeight = values[Matrix.MSCALE_Y] * imgHeight;

float currentWidth = values[Matrix.MSCALE_X] * imgWidth;

if(currentHeight < (getHeight()-5) && currentWidth < (getWidth()-5)) {

float scale = getWidth()/currentWidth;

matrix.postScale(scale,scale);

placeXCenter(values);

placeYCenter(values);

setImageMatrix(matrix);

}

}

//縮放最大最小的限制

public void limitZoomSize(float[] values) {

matrix.getValues(values);

if(values[Matrix.MSCALE_Y] * imgHeight > maxHeight || values[Matrix.MSCALE_X] * imgWidth > maxWidth) {

float scaleX = maxWidth / (imgWidth*values[Matrix.MSCALE_X]);

float scaleY = maxHeight / (imgHeight*values[Matrix.MSCALE_Y]);

matrix.postScale(scaleX, scaleY,xMid,yMid);

} else if(values[Matrix.MSCALE_Y] * imgHeight < minHeight || values[Matrix.MSCALE_X] * imgWidth < minWidth) {

float scaleX = minWidth / (imgWidth*values[Matrix.MSCALE_X]);

float scaleY = minHeight / (imgHeight*values[Matrix.MSCALE_Y]);

matrix.postScale(scaleX, scaleY,xMid,yMid);

}

}

//X方向上縮放過程中若未充滿控件寬度,那麼居中縮放

public void placeXCenter(float[] values){

//獲得最新的values

matrix.getValues(values);

//圖檔橫向能完全顯示時,需要居中

if(imgWidth*values[Matrix.MSCALE_X] < getWidth() + 1) {

System.out.println("placeXCenter:橫向正在居中");

float dx = getWidth()/2 - (imgWidth*values[Matrix.MSCALE_X]/2 + values[Matrix.MTRANS_X]);

tempdx = dx;

matrix.postTranslate(dx, 0);

}

else {

matrix.postTranslate(tempdx, 0); //圖像的橫向向從未充滿螢幕到充滿螢幕過程中,由于居中會産生的一定位移tempdx,需要補上,否則會跳變

}

}

//Y方向上縮放過程中若未充滿控件寬度,那麼居中縮放

public void placeYCenter(float[] values){

//獲得最新的values

matrix.getValues(values);

//圖檔縱向能完全顯示時,需要居中

if(imgHeight*values[Matrix.MSCALE_Y] < getHeight() + 1) {

System.out.println("placeYCenter:縱向正在居中");

float dy = getHeight()/2 - (imgHeight*values[Matrix.MSCALE_Y]/2 + values[Matrix.MTRANS_Y]);

tempdy = dy;

matrix.postTranslate(0, dy);

} else {

matrix.postTranslate(0, tempdy); //圖像的縱向從未充滿螢幕到充滿螢幕過程中,由于居中會産生的一定位移tempdy,需要補上,否則會跳變

}

}

//X方向上的邊緣檢測

public float checkXBorder(float[] values, float dx) {

//獲得最新的values

matrix.getValues(values);

if(imgWidth*values[Matrix.MSCALE_X] < getWidth()){ //圖檔寬度小于控件寬度時,不移動

dx = 0;

}

else if(values[Matrix.MTRANS_X]+dx>0) { //圖檔右移後若離開控件左邊緣,那麼将圖檔移動對齊到左邊緣

dx = -values[Matrix.MTRANS_X];

}

else if(imgWidth*values[Matrix.MSCALE_X]+values[Matrix.MTRANS_X]+ dx< getWidth()){ //圖檔左移後若離開控件右邊緣,那麼将圖檔移動對齊到右邊緣

dx = - imgWidth*values[Matrix.MSCALE_X] + getWidth() - values[Matrix.MTRANS_X];

}

return dx;

}

//Y方向上的邊緣檢測

public float checkYBorder(float[] values, float dy) {

//獲得最新的values

matrix.getValues(values);

if(imgHeight*values[Matrix.MSCALE_Y] < getHeight()){ //圖檔高度小于控件寬度時,不移動

dy = 0;

}

else if(values[Matrix.MTRANS_Y]+dy>0) { //圖檔下移後若離開控件上邊緣,那麼将圖檔移動對齊到上邊緣

dy = -values[Matrix.MTRANS_Y];

}

else if(imgHeight * values[Matrix.MSCALE_Y] + values[Matrix.MTRANS_Y] + dy < getHeight()){ //圖檔上移後若離開控件下邊緣,那麼将圖檔移動對齊到下邊緣

dy = - imgHeight*values[Matrix.MSCALE_Y] + getHeight() - values[Matrix.MTRANS_Y];

}

return dy;

}

public void setQuitAnimation(int x, int y, int height, int width) {

preWidth = width;

preHeight = height;

xLocation = x;

yLocation = y;

}

public void setMyAnimation (float toTranslateX, float toTranslateY, float toScaleX, float toScaleY) {

PropertyValuesHolder translateX = PropertyValuesHolder.ofFloat("translateX", 0.0f,toTranslateX);

PropertyValuesHolder translateY = PropertyValuesHolder.ofFloat("translateY", 0.0f,toTranslateY);

PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f,toScaleX);

PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f,toScaleY);

ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(translateX,translateY,scaleX,scaleY);

animator.addUpdateListener ( new MyAnimatorListener ( getImageMatrix() ) );

animator.setDuration ( 150 );

animator.setInterpolator ( new LinearInterpolator () );

animator.setStartDelay ( 0 );

animator.start ();

animator.addListener(new AnimatorListener(){

@Override

public void onAnimationStart(Animator animation) {

}

@Override

public void onAnimationEnd(Animator animation) {

AcManager.getAcManager().popActivity((Activity)context);

}

@Override

public void onAnimationCancel(Animator animation) {

}

@Override

public void onAnimationRepeat(Animator animation) {

}

});

}

class MyAnimatorListener implements AnimatorUpdateListener {

private Matrix mMatrix;

public MyAnimatorListener(Matrix matrix) {

mMatrix = new Matrix(matrix);

}

@Override

public void onAnimationUpdate(ValueAnimator animation) {

float dx = (float) animation.getAnimatedValue("translateX");

float dy = (float) animation.getAnimatedValue("translateY");

float scaleX = (float) animation.getAnimatedValue("scaleX");

float scaleY = (float) animation.getAnimatedValue("scaleY");

Matrix matrix = new Matrix(mMatrix);

float[] values = new float[9];

matrix.getValues(values);

float beforeX = values[Matrix.MTRANS_X];

float beforeY = values[Matrix.MTRANS_Y];

matrix.postScale(scaleX,scaleY);

matrix.getValues(values);

matrix.postTranslate(beforeX - values[Matrix.MTRANS_X] , beforeY - values[Matrix.MTRANS_Y]);

matrix.postTranslate(dx, dy);

setImageMatrix(matrix);

}

}

public float getCurrentImageHeight(){

float[] values = new float[9];

matrix.getValues(values);

return imgHeight*values[Matrix.MSCALE_Y];

}

public float getCurrentImageWidth(){

float[] values = new float[9];

matrix.getValues(values);

return imgWidth*values[Matrix.MSCALE_X];

}

}

圖檔退出動畫

然後如果要在onCreate裡設定圖檔的話,由于此時是獲得的ImageView的尺寸是0,是以要在Activity的onCreate中用ViewTreeObserver來擷取尺寸:

ViewTreeObserver viewTreeObserver = pic.getViewTreeObserver();

viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

pic.setViewSize(pic.getHeight(),pic.getWidth());

}

});

如果要設定下滑退出,縮回到原來小圖檔位置的動畫,需要在Activity中設定此項:

//xLocation和yLocation是原來的小圖檔相對于螢幕的坐标

//preHeight和preWidth是小圖檔的高和寬

pic.setQuitAnimation(xLocation, yLocation, preHeight, preWidth);

由于傳入的是相對螢幕的坐标,是以TouchImageView的大小也要是整個螢幕,這樣動畫才會跑到正确的位置。在TouchImageView對應的Activity中設定:

getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSCREEN , WindowManager.LayoutParams. FLAG_FULLSCREEN);

另外,需要把TouchImageView對應的Activity的背景色設定為透明,在AndroidManifest.xml檔案中對該Activity設定:

android:theme="@android:style/Theme.Translucent.NoTitleBar"

最後最後,其實代碼中已經有比較多的注釋了,大家可以看看。希望通過寫文章,能加深自己的了解以及發現自己的錯誤,在學習的道路上不斷前行。