[原创]实现android知乎、一览等的开场动画图片放大效果
代码下载地址:
https://github.com/Carbs0126/AutoZoomInImageView
知乎等app的开场动画为:一张图片被显示到屏幕的正中央,并充满整个屏幕,过一小段时间后,开始慢慢方法,且图片的正中央始终处于屏幕的正中央,也就是“镜头缓慢放大”的效果
难点1.android手机屏幕碎片化。由于是全屏显示,因此一张图片需要放到不同大小的ImageView中,且图片中央需要放到ImageView中央;
难点2.放大时需要保证图片中央与ImageView中央处于一点;
难点3.实现缓慢放大效果(这个可以利用ValueAnimator实现)
实现原理是:采用调整ImageView的matrix的方式来实现此效果。
我将其实现过程拆分为两部:
1.调整图片,使图片位于屏幕的正中间。由于android手机屏幕尺寸多种多样,而图片的大小也不甚相同,为了灵活的使用此效果,需要将任意尺寸比例的图片显示在任意尺寸比例的手机屏幕的正中间,同时不使图片扭曲变形。
2更改float[]的值,然后更新Matrix并应用到ImageView中,从而达到图片zoomin的效果
本篇文章抽象出一个新的view,以便于使用及修改,同时完善了上篇文章由于行文仓促而留下的多个未实现的需求。
实现效果如下:
快速应用到工程:
首先,添加依赖:
compile 'cn.carbs.android:AutoZoomInImageView:1.0.0'
其次,xml布局文件中声明
<cn.carbs.android.library.AutoZoomInImageView android:id="@+id/iv" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/horse" />
最后,在Activity中使用代码:(注意,要在view完全显示出来之后使用,因此这里我用了post(runnable)的方式)
iv.post(new Runnable() {//iv即AutoZoomInImageView @Override public void run() { //简单方式启动放大动画 // iv.init() // .startZoomInByScaleDeltaAndDuration(0.3f, 1000, 1000);//放大增量是0.3,放大时间是1000毫秒,放大开始时间是1000毫秒以后 //使用较为具体的方式启动放大动画 iv.init() .setScaleDelta(0.2f)//放大的系数是原来的(1 + 0.2)倍 .setDurationMillis(1500)//动画的执行时间为1500毫秒 .setOnZoomListener(new AutoZoomInImageView.OnZoomListener(){ @Override public void onStart(View view) { //放大动画开始时的回调 } @Override public void onUpdate(View view, float progress) { //放大动画进行过程中的回调 progress取值范围是[0,1] } @Override public void onEnd(View view) { //放大动画结束时的回调 } }) .start(1000);//延迟1000毫秒启动 } });
主要的代码如下:
1.AutoZoomInImageView的代码为:
1 package cn.carbs.android.library; 2 3 import android.animation.Animator; 4 import android.animation.ValueAnimator; 5 import android.annotation.SuppressLint; 6 import android.content.Context; 7 import android.graphics.Matrix; 8 import android.graphics.drawable.Drawable; 9 import android.util.AttributeSet; 10 import android.view.View; 11 import android.widget.ImageView; 12 13 @SuppressLint("NewApi") 14 public class AutoZoomInImageView extends ImageView{ 15 16 private Drawable mDrawable; 17 private int mDrawableW; 18 private int mDrawableH; 19 20 private int mImageViewW; 21 private int mImageViewH; 22 23 private Matrix mMatrix; 24 private float[] mValues = new float[9]; 25 26 private float mScaleDelta = 0.2f; 27 private long mDurationMillis = 700; 28 29 public AutoZoomInImageView(Context context) { 30 super(context); 31 this.setScaleType(ScaleType.MATRIX); 32 } 33 34 public AutoZoomInImageView(Context context, AttributeSet attrs) { 35 super(context, attrs); 36 this.setScaleType(ScaleType.MATRIX); 37 } 38 39 public AutoZoomInImageView(Context context, AttributeSet attrs, int defStyle) { 40 super(context, attrs, defStyle); 41 this.setScaleType(ScaleType.MATRIX); 42 } 43 44 public AutoZoomInImageView init(){ 45 initInternalValues(); 46 initPicturePosition(); 47 return this; 48 } 49 50 public void init(Drawable drawable){ 51 initInternalValues(drawable); 52 initPicturePosition(); 53 } 54 55 private void initInternalValues(){ 56 mDrawable = getDrawable(); 57 58 if(mDrawable == null){ 59 throw new IllegalArgumentException("please set the source of AutoZoomInImageView"); 60 } 61 62 mDrawableW = mDrawable.getIntrinsicWidth(); 63 mDrawableH = mDrawable.getIntrinsicHeight(); 64 65 mImageViewW = getMeasuredWidth(); 66 mImageViewH = getMeasuredHeight(); 67 68 mMatrix = getImageMatrix(); 69 mMatrix.getValues(mValues); 70 } 71 72 private void initInternalValues(Drawable drawable){ 73 mDrawable = drawable; 74 75 if(mDrawable == null){ 76 throw new IllegalArgumentException("please set the source of AutoZoomInImageView"); 77 } 78 79 mDrawableW = mDrawable.getIntrinsicWidth(); 80 mDrawableH = mDrawable.getIntrinsicHeight(); 81 82 mImageViewW = getMeasuredWidth(); 83 mImageViewH = getMeasuredHeight(); 84 85 mMatrix = getImageMatrix(); 86 mMatrix.getValues(mValues); 87 } 88 89 private void initPicturePosition(){ 90 updateMatrixValuesOrigin(mMatrix, mValues, mDrawableW, mDrawableH, mImageViewW, mImageViewH); 91 setImageMatrix(mMatrix); 92 } 93 94 private void startZoomInByScaleDelta(final float scaleDelta, long duration){ 95 96 final float oriScaleX = mValues[0]; 97 final float oriScaleY = mValues[4]; 98 99 ValueAnimator va = ValueAnimator.ofFloat(0, scaleDelta); 100 va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 101 102 @Override 103 public void onAnimationUpdate(ValueAnimator animation) { 104 float value = (Float)animation.getAnimatedValue(); 105 if(mOnZoomListener != null) mOnZoomListener.onUpdate(AutoZoomInImageView.this, value / scaleDelta); 106 updateMatrixValuesSpan(mValues, mDrawableW, mDrawableH, mImageViewW, mImageViewH, 107 oriScaleX, oriScaleY, value); 108 mMatrix.setValues(mValues); 109 setImageMatrix(mMatrix); 110 } 111 }); 112 va.addListener(new Animator.AnimatorListener() { 113 @Override 114 public void onAnimationStart(Animator animation) { 115 if(mOnZoomListener != null) mOnZoomListener.onStart(AutoZoomInImageView.this); 116 } 117 118 @Override 119 public void onAnimationEnd(Animator animation) { 120 if(mOnZoomListener != null) mOnZoomListener.onEnd(AutoZoomInImageView.this); 121 } 122 @Override 123 public void onAnimationCancel(Animator animation) {} 124 @Override 125 public void onAnimationRepeat(Animator animation) {} 126 }); 127 va.setDuration(duration); 128 va.start(); 129 } 130 131 /** 132 * 开始放大动画 133 * start zooming in 134 * @param scaleDelta 放大的增大倍数,如果是0.2,那么最后大小放大至1.2倍。 135 * the scale that the image will add to original scale 136 * @param durationMillis 放大效果的持续时间,单位毫秒。 137 * the duration of zoomin animation, in millisecond. 138 * @param delayMillis 开始放大效果的延迟时间,单位毫秒。delayed毫秒后开始放大动画效果。 139 * the delayed time of starting zoomin animation, in millisecond. 140 */ 141 public void startZoomInByScaleDeltaAndDuration(final float scaleDelta, final long durationMillis, long delayMillis){ 142 if(scaleDelta < 0){ 143 throw new IllegalArgumentException("scaleDelta should be larger than 0, now scaleDelta is " + scaleDelta); 144 } 145 if(durationMillis < 0){ 146 throw new IllegalArgumentException("durationMillis should not be less than 0, now durationMillis is " + durationMillis); 147 } 148 if(delayMillis < 0){ 149 throw new IllegalArgumentException("delayMillis should not be less than 0, now delayMillis is " + delayMillis); 150 } 151 152 postDelayed(new Runnable() { 153 @Override 154 public void run() { 155 startZoomInByScaleDelta(scaleDelta, durationMillis); 156 } 157 }, delayMillis); 158 } 159 160 /** 161 * 放大的增大倍数,如果是0.2,那么最后大小放大至1.2倍。 162 * the scale that the image will add to original scale 163 * @param scaleDelta 164 * @return 165 */ 166 public AutoZoomInImageView setScaleDelta(float scaleDelta){ 167 mScaleDelta = scaleDelta; 168 return this; 169 } 170 171 /** 172 * 放大效果的持续时间,单位毫秒。 173 * the duration of zoomin animation, in millisecond. 174 * @param durationMillis 175 * @return 176 */ 177 public AutoZoomInImageView setDurationMillis(long durationMillis){ 178 mDurationMillis = durationMillis; 179 return this; 180 } 181 182 /** 183 * 动画结束的回调 184 * callback when zoomin animation finished 185 * @param onZoomListener 186 * @return 187 */ 188 public AutoZoomInImageView setOnZoomListener(OnZoomListener onZoomListener){ 189 mOnZoomListener = onZoomListener; 190 return this; 191 } 192 193 /** 194 * 开始放大效果 195 * start animation of zoomin 196 * @param delayMillis 开始放大效果的延迟时间,单位毫秒。delayed毫秒后开始放大动画效果 197 * the delayed time of starting zoomin animation, in millisecond. 198 */ 199 public void start(long delayMillis){ 200 postDelayed(new Runnable() { 201 @Override 202 public void run() { 203 startZoomInByScaleDelta(mScaleDelta, mDurationMillis); 204 } 205 }, delayMillis); 206 } 207 208 private void updateMatrixValuesOrigin(Matrix outMatrix, float[] outValues, float drawW, float drawH, float imageW, float imageH){ 209 210 if(outMatrix == null || outValues == null){ 211 throw new IllegalArgumentException("please set the source of AutoZoomInImageView's matrix and values"); 212 } 213 214 outMatrix.reset(); 215 216 if((imageH * drawW > drawH * imageW)){ 217 float scale1 = (imageH)/(drawH); 218 float offset1 = (drawW * scale1 - imageW)/2; 219 220 outMatrix.postScale(scale1, scale1); 221 outMatrix.postTranslate(-offset1, 0); 222 223 }else{ 224 float scale2 = (imageW)/(drawW); 225 float offset2 = (drawH * scale2 - imageH)/2; 226 227 outMatrix.postScale(scale2, scale2); 228 outMatrix.postTranslate(0, -offset2); 229 } 230 outMatrix.getValues(outValues); 231 } 232 233 private void updateMatrixValuesSpan(float[] outValues, 234 float drawW, float drawH, 235 float imageW, float imageH, 236 float oriScaleX, float oriScaleY, 237 float scaleDelta){ 238 //根据四个参数:图片的宽高、控件的宽高,动态的计算出输出的矩阵(float数组)的值 239 outValues[0] = oriScaleX * (1 + scaleDelta); 240 outValues[4] = oriScaleY * (1 + scaleDelta); 241 float offsetwidth = (drawW * outValues[0] - imageW)/2; 242 outValues[2] = - offsetwidth; 243 float offsetHeight = (drawH * outValues[0] - imageH)/2; 244 outValues[5] = - offsetHeight; 245 } 246 247 private OnZoomListener mOnZoomListener; 248 public interface OnZoomListener{ 249 /** 250 * 动画更新时执行的回调 251 * @param view 返回此AutoZoomInImageView 252 * @param progress 返回动画进行过程,范围是[0,1] 253 */ 254 void onUpdate(View view, float progress); 255 void onEnd(View view); 256 void onStart(View view); 257 } 258 259 260 //function for log 261 public String printMyMatrix(Matrix m){ 262 float[] valueFloat = new float[9]; 263 m.getValues(valueFloat); 264 265 String s = ""; 266 for(int i = 0; i < 9; i++){ 267 s = s + " [ " + valueFloat[i] + " ] "; 268 } 269 return s; 270 } 271 272 //function for log 273 public String printMyValue(float[] valueFloat){ 274 String s = ""; 275 for(int i = 0; i < 9; i++){ 276 s = s + " [ " + valueFloat[i] + " ] "; 277 } 278 return s; 279 } 280 281 }
2.代码中调用AutoZoomInImageView启用动画的方法:
1 iv.post(new Runnable() {//iv即AutoZoomInImageView 2 3 @Override 4 public void run() { 5 //简单方式启动放大动画 6 // iv.init() 7 // .startZoomInByScaleDeltaAndDuration(0.3f, 1000, 1000);//放大增量是0.3,放大时间是1000毫秒,放大开始时间是1000毫秒以后 8 //使用较为具体的方式启动放大动画 9 iv.init() 10 .setScaleDelta(0.2f)//放大的系数是原来的(1 + 0.3)倍 11 .setDurationMillis(1500)//动画的执行时间为1000毫秒 12 .setOnZoomListener(new AutoZoomInImageView.OnZoomListener(){ 13 @Override 14 public void onStart(View view) { 15 //放大动画开始时的回调 16 } 17 @Override 18 public void onUpdate(View view, float progress) { 19 //放大动画进行过程中的回调 progress取值范围是[0,1] 20 } 21 @Override 22 public void onEnd(View view) { 23 //放大动画结束时的回调 24 } 25 }) 26 .start(1000);//延迟1000毫秒启动 27 } 28 });
3.xml文件中声明此view:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <cn.carbs.android.library.AutoZoomInImageView android:id="@+id/iv" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/horse" /> </LinearLayout>
代码下载地址: