[转]Android UI:看看Google官方自定义带旋转动画的ImageView-----RotateImageView怎么写(附 图片淡入淡出效果)

http://blog.csdn.net/yanzi1225627/article/details/22439119

 

众所周知,想要让ImageView旋转的话,可以用setRotation()让其围绕中心点旋转,但这个旋转是不带动画的,也就是旋转屏幕时图片噌的一下就转过去了,看不到旋转的过程,此UI体验不大好,为此需要自定义带旋转动画的ImageView.虽然Google SDK里基本控件里没有,但在Camera的原生APP代码里却给出了带旋转动画的ImageView,即今天的主角:RotateImageView。

尽管民间已有链接1 链接2  链接3 提供思路实现带旋转动画的ImageView,都不如Google官方标配的啊。先上源码吧,为实现此目的,需要四个文件:

1、Rotatable.java

 

  1. <span style="font-family:Comic Sans MS;font-size:18px;">/* 
  2.  * Copyright (C) 2011 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package com.android.ui;  
  18.   
  19. public interface Rotatable {  
  20.     // Set parameter 'animation' to true to have animation when rotation.  
  21.     public void setOrientation(int orientation, boolean animation);  
  22. }  
  23. </span>  


他就是个接口,里面有setOrientation这个方法。Google这么写是因为有大量自定义UI都要继承这个接口。

 

2、TwoStateImageView.java

 

  1. <span style="font-family:Comic Sans MS;font-size:18px;">/* 
  2.  * Copyright (C) 2011 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package com.android.ui;  
  18.   
  19. import android.content.Context;  
  20. import android.util.AttributeSet;  
  21. import android.widget.ImageView;  
  22.   
  23. /** 
  24.  * A @{code ImageView} which change the opacity of the icon if disabled. 
  25.  */  
  26. public class TwoStateImageView extends ImageView {  
  27.     private static final int ENABLED_ALPHA = 255;  
  28.     private static final int DISABLED_ALPHA = (int) (255 * 0.4);  
  29.     private boolean mFilterEnabled = true;  
  30.   
  31.     public TwoStateImageView(Context context, AttributeSet attrs) {  
  32.         super(context, attrs);  
  33.     }  
  34.   
  35.     public TwoStateImageView(Context context) {  
  36.         this(context, null);  
  37.     }  
  38.   
  39.     @SuppressWarnings("deprecation")  
  40.     @Override  
  41.     public void setEnabled(boolean enabled) {  
  42.         super.setEnabled(enabled);  
  43.         if (mFilterEnabled) {  
  44.             if (enabled) {  
  45.                 setAlpha(ENABLED_ALPHA);  
  46.             } else {  
  47.                 setAlpha(DISABLED_ALPHA);  
  48.             }  
  49.         }  
  50.     }  
  51.   
  52.     public void enableFilter(boolean enabled) {  
  53.         mFilterEnabled = enabled;  
  54.     }  
  55. }  
  56. </span>  


在ImageView的基础上增加了mFilterEnabled这个属性,开关打开后,通过改变图片的Alpha实现两种状态,默认这个开关是开的,图片透明度为255,即不透明。

 

