• Android浏览图片,点击放大至全屏效果


     

    最近做一个项目类似于QQ空间,做到照片浏览的功能,对于QQ空间中点击图片放大至全屏,感觉效果很赞,于是也做了个类似的效果。如下。



    我不知道QQ那个是怎么做的,我的思路如下:

    首先,从图片缩略界面跳转到图片详情页面,应该是从一个Activity跳转到另外一个Activity,应该图片详情页面也有很多操作,用View或者Dialog不是很好。所以现在难点就是,如何使得前一个界面的ImageView在另外一个界面做缩放切割动画。

    一般缩略界面的ImageView的是如上图所示的正方形的,并且是CENTER_CROP缩放属性的。CENTER_CROP属性会导致ImageView中显示的Bitmap有被切割达到填充的效果。

    而详情页面的ImageView一般都是FIT_CENTER的缩放属性。所以要保证这个跳转动画的流畅,要做如下的变化:

    1、Bitmap的缩放,因为缩略图和详情图的缩放比例肯定不一样

    2、Bitmap位置的平移,因为缩略图的位置是不确定的,我们要使他平移到中间

    3、Bitmap的切割,因为CENTER_CROP是切割过得,而FIT_CENTER是没有切割的,那么两幅图显示的内容区域是不同的,所以也要显示区域的平滑变换。


    要完成上面的效果,如果单单是指对ImageView做一个动画变换,我觉得是完成不了这个要求的。所以自己重写了ImageView来完成上述的变换。

    直接贴上主要的ImageView

    1. package com.roamer.ui.view;  
    2.   
    3. import android.animation.Animator;  
    4. import android.animation.PropertyValuesHolder;  
    5. import android.animation.ValueAnimator;  
    6. import android.app.Activity;  
    7. import android.content.Context;  
    8. import android.graphics.Bitmap;  
    9. import android.graphics.Canvas;  
    10. import android.graphics.Matrix;  
    11. import android.graphics.Paint;  
    12. import android.graphics.Paint.Style;  
    13. import android.graphics.drawable.BitmapDrawable;  
    14. import android.util.AttributeSet;  
    15. import android.util.Log;  
    16. import android.view.animation.AccelerateDecelerateInterpolator;  
    17. import android.widget.ImageView;  
    18.   
    19. /** 
    20.  * 2d平滑变化的显示图片的ImageView 
    21.  * 仅限于用于:从一个ScaleType==CENTER_CROP的ImageView,切换到另一个ScaleType= 
    22.  * FIT_CENTER的ImageView,或者反之 (当然,得使用同样的图片最好) 
    23.  *  
    24.  * @author Dean Tao 
    25.  *  
    26.  */  
    27. public class SmoothImageView extends ImageView {  
    28.   
    29.     private static final int STATE_NORMAL = 0;  
    30.     private static final int STATE_TRANSFORM_IN = 1;  
    31.     private static final int STATE_TRANSFORM_OUT = 2;  
    32.     private int mOriginalWidth;  
    33.     private int mOriginalHeight;  
    34.     private int mOriginalLocationX;  
    35.     private int mOriginalLocationY;  
    36.     private int mState = STATE_NORMAL;  
    37.     private Matrix mSmoothMatrix;  
    38.     private Bitmap mBitmap;  
    39.     private boolean mTransformStart = false;  
    40.     private Transfrom mTransfrom;  
    41.     private final int mBgColor = 0xFF000000;  
    42.     private int mBgAlpha = 0;  
    43.     private Paint mPaint;  
    44.       
    45.     public SmoothImageView(Context context) {  
    46.         super(context);  
    47.         init();  
    48.     }  
    49.   
    50.     public SmoothImageView(Context context, AttributeSet attrs) {  
    51.         super(context, attrs);  
    52.         init();  
    53.     }  
    54.   
    55.     public SmoothImageView(Context context, AttributeSet attrs, int defStyle) {  
    56.         super(context, attrs, defStyle);  
    57.         init();  
    58.     }  
    59.   
    60.     private void init() {  
    61.         mSmoothMatrix = new Matrix();  
    62.         mPaint=new Paint();  
    63.         mPaint.setColor(mBgColor);  
    64.         mPaint.setStyle(Style.FILL);  
    65. //      setBackgroundColor(mBgColor);  
    66.     }  
    67.   
    68.     public void setOriginalInfo(int width, int height, int locationX, int locationY) {  
    69.         mOriginalWidth = width;  
    70.         mOriginalHeight = height;  
    71.         mOriginalLocationX = locationX;  
    72.         mOriginalLocationY = locationY;  
    73.         // 因为是屏幕坐标,所以要转换为该视图内的坐标,因为我所用的该视图是MATCH_PARENT,所以不用定位该视图的位置,如果不是的话,还需要定位视图的位置,然后计算mOriginalLocationX和mOriginalLocationY  
    74.         mOriginalLocationY = mOriginalLocationY - getStatusBarHeight(getContext());  
    75.     }  
    76.   
    77.     /** 
    78.      * 获取状态栏高度 
    79.      *  
    80.      * @return 
    81.      */  
    82.     public static int getStatusBarHeight(Context context) {  
    83.         Class<?> c = null;  
    84.         Object obj = null;  
    85.         java.lang.reflect.Field field = null;  
    86.         int x = 0;  
    87.         int statusBarHeight = 0;  
    88.         try {  
    89.             c = Class.forName("com.android.internal.R$dimen");  
    90.             obj = c.newInstance();  
    91.             field = c.getField("status_bar_height");  
    92.             x = Integer.parseInt(field.get(obj).toString());  
    93.             statusBarHeight = context.getResources().getDimensionPixelSize(x);  
    94.             return statusBarHeight;  
    95.         } catch (Exception e) {  
    96.             e.printStackTrace();  
    97.         }  
    98.         return statusBarHeight;  
    99.     }  
    100.   
    101.     /** 
    102.      * 用于开始进入的方法。 调用此方前,需已经调用过setOriginalInfo 
    103.      */  
    104.     public void transformIn() {  
    105.         mState = STATE_TRANSFORM_IN;  
    106.         mTransformStart = true;  
    107.         invalidate();  
    108.     }  
    109.   
    110.     /** 
    111.      * 用于开始退出的方法。 调用此方前,需已经调用过setOriginalInfo 
    112.      */  
    113.     public void transformOut() {  
    114.         mState = STATE_TRANSFORM_OUT;  
    115.         mTransformStart = true;  
    116.         invalidate();  
    117.     }  
    118.   
    119.     private class Transfrom {  
    120.         float startScale;// 图片开始的缩放值  
    121.         float endScale;// 图片结束的缩放值  
    122.         float scale;// 属性ValueAnimator计算出来的值  
    123.         LocationSizeF startRect;// 开始的区域  
    124.         LocationSizeF endRect;// 结束的区域  
    125.         LocationSizeF rect;// 属性ValueAnimator计算出来的值  
    126.   
    127.         void initStartIn() {  
    128.             scale = startScale;  
    129.             try {  
    130.                 rect = (LocationSizeF) startRect.clone();  
    131.             } catch (CloneNotSupportedException e) {  
    132.                 e.printStackTrace();  
    133.             }  
    134.         }  
    135.   
    136.         void initStartOut() {  
    137.             scale = endScale;  
    138.             try {  
    139.                 rect = (LocationSizeF) endRect.clone();  
    140.             } catch (CloneNotSupportedException e) {  
    141.                 e.printStackTrace();  
    142.             }  
    143.         }  
    144.           
    145.     }  
    146.   
    147.     /** 
    148.      * 初始化进入的变量信息 
    149.      */  
    150.     private void initTransform() {  
    151.         if (getDrawable() == null) {  
    152.             return;  
    153.         }  
    154.         if (mBitmap == null || mBitmap.isRecycled()) {  
    155.             mBitmap = ((BitmapDrawable) getDrawable()).getBitmap();  
    156.         }  
    157.         //防止mTransfrom重复的做同样的初始化  
    158.         if (mTransfrom != null) {  
    159.             return;  
    160.         }  
    161.         if (getWidth() == 0 || getHeight() == 0) {  
    162.             return;  
    163.         }  
    164.         mTransfrom = new Transfrom();  
    165.   
    166.         /** 下面为缩放的计算 */  
    167.         /* 计算初始的缩放值,初始值因为是CENTR_CROP效果,所以要保证图片的宽和高至少1个能匹配原始的宽和高,另1个大于 */  
    168.         float xSScale = mOriginalWidth / ((float) mBitmap.getWidth());  
    169.         float ySScale = mOriginalHeight / ((float) mBitmap.getHeight());  
    170.         float startScale = xSScale > ySScale ? xSScale : ySScale;  
    171.         mTransfrom.startScale = startScale;  
    172.         /* 计算结束时候的缩放值,结束值因为要达到FIT_CENTER效果,所以要保证图片的宽和高至少1个能匹配原始的宽和高,另1个小于 */  
    173.         float xEScale = getWidth() / ((float) mBitmap.getWidth());  
    174.         float yEScale = getHeight() / ((float) mBitmap.getHeight());  
    175.         float endScale = xEScale < yEScale ? xEScale : yEScale;  
    176.         mTransfrom.endScale = endScale;  
    177.   
    178.         /** 
    179.          * 下面计算Canvas Clip的范围,也就是图片的显示的范围,因为图片是慢慢变大,并且是等比例的,所以这个效果还需要裁减图片显示的区域 
    180.          * ,而显示区域的变化范围是在原始CENTER_CROP效果的范围区域 
    181.          * ,到最终的FIT_CENTER的范围之间的,区域我用LocationSizeF更好计算 
    182.          * ,他就包括左上顶点坐标,和宽高,最后转为Canvas裁减的Rect. 
    183.          */  
    184.         /* 开始区域 */  
    185.         mTransfrom.startRect = new LocationSizeF();  
    186.         mTransfrom.startRect.left = mOriginalLocationX;  
    187.         mTransfrom.startRect.top = mOriginalLocationY;  
    188.         mTransfrom.startRect.width = mOriginalWidth;  
    189.         mTransfrom.startRect.height = mOriginalHeight;  
    190.         /* 结束区域 */  
    191.         mTransfrom.endRect = new LocationSizeF();  
    192.         float bitmapEndWidth = mBitmap.getWidth() * mTransfrom.endScale;// 图片最终的宽度  
    193.         float bitmapEndHeight = mBitmap.getHeight() * mTransfrom.endScale;// 图片最终的宽度  
    194.         mTransfrom.endRect.left = (getWidth() - bitmapEndWidth) / 2;  
    195.         mTransfrom.endRect.top = (getHeight() - bitmapEndHeight) / 2;  
    196.         mTransfrom.endRect.width = bitmapEndWidth;  
    197.         mTransfrom.endRect.height = bitmapEndHeight;  
    198.   
    199.         mTransfrom.rect = new LocationSizeF();  
    200.     }  
    201.   
    202.     private class LocationSizeF implements Cloneable{  
    203.         float left;  
    204.         float top;  
    205.         float width;  
    206.         float height;  
    207.         @Override  
    208.         public String toString() {  
    209.             return "[left:"+left+" top:"+top+" "+width+" height:"+height+"]";  
    210.         }  
    211.           
    212.         @Override  
    213.         public Object clone() throws CloneNotSupportedException {  
    214.             // TODO Auto-generated method stub  
    215.             return super.clone();  
    216.         }  
    217.           
    218.     }  
    219.   
    220.     /* 下面实现了CENTER_CROP的功能 的Matrix,在优化的过程中,已经不用了 */  
    221.     private void getCenterCropMatrix() {  
    222.         if (getDrawable() == null) {  
    223.             return;  
    224.         }  
    225.         if (mBitmap == null || mBitmap.isRecycled()) {  
    226.             mBitmap = ((BitmapDrawable) getDrawable()).getBitmap();  
    227.         }  
    228.         /* 下面实现了CENTER_CROP的功能 */  
    229.         float xScale = mOriginalWidth / ((float) mBitmap.getWidth());  
    230.         float yScale = mOriginalHeight / ((float) mBitmap.getHeight());  
    231.         float scale = xScale > yScale ? xScale : yScale;  
    232.         mSmoothMatrix.reset();  
    233.         mSmoothMatrix.setScale(scale, scale);  
    234.         mSmoothMatrix.postTranslate(-(scale * mBitmap.getWidth() / 2 - mOriginalWidth / 2), -(scale * mBitmap.getHeight() / 2 - mOriginalHeight / 2));  
    235.     }  
    236.   
    237.     private void getBmpMatrix() {  
    238.         if (getDrawable() == null) {  
    239.             return;  
    240.         }  
    241.         if (mTransfrom == null) {  
    242.             return;  
    243.         }  
    244.         if (mBitmap == null || mBitmap.isRecycled()) {  
    245.             mBitmap = ((BitmapDrawable) getDrawable()).getBitmap();  
    246.         }  
    247.         /* 下面实现了CENTER_CROP的功能 */  
    248.         mSmoothMatrix.setScale(mTransfrom.scale, mTransfrom.scale);  
    249.         mSmoothMatrix.postTranslate(-(mTransfrom.scale * mBitmap.getWidth() / 2 - mTransfrom.rect.width / 2),  
    250.                 -(mTransfrom.scale * mBitmap.getHeight() / 2 - mTransfrom.rect.height / 2));  
    251.     }  
    252.   
    253.     @Override  
    254.     protected void onDraw(Canvas canvas) {  
    255.         if (getDrawable() == null) {  
    256.             return; // couldn't resolve the URI  
    257.         }  
    258.   
    259.         if (mState == STATE_TRANSFORM_IN || mState == STATE_TRANSFORM_OUT) {  
    260.             if (mTransformStart) {  
    261.                 initTransform();  
    262.             }  
    263.             if (mTransfrom == null) {  
    264.                 super.onDraw(canvas);  
    265.                 return;  
    266.             }  
    267.   
    268.             if (mTransformStart) {  
    269.                 if (mState == STATE_TRANSFORM_IN) {  
    270.                     mTransfrom.initStartIn();  
    271.                 } else {  
    272.                     mTransfrom.initStartOut();  
    273.                 }  
    274.             }  
    275.   
    276.             if(mTransformStart){  
    277.                 Log.d("Dean", "mTransfrom.startScale:"+mTransfrom.startScale);  
    278.                 Log.d("Dean", "mTransfrom.startScale:"+mTransfrom.endScale);  
    279.                 Log.d("Dean", "mTransfrom.scale:"+mTransfrom.scale);  
    280.                 Log.d("Dean", "mTransfrom.startRect:"+mTransfrom.startRect.toString());  
    281.                 Log.d("Dean", "mTransfrom.endRect:"+mTransfrom.endRect.toString());  
    282.                 Log.d("Dean", "mTransfrom.rect:"+mTransfrom.rect.toString());  
    283.             }  
    284.               
    285.             mPaint.setAlpha(mBgAlpha);  
    286.             canvas.drawPaint(mPaint);  
    287.               
    288.             int saveCount = canvas.getSaveCount();  
    289.             canvas.save();  
    290.             // 先得到图片在此刻的图像Matrix矩阵  
    291.             getBmpMatrix();  
    292.             canvas.translate(mTransfrom.rect.left, mTransfrom.rect.top);  
    293.             canvas.clipRect(0, 0, mTransfrom.rect.width, mTransfrom.rect.height);  
    294.             canvas.concat(mSmoothMatrix);  
    295.             getDrawable().draw(canvas);  
    296.             canvas.restoreToCount(saveCount);  
    297.             if (mTransformStart) {  
    298.                 mTransformStart=false;  
    299.                 startTransform(mState);  
    300.             }   
    301.         } else {  
    302.             //当Transform In变化完成后,把背景改为黑色,使得Activity不透明  
    303.             mPaint.setAlpha(255);  
    304.             canvas.drawPaint(mPaint);  
    305.             super.onDraw(canvas);  
    306.         }  
    307.     }  
    308.   
    309.     private void startTransform(final int state) {  
    310.         if (mTransfrom == null) {  
    311.             return;  
    312.         }  
    313.         ValueAnimator valueAnimator = new ValueAnimator();  
    314.         valueAnimator.setDuration(300);  
    315.         valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());  
    316.         if (state == STATE_TRANSFORM_IN) {  
    317.             PropertyValuesHolder scaleHolder = PropertyValuesHolder.ofFloat("scale", mTransfrom.startScale, mTransfrom.endScale);  
    318.             PropertyValuesHolder leftHolder = PropertyValuesHolder.ofFloat("left", mTransfrom.startRect.left, mTransfrom.endRect.left);  
    319.             PropertyValuesHolder topHolder = PropertyValuesHolder.ofFloat("top", mTransfrom.startRect.top, mTransfrom.endRect.top);  
    320.             PropertyValuesHolder widthHolder = PropertyValuesHolder.ofFloat("width", mTransfrom.startRect.width, mTransfrom.endRect.width);  
    321.             PropertyValuesHolder heightHolder = PropertyValuesHolder.ofFloat("height", mTransfrom.startRect.height, mTransfrom.endRect.height);  
    322.             PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 0, 255);  
    323.             valueAnimator.setValues(scaleHolder, leftHolder, topHolder, widthHolder, heightHolder, alphaHolder);  
    324.         } else {  
    325.             PropertyValuesHolder scaleHolder = PropertyValuesHolder.ofFloat("scale", mTransfrom.endScale, mTransfrom.startScale);  
    326.             PropertyValuesHolder leftHolder = PropertyValuesHolder.ofFloat("left", mTransfrom.endRect.left, mTransfrom.startRect.left);  
    327.             PropertyValuesHolder topHolder = PropertyValuesHolder.ofFloat("top", mTransfrom.endRect.top, mTransfrom.startRect.top);  
    328.             PropertyValuesHolder widthHolder = PropertyValuesHolder.ofFloat("width", mTransfrom.endRect.width, mTransfrom.startRect.width);  
    329.             PropertyValuesHolder heightHolder = PropertyValuesHolder.ofFloat("height", mTransfrom.endRect.height, mTransfrom.startRect.height);  
    330.             PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 255, 0);  
    331.             valueAnimator.setValues(scaleHolder, leftHolder, topHolder, widthHolder, heightHolder, alphaHolder);  
    332.         }  
    333.   
    334.         valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    335.             @Override  
    336.             public synchronized void onAnimationUpdate(ValueAnimator animation) {  
    337.                 mTransfrom.scale = (Float) animation.getAnimatedValue("scale");  
    338.                 mTransfrom.rect.left = (Float) animation.getAnimatedValue("left");  
    339.                 mTransfrom.rect.top = (Float) animation.getAnimatedValue("top");  
    340.                 mTransfrom.rect.width = (Float) animation.getAnimatedValue("width");  
    341.                 mTransfrom.rect.height = (Float) animation.getAnimatedValue("height");  
    342.                 mBgAlpha = (Integer) animation.getAnimatedValue("alpha");  
    343.                 invalidate();  
    344.                 ((Activity)getContext()).getWindow().getDecorView().invalidate();  
    345.             }  
    346.         });  
    347.         valueAnimator.addListener(new ValueAnimator.AnimatorListener() {  
    348.             @Override  
    349.             public void onAnimationStart(Animator animation) {  
    350.   
    351.             }  
    352.   
    353.             @Override  
    354.             public void onAnimationRepeat(Animator animation) {  
    355.   
    356.             }  
    357.   
    358.             @Override  
    359.             public void onAnimationEnd(Animator animation) {  
    360.                 /* 
    361.                  * 如果是进入的话,当然是希望最后停留在center_crop的区域。但是如果是out的话,就不应该是center_crop的位置了 
    362.                  * , 而应该是最后变化的位置,因为当out的时候结束时,不回复视图是Normal,要不然会有一个突然闪动回去的bug 
    363.                  */  
    364.                 // TODO 这个可以根据实际需求来修改  
    365.                 if (state == STATE_TRANSFORM_IN) {  
    366.                     mState = STATE_NORMAL;  
    367.                 }  
    368.                 if (mTransformListener != null) {  
    369.                     mTransformListener.onTransformComplete(state);  
    370.                 }  
    371.             }  
    372.   
    373.             @Override  
    374.             public void onAnimationCancel(Animator animation) {  
    375.   
    376.             }  
    377.         });  
    378.         valueAnimator.start();  
    379.     }  
    380.   
    381.     public void setOnTransformListener(TransformListener listener) {  
    382.         mTransformListener = listener;  
    383.     }  
    384.   
    385.     private TransformListener mTransformListener;  
    386.   
    387.     public static interface TransformListener {  
    388.         /** 
    389.          *  
    390.          * @param mode 
    391.          *            STATE_TRANSFORM_IN 1 ,STATE_TRANSFORM_OUT 2 
    392.          */  
    393.         void onTransformComplete(int mode);// mode 1  
    394.     }  
    395.   
    396. }  


    使用的时候,从前一个Activity传递到详情Activity下面几个主要的信息:

    1. Intent intent = new Intent(MainActivity.this, SpaceImageDetailActivity.class);  
    2.                     intent.putExtra("images", (ArrayList<String>) datas);//非必须  
    3.                     intent.putExtra("position", position);  
    4.                     int[] location = new int[2];  
    5.                     imageView.getLocationOnScreen(location);  
    6.                     intent.putExtra("locationX", location[0]);//必须  
    7.                     intent.putExtra("locationY", location[1]);//必须  
    8.   
    9.                     intent.putExtra("width", imageView.getWidth());//必须  
    10.                     intent.putExtra("height", imageView.getHeight());//必须  
    11.                     startActivity(intent);  
    12.                     overridePendingTransition(0, 0);  

    在详情Activity接受到这些参数,并对SmoothImageView初始化位置信息,然后就可以进行变化了。

    1. mDatas = (ArrayList<String>) getIntent().getSerializableExtra("images");  
    2. mPosition = getIntent().getIntExtra("position", 0);  
    3. mLocationX = getIntent().getIntExtra("locationX", 0);  
    4. mLocationY = getIntent().getIntExtra("locationY", 0);  
    5. mWidth = getIntent().getIntExtra("width", 0);  
    6. mHeight = getIntent().getIntExtra("height", 0);  
    7.   
    8. imageView = new SmoothImageView(this);  
    9. imageView.setOriginalInfo(mWidth, mHeight, mLocationX, mLocationY);  
    10. imageView.transformIn();  
    11. imageView.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));  
    12. imageView.setScaleType(ScaleType.FIT_CENTER);  
    13. setContentView(imageView);  
    14. ImageLoader.getInstance().displayImage(mDatas.get(mPosition), imageView);  



    上面的就已经完成了图片的缩放效果,但是还需要设置下Activity透明的风格,才能使得alpha效果体验出来,用户体验更好。

    对Activity设置如下风格,另外说明,在SmoothImageView中没有定位视图的位置,只是做了对状态栏的处理,所以要设置Activity 为NotitleBar,具体style如下:

    1. <style name="IMTheme.Transparent" >  
    2.        <item name="android:windowBackground">@android:color/transparent</item>  
    3.        <item name="android:windowIsTranslucent">true</item>  
    4.        <item name="android:windowNoTitle">true</item>  
    5.        <item name="android:windowContentOverlay">@null</item>  
    6. lt;/style>  


    Demo下载

  • 相关阅读:
    sed匹配多行并替换其中的内容
    sysbench 安装、使用和测试
    linux inode号已满的解决办法
    Linux双网卡绑定
    es安装
    kibana安装
    filebeat
    Codeforces 464E The Classic Problem (最短路 + 主席树 + hash)
    Codeforces 1137C Museums Tour (强连通分量, DP)
    HDU 4921 Map(状态压缩)
  • 原文地址:https://www.cnblogs.com/xgjblog/p/3849176.html
Copyright © 2020-2023  润新知