├── libs └── android-support-v4.jar ├── res ├── drawable │ └── gif_heart.gif ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── values-sw600dp │ └── dimens.xml ├── values │ ├── dimens.xml │ ├── strings.xml │ ├── attrs.xml │ └── styles.xml ├── menu │ └── main.xml ├── values-sw720dp-land │ └── dimens.xml ├── values-v11 │ └── styles.xml ├── values-v14 │ └── styles.xml └── layout │ └── activity_main.xml ├── .gitignore ├── README.md ├── project.properties ├── proguard-project.txt ├── AndroidManifest.xml ├── src └── com │ └── basv │ └── gifmoviewview │ ├── MainActivity.java │ └── widget │ └── GifMovieView.java └── LICENSE /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbakhtiarov/gif-movie-view/HEAD/libs/android-support-v4.jar -------------------------------------------------------------------------------- /res/drawable/gif_heart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbakhtiarov/gif-movie-view/HEAD/res/drawable/gif_heart.gif -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbakhtiarov/gif-movie-view/HEAD/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbakhtiarov/gif-movie-view/HEAD/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbakhtiarov/gif-movie-view/HEAD/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | GifMoviewView 4 | Settings 5 | Hello world! 6 | -------------------------------------------------------------------------------- /res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 128dp 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | -------------------------------------------------------------------------------- /res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | -------------------------------------------------------------------------------- /res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gif-movie-view 2 | ============== 3 | 4 | Android View widget for displaying GIF animations. 5 | 6 | To show animated GIF in your application just add GifMovieView into your layout. 7 | 8 | 13 | 14 | 15 | You can set Movie object dynamically or as a resource ID and control animation playback. 16 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/com/basv/gifmoviewview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.basv.gifmoviewview; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.Menu; 6 | import android.view.View; 7 | 8 | import com.basv.gifmoviewview.widget.GifMovieView; 9 | 10 | public class MainActivity extends Activity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | 17 | final GifMovieView gif1 = (GifMovieView) findViewById(R.id.gif1); 18 | gif1.setMovieResource(R.drawable.gif_heart); 19 | } 20 | 21 | @Override 22 | public boolean onCreateOptionsMenu(Menu menu) { 23 | // Inflate the menu; this adds items to the action bar if it is present. 24 | getMenuInflater().inflate(R.menu.main, menu); 25 | return true; 26 | } 27 | 28 | public void onGifClick(View v) { 29 | GifMovieView gif = (GifMovieView) v; 30 | gif.setPaused(!gif.isPaused()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Sergey Bakhtiarov 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 25 | 26 | 30 | 31 | 37 | 38 | 44 | 45 | 51 | 52 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/com/basv/gifmoviewview/widget/GifMovieView.java: -------------------------------------------------------------------------------- 1 | package com.basv.gifmoviewview.widget; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Canvas; 7 | import android.graphics.Movie; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | 12 | import com.basv.gifmoviewview.R; 13 | 14 | /** 15 | * This is a View class that wraps Android {@link Movie} object and displays it. 16 | * You can set GIF as a Movie object or as a resource id from XML or by calling 17 | * {@link #setMovie(Movie)} or {@link #setMovieResource(int)}. 18 | *

19 | * You can pause and resume GIF animation by calling {@link #setPaused(boolean)}. 20 | *

21 | * The animation is drawn in the center inside of the measured view bounds. 22 | * 23 | * @author Sergey Bakhtiarov 24 | */ 25 | 26 | public class GifMovieView extends View { 27 | 28 | private static final int DEFAULT_MOVIEW_DURATION = 1000; 29 | 30 | private int mMovieResourceId; 31 | private Movie mMovie; 32 | 33 | private long mMovieStart; 34 | private int mCurrentAnimationTime = 0; 35 | 36 | /** 37 | * Position for drawing animation frames in the center of the view. 38 | */ 39 | private float mLeft; 40 | private float mTop; 41 | 42 | /** 43 | * Scaling factor to fit the animation within view bounds. 44 | */ 45 | private float mScale; 46 | 47 | /** 48 | * Scaled movie frames width and height. 49 | */ 50 | private int mMeasuredMovieWidth; 51 | private int mMeasuredMovieHeight; 52 | 53 | private volatile boolean mPaused = false; 54 | private boolean mVisible = true; 55 | 56 | public GifMovieView(Context context) { 57 | this(context, null); 58 | } 59 | 60 | public GifMovieView(Context context, AttributeSet attrs) { 61 | this(context, attrs, R.styleable.CustomTheme_gifMoviewViewStyle); 62 | } 63 | 64 | public GifMovieView(Context context, AttributeSet attrs, int defStyle) { 65 | super(context, attrs, defStyle); 66 | 67 | setViewAttributes(context, attrs, defStyle); 68 | } 69 | 70 | @SuppressLint("NewApi") 71 | private void setViewAttributes(Context context, AttributeSet attrs, int defStyle) { 72 | 73 | /** 74 | * Starting from HONEYCOMB have to turn off HW acceleration to draw 75 | * Movie on Canvas. 76 | */ 77 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 78 | setLayerType(View.LAYER_TYPE_SOFTWARE, null); 79 | } 80 | 81 | final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GifMoviewView, defStyle, 82 | R.style.Widget_GifMoviewView); 83 | 84 | mMovieResourceId = array.getResourceId(R.styleable.GifMoviewView_gif, -1); 85 | mPaused = array.getBoolean(R.styleable.GifMoviewView_paused, false); 86 | 87 | array.recycle(); 88 | 89 | if (mMovieResourceId != -1) { 90 | mMovie = Movie.decodeStream(getResources().openRawResource(mMovieResourceId)); 91 | } 92 | } 93 | 94 | public void setMovieResource(int movieResId) { 95 | this.mMovieResourceId = movieResId; 96 | mMovie = Movie.decodeStream(getResources().openRawResource(mMovieResourceId)); 97 | requestLayout(); 98 | } 99 | 100 | public void setMovie(Movie movie) { 101 | this.mMovie = movie; 102 | requestLayout(); 103 | } 104 | 105 | public Movie getMovie() { 106 | return mMovie; 107 | } 108 | 109 | public void setMovieTime(int time) { 110 | mCurrentAnimationTime = time; 111 | invalidate(); 112 | } 113 | 114 | public void setPaused(boolean paused) { 115 | this.mPaused = paused; 116 | 117 | /** 118 | * Calculate new movie start time, so that it resumes from the same 119 | * frame. 120 | */ 121 | if (!paused) { 122 | mMovieStart = android.os.SystemClock.uptimeMillis() - mCurrentAnimationTime; 123 | } 124 | 125 | invalidate(); 126 | } 127 | 128 | public boolean isPaused() { 129 | return this.mPaused; 130 | } 131 | 132 | @Override 133 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 134 | 135 | if (mMovie != null) { 136 | int movieWidth = mMovie.width(); 137 | int movieHeight = mMovie.height(); 138 | 139 | /* 140 | * Calculate horizontal scaling 141 | */ 142 | float scaleH = 1f; 143 | int measureModeWidth = MeasureSpec.getMode(widthMeasureSpec); 144 | 145 | if (measureModeWidth != MeasureSpec.UNSPECIFIED) { 146 | int maximumWidth = MeasureSpec.getSize(widthMeasureSpec); 147 | if (movieWidth > maximumWidth) { 148 | scaleH = (float) movieWidth / (float) maximumWidth; 149 | } 150 | } 151 | 152 | /* 153 | * calculate vertical scaling 154 | */ 155 | float scaleW = 1f; 156 | int measureModeHeight = MeasureSpec.getMode(heightMeasureSpec); 157 | 158 | if (measureModeHeight != MeasureSpec.UNSPECIFIED) { 159 | int maximumHeight = MeasureSpec.getSize(heightMeasureSpec); 160 | if (movieHeight > maximumHeight) { 161 | scaleW = (float) movieHeight / (float) maximumHeight; 162 | } 163 | } 164 | 165 | /* 166 | * calculate overall scale 167 | */ 168 | mScale = 1f / Math.max(scaleH, scaleW); 169 | 170 | mMeasuredMovieWidth = (int) (movieWidth * mScale); 171 | mMeasuredMovieHeight = (int) (movieHeight * mScale); 172 | 173 | setMeasuredDimension(mMeasuredMovieWidth, mMeasuredMovieHeight); 174 | 175 | } else { 176 | /* 177 | * No movie set, just set minimum available size. 178 | */ 179 | setMeasuredDimension(getSuggestedMinimumWidth(), getSuggestedMinimumHeight()); 180 | } 181 | } 182 | 183 | @Override 184 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 185 | super.onLayout(changed, l, t, r, b); 186 | 187 | /* 188 | * Calculate left / top for drawing in center 189 | */ 190 | mLeft = (getWidth() - mMeasuredMovieWidth) / 2f; 191 | mTop = (getHeight() - mMeasuredMovieHeight) / 2f; 192 | 193 | mVisible = getVisibility() == View.VISIBLE; 194 | } 195 | 196 | @Override 197 | protected void onDraw(Canvas canvas) { 198 | if (mMovie != null) { 199 | if (!mPaused) { 200 | updateAnimationTime(); 201 | drawMovieFrame(canvas); 202 | invalidateView(); 203 | } else { 204 | drawMovieFrame(canvas); 205 | } 206 | } 207 | } 208 | 209 | /** 210 | * Invalidates view only if it is visible. 211 | *
212 | * {@link #postInvalidateOnAnimation()} is used for Jelly Bean and higher. 213 | * 214 | */ 215 | @SuppressLint("NewApi") 216 | private void invalidateView() { 217 | if(mVisible) { 218 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 219 | postInvalidateOnAnimation(); 220 | } else { 221 | invalidate(); 222 | } 223 | } 224 | } 225 | 226 | /** 227 | * Calculate current animation time 228 | */ 229 | private void updateAnimationTime() { 230 | long now = android.os.SystemClock.uptimeMillis(); 231 | 232 | if (mMovieStart == 0) { 233 | mMovieStart = now; 234 | } 235 | 236 | int dur = mMovie.duration(); 237 | 238 | if (dur == 0) { 239 | dur = DEFAULT_MOVIEW_DURATION; 240 | } 241 | 242 | mCurrentAnimationTime = (int) ((now - mMovieStart) % dur); 243 | } 244 | 245 | /** 246 | * Draw current GIF frame 247 | */ 248 | private void drawMovieFrame(Canvas canvas) { 249 | 250 | mMovie.setTime(mCurrentAnimationTime); 251 | 252 | canvas.save(Canvas.MATRIX_SAVE_FLAG); 253 | canvas.scale(mScale, mScale); 254 | mMovie.draw(canvas, mLeft / mScale, mTop / mScale); 255 | canvas.restore(); 256 | } 257 | 258 | @SuppressLint("NewApi") 259 | @Override 260 | public void onScreenStateChanged(int screenState) { 261 | super.onScreenStateChanged(screenState); 262 | mVisible = screenState == SCREEN_STATE_ON; 263 | invalidateView(); 264 | } 265 | 266 | @SuppressLint("NewApi") 267 | @Override 268 | protected void onVisibilityChanged(View changedView, int visibility) { 269 | super.onVisibilityChanged(changedView, visibility); 270 | mVisible = visibility == View.VISIBLE; 271 | invalidateView(); 272 | } 273 | 274 | @Override 275 | protected void onWindowVisibilityChanged(int visibility) { 276 | super.onWindowVisibilityChanged(visibility); 277 | mVisible = visibility == View.VISIBLE; 278 | invalidateView(); 279 | } 280 | } 281 | --------------------------------------------------------------------------------