3、RotateImageView.java

 

  1. <span style="font-family:Comic Sans MS;font-size:18px;">/* 
  2.  * Copyright (C) 2009 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package com.android.ui;  
  18.   
  19. import android.content.Context;  
  20. import android.graphics.Bitmap;  
  21. import android.graphics.Canvas;  
  22. import android.graphics.Rect;  
  23. import android.graphics.drawable.BitmapDrawable;  
  24. import android.graphics.drawable.Drawable;  
  25. import android.graphics.drawable.TransitionDrawable;  
  26. import android.media.ThumbnailUtils;  
  27. import android.util.AttributeSet;  
  28. import android.util.Log;  
  29. import android.view.ViewGroup.LayoutParams;  
  30. import android.view.animation.AnimationUtils;  
  31. import android.widget.ImageView;  
  32.   
  33. /** 
  34.  * A @{code ImageView} which can rotate it's content. 
  35.  */  
  36. public class RotateImageView extends TwoStateImageView implements Rotatable {  
  37.   
  38.     @SuppressWarnings("unused")  
  39.     private static final String TAG = "RotateImageView";  
  40.   
  41.     private static final int ANIMATION_SPEED = 270; // 270 deg/sec  
  42.   
  43.     private int mCurrentDegree = 0; // [0, 359]  
  44.     private int mStartDegree = 0;  
  45.     private int mTargetDegree = 0;  
  46.   
  47.     private boolean mClockwise = false, mEnableAnimation = true;  
  48.   
  49.     private long mAnimationStartTime = 0;  
  50.     private long mAnimationEndTime = 0;  
  51.   
  52.     public RotateImageView(Context context, AttributeSet attrs) {  
  53.         super(context, attrs);  
  54.     }  
  55.   
  56.     public RotateImageView(Context context) {  
  57.         super(context);  
  58.     }  
  59.   
  60.     protected int getDegree() {  
  61.         return mTargetDegree;  
  62.     }  
  63.   
  64.     // Rotate the view counter-clockwise  
  65.     @Override  
  66.     public void setOrientation(int degree, boolean animation) {  
  67.         mEnableAnimation = animation;  
  68.         // make sure in the range of [0, 359]  
  69.         degree = degree >= 0 ? degree % 360 : degree % 360 + 360;  
  70.         if (degree == mTargetDegree) return;  
  71.   
  72.         mTargetDegree = degree;  
  73.         if (mEnableAnimation) {  
  74.             mStartDegree = mCurrentDegree;  
  75.             mAnimationStartTime = AnimationUtils.currentAnimationTimeMillis();  
  76.   
  77.             int diff = mTargetDegree - mCurrentDegree;  
  78.             diff = diff >= 0 ? diff : 360 + diff; // make it in range [0, 359]  
  79.   
  80.             // Make it in range [-179, 180]. That's the shorted distance between the  
  81.             // two angles  
  82.             diff = diff > 180 ? diff - 360 : diff;  
  83.   
  84.             mClockwise = diff >= 0;  
  85.             mAnimationEndTime = mAnimationStartTime  
  86.                     + Math.abs(diff) * 1000 / ANIMATION_SPEED;  
  87.         } else {  
  88.             mCurrentDegree = mTargetDegree;  
  89.         }  
  90.   
  91.         invalidate();  
  92.     }  
  93.   
  94.     @Override  
  95.     protected void onDraw(Canvas canvas) {  
  96.         Drawable drawable = getDrawable();  
  97.         if (drawable == null) return;  
  98.   
  99.         Rect bounds = drawable.getBounds();  
  100.         int w = bounds.right - bounds.left;  
  101.         int h = bounds.bottom - bounds.top;  
  102.   
  103.         if (w == 0 || h == 0) return; // nothing to draw  
  104.   
  105.         if (mCurrentDegree != mTargetDegree) {  
  106.             long time = AnimationUtils.currentAnimationTimeMillis();  
  107.             if (time < mAnimationEndTime) {  
  108.                 int deltaTime = (int)(time - mAnimationStartTime);  
  109.                 int degree = mStartDegree + ANIMATION_SPEED  
  110.                         * (mClockwise ? deltaTime : -deltaTime) / 1000;  
  111.                 degree = degree >= 0 ? degree % 360 : degree % 360 + 360;  
  112.                 mCurrentDegree = degree;  
  113.                 invalidate();  
  114.             } else {  
  115.                 mCurrentDegree = mTargetDegree;  
  116.             }  
  117.         }  
  118.   
  119.         int left = getPaddingLeft();  
  120.         int top = getPaddingTop();  
  121.         int right = getPaddingRight();  
  122.         int bottom = getPaddingBottom();  
  123.         int width = getWidth() - left - right;  
  124.         int height = getHeight() - top - bottom;  
  125.   
  126.         int saveCount = canvas.getSaveCount();  
  127.   
  128.         // Scale down the image first if required.  
  129.         if ((getScaleType() == ImageView.ScaleType.FIT_CENTER) &&  
  130.                 ((width < w) || (height < h))) {  
  131.             float ratio = Math.min((float) width / w, (float) height / h);  
  132.             canvas.scale(ratio, ratio, width / 2.0f, height / 2.0f);  
  133.         }  
  134.         canvas.translate(left + width / 2, top + height / 2);  
  135.         canvas.rotate(-mCurrentDegree);  
  136.         canvas.translate(-w / 2, -h / 2);  
  137.         drawable.draw(canvas);  
  138.         canvas.restoreToCount(saveCount);  
  139.     }  
  140.   
  141.     private Bitmap mThumb;  
  142.     private Drawable[] mThumbs;  
  143.     private TransitionDrawable mThumbTransition;  
  144.   
  145.     public void setBitmap(Bitmap bitmap) {  
  146.         // Make sure uri and original are consistently both null or both  
  147.         // non-null.  
  148.         if (bitmap == null) {  
  149.             mThumb = null;  
  150.             mThumbs = null;  
  151.             setImageDrawable(null);  
  152.             setVisibility(GONE);  
  153.             return;  
  154.         }  
  155.   
  156.         LayoutParams param = getLayoutParams();  
  157.         //下面四行代码被我注释掉了,换成了固定值400*400 by yanguoqi 2014-3-28  
  158. //        final int miniThumbWidth = param.width  
  159. //                - getPaddingLeft() - getPaddingRight();  
  160. //        final int miniThumbHeight = param.height  
  161. //                - getPaddingTop() - getPaddingBottom();  
  162.         final int miniThumbWidth = 400;  
  163.         final int miniThumbHeight = 400;  
  164.           
  165.         Log.i("yan", "param.width = " + param.width + " getPaddingLeft() = "  
  166.                 + getPaddingLeft() + " getPaddingRight()" + getPaddingRight());  
  167.         Log.i("yan", "miniThumbWidth = " + miniThumbWidth);  
  168.         mThumb = ThumbnailUtils.extractThumbnail(  
  169.                 bitmap, miniThumbWidth, miniThumbHeight);  
  170.         Drawable drawable;  
  171.         if (mThumbs == null || !mEnableAnimation) {  
  172.             mThumbs = new Drawable[2];  
  173.             mThumbs[1] = new BitmapDrawable(getContext().getResources(), mThumb);  
  174.             setImageDrawable(mThumbs[1]);  
  175.         } else {  
  176.             mThumbs[0] = mThumbs[1];  
  177.             mThumbs[1] = new BitmapDrawable(getContext().getResources(), mThumb);  
  178.             mThumbTransition = new TransitionDrawable(mThumbs);  
  179.             setImageDrawable(mThumbTransition);  
  180.             mThumbTransition.startTransition(500);  
  181.         }  
  182.         setVisibility(VISIBLE);  
  183.     }  
  184. }  
  185. </span>  


