├── 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 |
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 |
--------------------------------------------------------------------------------