Android PanningView with Fadein and Fadeout Animation
This post about PanningView with fade in and fade out the images.
See the below video you can get more idea
Screen Shot
MainActivity.java
PanningViewAttacher.java
activity_main.xml
Download SourceCode
This post about PanningView with fade in and fade out the images.
See the below video you can get more idea
Screen Shot
MainActivity.java
package com.iamvijayakumar.panningviewwith;
import
android.annotation.SuppressLint;
import
android.app.Activity;
import
android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
/**
*
*
*
* @author iamvijayakumar
*
*
*/
@SuppressLint("NewApi")
public class MainActivity extends Activity {
PanningView mPanningView;
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPanningView = (PanningView)
findViewById(R.id.panningView);
AnimationDrawable animDrawable = new AnimationDrawable();
animDrawable.addFrame(getResources().getDrawable(R.drawable.images1),
10000);
animDrawable.addFrame(getResources().getDrawable(R.drawable.images2),
10000);
// For Repeat Animation Make it
false
animDrawable.setOneShot(false);
animDrawable.setEnterFadeDuration(2000);
animDrawable.setExitFadeDuration(2000);
mPanningView.setImageDrawable(animDrawable);
}
@Override
protected void onResume() {
super.onResume();
if (mPanningView != null) {
mPanningView.startPanning();
}
}
}
PanningView.java
package com.iamvijayakumar.panningviewwith;
import
android.content.Context;
import
android.content.res.TypedArray;
import
android.graphics.drawable.Drawable;
import android.net.Uri;
import
android.util.AttributeSet;
import
android.widget.ImageView;
public class PanningView extends ImageView {
private final PanningViewAttacher
mAttacher;
private int mPanningDurationInMs;
public PanningView(Context context) {
this(context, null);
}
public PanningView(Context context,
AttributeSet attr) {
this(context, attr, 0);
}
public PanningView(Context context,
AttributeSet attr, int defStyle) {
super(context, attr,
defStyle);
readStyleParameters(context,
attr);
super.setScaleType(ScaleType.MATRIX);
mAttacher = new PanningViewAttacher(this, mPanningDurationInMs);
}
/**
*
* @param context
*
* @param attributeSet
*/
private void
readStyleParameters(Context context, AttributeSet attributeSet) {
TypedArray a =
context.obtainStyledAttributes(attributeSet,
R.styleable.PanningView);
try {
mPanningDurationInMs = a.getInt(
R.styleable.PanningView_panningDurationInMs,
PanningViewAttacher.DEFAULT_PANNING_DURATION_IN_MS);
} finally {
a.recycle();
}
}
@Override
// setImageBitmap calls through to this
method
public void
setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
stopUpdateStartIfNecessary();
}
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
stopUpdateStartIfNecessary();
}
@Override
public void setImageURI(Uri uri)
{
super.setImageURI(uri);
stopUpdateStartIfNecessary();
}
private void
stopUpdateStartIfNecessary() {
if (null != mAttacher) {
boolean wasPanning = mAttacher.isPanning();
mAttacher.stopPanning();
mAttacher.update();
if (wasPanning) {
mAttacher.startPanning();
}
}
}
@Override
public void
setScaleType(ScaleType scaleType) {
throw new
UnsupportedOperationException(
"only matrix scaleType is
supported");
}
@Override
protected void
onDetachedFromWindow() {
mAttacher.cleanup();
super.onDetachedFromWindow();
}
public void startPanning() {
mAttacher.startPanning();
}
public void stopPanning() {
mAttacher.stopPanning();
}
}
package com.iamvijayakumar.panningviewwith;
import
android.content.res.Configuration;
import
android.graphics.Matrix;
import
android.graphics.RectF;
import android.util.Log;
import
android.view.ViewTreeObserver;
import
android.view.animation.LinearInterpolator;
import
android.widget.ImageView;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ValueAnimator;
import java.lang.ref.WeakReference;
public class PanningViewAttacher
implements
ViewTreeObserver.OnGlobalLayoutListener
{
public static final int DEFAULT_PANNING_DURATION_IN_MS = 500;
private static final String TAG = "PanningViewAttacher";
private enum Way {
R2L, L2R, T2B, B2T
};
private WeakReference<ImageView> mImageView;
private int mIvTop, mIvRight, mIvBottom, mIvLeft;
private ViewTreeObserver mViewTreeObserver;
private Matrix mMatrix;
private RectF mDisplayRect = new RectF();
private ValueAnimator mCurrentAnimator;
private LinearInterpolator mLinearInterpolator;
private boolean mIsPortrait;
private long mDuration;
private long mCurrentPlayTime;
private long mTotalTime;
private Way mWay;
private boolean mIsPanning;
public PanningViewAttacher(ImageView
imageView, long duration) {
if (imageView == null) {
throw new
IllegalArgumentException("imageView must not be null");
}
if (!hasDrawable(imageView)) {
throw new
IllegalArgumentException("drawable must not be null");
}
mLinearInterpolator = new
LinearInterpolator();
mDuration = duration;
mImageView = new
WeakReference<ImageView>(imageView);
mViewTreeObserver =
imageView.getViewTreeObserver();
mViewTreeObserver.addOnGlobalLayoutListener(this);
setImageViewScaleTypeMatrix(imageView);
mMatrix =
imageView.getImageMatrix();
if (mMatrix == null) {
mMatrix = new Matrix();
}
mIsPortrait =
imageView.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
update();
}
/**
*
*/
public void update() {
mWay = null;
mTotalTime = 0;
mCurrentPlayTime = 0;
getImageView().post(new Runnable() {
@Override
public void run() {
scale();
refreshDisplayRect();
}
});
}
public boolean isPanning() {
return mIsPanning;
}
/**
*
*
scale and start to pan the image background
*/
public void startPanning() {
if (mIsPanning) {
return;
}
mIsPanning = true;
final Runnable
panningRunnable = new Runnable() {
@Override
public void run() {
animate_();
}
};
getImageView().post(panningRunnable);
}
/**
*
*
stop current panning
*/
public void stopPanning() {
if (!mIsPanning) {
return;
}
mIsPanning = false;
Log.d(TAG, "panning
animation stopped by user");
if (mCurrentAnimator != null) {
mCurrentAnimator.removeAllListeners();
mCurrentAnimator.cancel();
mCurrentAnimator = null;
}
mTotalTime += mCurrentPlayTime;
Log.d(TAG, "mTotalTime :
" +
mTotalTime);
}
/**
*
*
Clean-up the resources
attached to this object. This needs to be called
*
*
when the ImageView is no longer used. A good example is from
*
* {@link
android.view.View#onDetachedFromWindow()} or from
*
* {@link
android.app.Activity#onDestroy()}. This is automatically called if
*
*
you are using {@link com.fourmob.panningview.PanningView}.
*/
public final void cleanup() {
if (null != mImageView) {
getImageView().getViewTreeObserver().removeGlobalOnLayoutListener(
this);
}
mViewTreeObserver = null;
stopPanning();
// Finally, clear ImageView
mImageView = null;
}
public final ImageView
getImageView() {
ImageView imageView = null;
if (null != mImageView) {
imageView = mImageView.get();
}
// If we don't have an ImageView,
call cleanup()
if (null == imageView) {
cleanup();
throw new
IllegalStateException(
"ImageView no
longer exists. You should not use this PanningViewAttacher any more.");
}
return imageView;
}
private int
getDrawableIntrinsicHeight() {
return
getImageView().getDrawable().getIntrinsicHeight();
}
private int
getDrawableIntrinsicWidth() {
return
getImageView().getDrawable().getIntrinsicWidth();
}
private int getImageViewWidth()
{
return
getImageView().getWidth();
}
private int getImageViewHeight()
{
return
getImageView().getHeight();
}
/**
*
*
Set's the ImageView's ScaleType to Matrix.
*/
private static void
setImageViewScaleTypeMatrix(ImageView imageView) {
if (null != imageView
&& !(imageView instanceof PanningView)) {
imageView.setScaleType(ImageView.ScaleType.MATRIX);
}
}
/**
*
* @return true if the
ImageView exists, and it's Drawable exists
*/
private static boolean
hasDrawable(ImageView imageView) {
return null != imageView
&& null != imageView.getDrawable();
}
@Override
public void onGlobalLayout() {
ImageView imageView =
getImageView();
if (null != imageView) {
final int top =
imageView.getTop();
final int right =
imageView.getRight();
final int bottom =
imageView.getBottom();
final int left =
imageView.getLeft();
/**
*
* We need to check whether the ImageView's
bounds have changed.
*
* This would be easier if we targeted API 11+
as we could just use
*
* View.OnLayoutChangeListener. Instead we have
to replicate the
*
* work, keeping track of the ImageView's
bounds and then checking
*
* if the values change.
*/
if (top != mIvTop || bottom != mIvBottom || left != mIvLeft
|| right != mIvRight) {
update();
// Update values as
something has changed
mIvTop = top;
mIvRight = right;
mIvBottom = bottom;
mIvLeft = left;
}
}
}
private void animate_() {
refreshDisplayRect();
if (mWay == null) {
mWay = mIsPortrait ? Way.R2L : Way.B2T;
}
Log.d(TAG, "mWay : " + mWay);
Log.d(TAG, "mDisplayRect
: "
+ mDisplayRect);
long remainingDuration = mDuration - mTotalTime;
if (mIsPortrait) {
if (mWay == Way.R2L) {
animate(mDisplayRect.left, mDisplayRect.left
- (mDisplayRect.right -
getImageViewWidth()),
remainingDuration);
} else {
animate(mDisplayRect.left, 0.0f,
remainingDuration);
}
} else {
if (mWay == Way.B2T) {
animate(mDisplayRect.top, mDisplayRect.top
- (mDisplayRect.bottom -
getImageViewHeight()),
remainingDuration);
} else {
animate(mDisplayRect.top, 0.0f,
remainingDuration);
}
}
}
private void changeWay() {
if (mWay == Way.R2L) {
mWay = Way.L2R;
} else if (mWay == Way.L2R) {
mWay = Way.R2L;
} else if (mWay == Way.T2B) {
mWay = Way.B2T;
} else if (mWay == Way.B2T) {
mWay = Way.T2B;
}
mCurrentPlayTime = 0;
mTotalTime = 0;
}
private void animate(float start, float end, long duration) {
Log.d(TAG, "startPanning
: "
+ start + "
to "
+ end + ",
in "
+ duration + "ms");
mCurrentAnimator = ValueAnimator.ofFloat(start,
end);
mCurrentAnimator
.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator
animation) {
float value = (Float)
animation.getAnimatedValue();
mMatrix.reset();
applyScaleOnMatrix();
if (mIsPortrait) {
mMatrix.postTranslate(value,
0);
} else {
mMatrix.postTranslate(0,
value);
}
refreshDisplayRect();
mCurrentPlayTime =
animation.getCurrentPlayTime();
setCurrentImageMatrix();
}
});
mCurrentAnimator.addListener(new AnimatorListenerAdapter()
{
@Override
public void onAnimationEnd(Animator
animation) {
Log.d(TAG,
"animation has
finished, startPanning in the other way");
changeWay();
animate_();
}
@Override
public void onAnimationCancel(Animator
animation) {
Log.d(TAG, "panning
animation canceled");
}
});
mCurrentAnimator.setDuration(duration);
mCurrentAnimator.setInterpolator(mLinearInterpolator);
mCurrentAnimator.start();
}
private void
setCurrentImageMatrix() {
getImageView().setImageMatrix(mMatrix);
getImageView().invalidate();
getImageView().requestLayout();
}
private void refreshDisplayRect()
{
mDisplayRect.set(0, 0,
getDrawableIntrinsicWidth(),
getDrawableIntrinsicHeight());
mMatrix.mapRect(mDisplayRect);
}
private void scale() {
mMatrix.reset();
applyScaleOnMatrix();
setCurrentImageMatrix();
}
private void applyScaleOnMatrix()
{
int drawableSize = mIsPortrait ?
getDrawableIntrinsicHeight()
: getDrawableIntrinsicWidth();
int imageViewSize = mIsPortrait ?
getImageViewHeight()
: getImageViewWidth();
float scaleFactor = (float) imageViewSize / (float) drawableSize;
mMatrix.postScale(scaleFactor,
scaleFactor);
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}"
>
<com.iamvijayakumar.panningviewwith.PanningView
android:id="@+id/panningView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/images1"
custom:panningDurationInMs="10000"
/>
</RelativeLayout>
AndroidManifest.xml
<?xml version="1.0"
encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.iamvijayakumar.panningviewwith"
android:versionCode="1"
android:versionName="1.0"
>
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21"
/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"
/>
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
</application>
</manifest>
Download SourceCode