整体没啥可说的,在setBitmap处有四句代码运行不正确我给换成了固定值。这个setBitmap干啥呢?是为了实现在同一个ImageView切换图片时的淡入淡出效果,如果单纯是旋转则不需要这个函数。不过本文的测试代码还是对这一功能做了测试。其思想也很简单,用Drawable[] mThumbs来存两个缩略图,第一次set的时候缩略图存一张,第二次再set的时候再放数组里一张,然后将Drawable[]数组实例化到TransitionDrawable变量里,通过这个变量的startTransition()显示淡入淡出效果,里面的参数表示时间。如果设成1000毫秒即1秒则会非常明显。关于TransitionDrawable的更多用法和解释可以参见 这里

 

4、有了以上三个文件其实已经可以完成旋转ImageView了,在布局里定义成RotateImageView即可。但仍需要角度。下面这个函数是将连续的旋转角度0---360度变换成0°、90°、180°、270°四个值。我们旋转屏幕时,当成一定角度时才旋转图片,而不是稍微动一下就旋转,除非需求如此。

Util.java

 

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package com.android.util;  
  2.   
  3. import android.app.Activity;  
  4. import android.view.OrientationEventListener;  
  5. import android.view.Surface;  
  6.   
  7. public class Util {  
  8.     public static final int ORIENTATION_HYSTERESIS = 5;  
  9.   
  10.     public static int roundOrientation(int orientation, int orientationHistory) {  
  11.         boolean changeOrientation = false;  
  12.         if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {  
  13.             changeOrientation = true;  
  14.         } else {  
  15.             int dist = Math.abs(orientation - orientationHistory);  
  16.             dist = Math.min( dist, 360 - dist );  
  17.             changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );  
  18.         }  
  19.         if (changeOrientation) {  
  20.             return ((orientation + 45) / 90 * 90) % 360;  
  21.         }  
  22.         return orientationHistory;  
  23.     }  
  24.     public static int getDisplayRotation(Activity activity) {  
  25.         int rotation = activity.getWindowManager().getDefaultDisplay()  
  26.                 .getRotation();  
  27.         switch (rotation) {  
  28.             case Surface.ROTATION_0: return 0;  
  29.             case Surface.ROTATION_90: return 90;  
  30.             case Surface.ROTATION_180: return 180;  
  31.             case Surface.ROTATION_270: return 270;  
  32.         }  
  33.         return 0;  
  34.     }  
  35. }  
  36. </span>  


下面就要解决如何获得屏幕旋转角度的问题。最初我也想着用onConfigurationChanged()但发现这就是扯淡,这个只能检测此时处在横屏还是竖屏。后面再交代其用法。最终是用OrientationEventListener监测的。

 

MainActivity.java代码如下:

 

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.testrotateimageview;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Context;  
  5. import android.content.res.Configuration;  
  6. import android.graphics.Bitmap;  
  7. import android.graphics.BitmapFactory;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10. import android.view.Menu;  
  11. import android.view.OrientationEventListener;  
  12. import android.view.View;  
  13. import android.widget.Button;  
  14. import android.widget.ImageView;  
  15.   
  16. import com.android.ui.RotateImageView;  
  17. import com.android.util.Util;  
  18.   
  19. public class MainActivity extends Activity {  
  20.     private static final String tag = "yan";  
  21.     RotateImageView rotateImg1;  
  22.     RotateImageView rotateImg2;  
  23.     ImageView commonImg;  
  24.     Button fadeBtn;  
  25.     MyOrientationEventListener mOrientationListener;  
  26.     Bitmap a;  
  27.     Bitmap b;  
  28.     boolean flag = true;  
  29.     int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;  
  30.     @Override  
  31.     protected void onCreate(Bundle savedInstanceState) {  
  32.         super.onCreate(savedInstanceState);  
  33.         setContentView(R.layout.activity_main);  
  34.         initUI();  
  35.         mOrientationListener = new MyOrientationEventListener(this);  
  36.         b = BitmapFactory.decodeResource(getResources(), R.drawable.kunqing2);  
  37.         a = BitmapFactory.decodeResource(getResources(), R.drawable.kunlong);  
  38.         fadeBtn.setOnClickListener(new View.OnClickListener() {  
  39.   
  40.             @Override  
  41.             public void onClick(View v) {  
  42.                 // TODO Auto-generated method stub  
  43.                 if(flag){  
  44.                     rotateImg1.setBitmap(b);  
  45.                     flag = false;  
  46.                 }  
  47.                 else{  
  48.                     rotateImg1.setBitmap(a);  
  49.                     flag = true;  
  50.                 }  
  51.             }  
  52.         });  
  53.   
  54.   
  55.   
  56.   
  57.   
  58.     }  
  59.   
  60.     @Override  
  61.     public boolean onCreateOptionsMenu(Menu menu) {  
  62.         // Inflate the menu; this adds items to the action bar if it is present.  
  63.         getMenuInflater().inflate(R.menu.main, menu);  
  64.         return true;  
  65.     }  
  66.   
  67.   
  68.     @Override  
  69.     protected void onResume() {  
  70.         // TODO Auto-generated method stub  
  71.         super.onResume();  
  72.         mOrientationListener.enable();  
  73.     }  
  74.   
  75.     @Override  
  76.     protected void onPause() {  
  77.         // TODO Auto-generated method stub  
  78.         super.onPause();  
  79.         mOrientationListener.disable();  
  80.     }  
  81.   
  82.     private void initUI(){  
  83.         rotateImg1 = (RotateImageView)findViewById(R.id.rotate_img_1);  
  84.         rotateImg1.setImageResource(R.drawable.nan_1);  
  85.         rotateImg2 = (RotateImageView)findViewById(R.id.rotate_img_2);  
  86.         rotateImg2.setImageResource(R.drawable.nan_2);  
  87.         commonImg = (ImageView)findViewById(R.id.common_img);  
  88.         fadeBtn = (Button)findViewById(R.id.btn_fade);  
  89.     }  
  90.     private class MyOrientationEventListener extends OrientationEventListener{  
  91.   
  92.         public MyOrientationEventListener(Context context) {  
  93.             super(context);  
  94.             // TODO Auto-generated constructor stub  
  95.         }  
  96.   
  97.         @Override  
  98.         public void onOrientationChanged(int orientation) {  
  99.             // TODO Auto-generated method stub  
  100.             if(orientation == OrientationEventListener.ORIENTATION_UNKNOWN){  
  101.                 return;  
  102.             }  
  103.             mOrientation = Util.roundOrientation(orientation, mOrientation);  
  104.             Log.i(tag, "MyOrientationEventListener mOrientation = " + mOrientation);  
  105.   
  106.             rotateImg1.setOrientation(mOrientation, true);  
  107.             rotateImg2.setOrientation(mOrientation, true);  
  108.             commonImg.setRotation(-mOrientation);  
  109.         }  
  110.   
  111.     }  
  112.     @Override  
  113.     public void onConfigurationChanged(Configuration newConfig) {  
  114.         // TODO Auto-generated method stub  
  115.         super.onConfigurationChanged(newConfig);  
  116.         int degree = newConfig.orientation;  
  117.         Log.i("yan", "onConfigurationChanged = " + degree);  
  118.     }  
  119.       
  120.   
  121. }  
  122. </span>  


布局如下:activity_main.xml

 

 

  1. <span style="font-family:Comic Sans MS;font-size:18px;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.   
  11.     <Button  
  12.         android:id="@+id/btn_fade"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:text="淡入淡出\n效果测试" />  
  16.     <com.android.ui.RotateImageView   
  17.         android:id="@+id/rotate_img_1"  
  18.         android:layout_width="wrap_content"  
  19.         android:layout_height="wrap_content"  
  20.         android:layout_alignParentTop="true"  
  21.         android:layout_centerHorizontal="true"  
  22. />  
  23.     <com.android.ui.RotateImageView   
  24.         android:id="@+id/rotate_img_2"  
  25.         android:layout_width="wrap_content"  
  26.         android:layout_height="wrap_content"  
  27.         android:layout_marginTop="20dip"  
  28.         android:layout_below="@id/rotate_img_1"  
  29.         android:layout_centerHorizontal="true"/>  
  30.     <ImageView   
  31.         android:id="@+id/common_img"  
  32.          android:layout_width="wrap_content"  
  33.         android:layout_height="wrap_content"  
  34.           android:layout_below="@id/rotate_img_2"  
  35.           android:layout_marginTop="20dip"  
  36.         android:layout_centerHorizontal="true"  
  37.         android:src="@drawable/nan_1"/>  
  38.   
  39. </RelativeLayout>  
  40. </span>  


运行效果: 下图是初始界面,三幅图,前两个是RotateImageView,第三个是一般的ImageView.可以看出当RoteteImageView设置不使用动画时,其旋转效果和ImageView的setRotation是一样的。第一幅图和第二图的差别,第一图南怀瑾先生的,四周不带透明区域,第二幅图我用ps做了四周的透明处理。

 

如果不使用文中的Util.roundOrientation()函数,即有个角度就让它转,如果它的四周没有透明区域的话将会看到下图:

(抱歉,截图不是一次截的,但效果是真实的,此图周四晚截得)

下面这幅图是用大中兴的geek牛逼的连续拍照拍下来的,记录了四周不带透明区域旋转时图片变形的场景:

第一副图片里的淡入淡出测试按钮大家自己按看效果,太晚了不传图了。

代码下载:http://download.csdn.net/detail/yanzi1225627/7115009

posted @ 2015-11-18 17:31  牧之丨  阅读(603)  评论(0编辑  收藏  举报