I developed a custom animation for my Splash Screen activity :
=> Here is an animation that show what is happening :
Of course my real app :
My designer provided me 60 png files.
=> One example to illustrate :
My target is:
To run this, I have one Multilayer layout for the SpashScreenActivity with:
Here is the xml code for the SpashScreen Layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
android:orientation="vertical">
<FrameLayout
android:id="@+id/fl_logo_top_marge_hidden"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:visibility="invisible"
android:background="@color/colorPrimary" />
<include
android:visibility="invisible"
android:id="@+id/l_logo_activate_hidden"
layout="@layout/part_logo_activate"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:visibility="invisible"
android:id="@+id/fl_logo_bottom_marge_hidden"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/colorPrimary" />
<fr.millezimsolutions.app.splashanimation.SquareAspectWidthBasedImageView
android:visibility="invisible"
android:id="@+id/iv_home_hidden"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:src="@drawable/p_log_android" />
<FrameLayout
android:visibility="invisible"
android:id="@+id/fl_bar_hidden"
android:layout_width="match_parent"
android:layout_height="@dimen/start_degustation_bar_height"
android:background="@color/colorAccent"
android:gravity="bottom" />
</LinearLayout>
<LinearLayout
android:id="@+id/fl_middle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:background="@color/colorPrimary"
android:gravity="bottom"
android:orientation="vertical">
<fr.millezimsolutions.app.splashanimation.FitXCropTopImageView
android:id="@+id/iv_slogan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorTransparent"
android:scaleType="fitStart"
android:src="@drawable/p_log_apple" />
<FrameLayout
android:id="@+id/fl_logo_bottom_bar"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/colorAccent"
android:gravity="bottom" />
</LinearLayout>
<LinearLayout
android:id="@+id/fl_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/fl_logo_top_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="40"
android:background="@color/colorPrimary" />
<FrameLayout
android:id="@+id/fl_logo_top_marge"
android:layout_width="match_parent"
android:layout_height="5dp"
android:background="@color/colorTransparent" />
<include
android:id="@+id/l_logo_activate"
layout="@layout/part_logo_activate"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:id="@+id/fl_logo_bottom_marge"
android:layout_width="match_parent"
android:layout_height="5dp"
android:background="@color/colorTransparent" />
<FrameLayout
android:id="@+id/fl_logo_bottom_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="40"
android:background="@color/colorTransparent" />
</LinearLayout>
</RelativeLayout>
2 The xml code for the top of the layout (via include)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorTransparent"
android:gravity="bottom"
android:orientation="vertical">
<FrameLayout
android:id="@+id/fl_home_marginTop"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/iv_millezimuLogo"
android:layout_width="wrap_content"
android:layout_height="64dp"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="@dimen/marge"
android:layout_marginRight="@dimen/marge"
android:src="@drawable/p_log_so" />
<TextView
android:id="@+id/tv_slogan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="@dimen/marge_small"
android:layout_marginTop="@dimen/marge_small_border"
android:gravity="center"
android:hint=""
android:text="Bonjour"
android:textColor="@color/colorAccent"
android:textSize="20sp" />
<ImageView
android:id="@+id/iv_sponsorLogo"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="@dimen/marge"
android:layout_marginRight="@dimen/marge"
android:src="@drawable/p_log_so"
android:visibility="gone" />
<TextView
android:id="@+id/tv_sponsorLogo"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="@dimen/marge"
android:layout_marginRight="@dimen/marge"
android:gravity="center"
android:textColor="@color/colorAccent"
android:textSize="20sp"
android:visibility="gone" />
<FrameLayout
android:id="@+id/fl_home_marginBottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
3 Here is the code of the Activity.
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class SplashScreenActivity extends AppCompatActivity {
// Splash screen timer
private static int SPLASH_TIME_OUT = 3000;
private int finalTopMarge, finalBottomMarge, topMargeDec, bottomMargeInc;
long currentTimeStamp;
private int[] mSplashAnimFrames = {R.drawable.p_wave_spashscreen_00, R.drawable.p_wave_spashscreen_01, R.drawable.p_wave_spashscreen_02, R.drawable.p_wave_spashscreen_03, R.drawable.p_wave_spashscreen_04, R.drawable.p_wave_spashscreen_05, R.drawable.p_wave_spashscreen_06, R.drawable.p_wave_spashscreen_07, R.drawable.p_wave_spashscreen_08, R.drawable.p_wave_spashscreen_09,
R.drawable.p_wave_spashscreen_10, R.drawable.p_wave_spashscreen_11, R.drawable.p_wave_spashscreen_12, R.drawable.p_wave_spashscreen_13, R.drawable.p_wave_spashscreen_14, R.drawable.p_wave_spashscreen_15, R.drawable.p_wave_spashscreen_16, R.drawable.p_wave_spashscreen_17, R.drawable.p_wave_spashscreen_18, R.drawable.p_wave_spashscreen_19,
R.drawable.p_wave_spashscreen_20, R.drawable.p_wave_spashscreen_21, R.drawable.p_wave_spashscreen_22, R.drawable.p_wave_spashscreen_23, R.drawable.p_wave_spashscreen_24, R.drawable.p_wave_spashscreen_25, R.drawable.p_wave_spashscreen_26, R.drawable.p_wave_spashscreen_27, R.drawable.p_wave_spashscreen_28, R.drawable.p_wave_spashscreen_29,
R.drawable.p_wave_spashscreen_30, R.drawable.p_wave_spashscreen_31, R.drawable.p_wave_spashscreen_32, R.drawable.p_wave_spashscreen_33, R.drawable.p_wave_spashscreen_34, R.drawable.p_wave_spashscreen_35, R.drawable.p_wave_spashscreen_36, R.drawable.p_wave_spashscreen_37, R.drawable.p_wave_spashscreen_38, R.drawable.p_wave_spashscreen_39,
R.drawable.p_wave_spashscreen_40, R.drawable.p_wave_spashscreen_41, R.drawable.p_wave_spashscreen_42, R.drawable.p_wave_spashscreen_43, R.drawable.p_wave_spashscreen_44, R.drawable.p_wave_spashscreen_45, R.drawable.p_wave_spashscreen_46, R.drawable.p_wave_spashscreen_47, R.drawable.p_wave_spashscreen_48, R.drawable.p_wave_spashscreen_49,
R.drawable.p_wave_spashscreen_50, R.drawable.p_wave_spashscreen_51, R.drawable.p_wave_spashscreen_52, R.drawable.p_wave_spashscreen_53, R.drawable.p_wave_spashscreen_54, R.drawable.p_wave_spashscreen_55, R.drawable.p_wave_spashscreen_56, R.drawable.p_wave_spashscreen_57, R.drawable.p_wave_spashscreen_58, R.drawable.p_wave_spashscreen_59};
private final int C_STOP = 120, C_MOVE = 40, C_BAR = 80;
private int bottomBarRatio;
private ImageView finalImageView;
private int targetWidth, targetHeight;
private Rect mImageViewRect;
private Paint paint;
private Bitmap original;
private Bitmap result;
private boolean setupOk = false;
private ImageView mImageView;
private Bitmap mask;
private FrameLayout ltm;
private FrameLayout lbm;
private FrameLayout lbb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash_screen);
// Indique que l'ecran est full Screen
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
ImageManager.create(this);
}
@Override
protected void onResume() {
super.onResume();
int delay = SPLASH_TIME_OUT / C_STOP;
bottomBarRatio = getResources().getDimensionPixelSize(R.dimen.bar_nav_height) / (C_STOP - C_BAR);
runCycle(0, delay);
}
private void logStamp() {
long oldTimeStamp = currentTimeStamp;
currentTimeStamp = System.currentTimeMillis();
long delay = currentTimeStamp - oldTimeStamp;
Log.v("TIMESTAMP", String.valueOf(delay));
}
public void runCycle(final int cycle, final int delay) {
if (BuildConfig.DEBUG)
logStamp();
Handler cyclic = new Handler();
cyclic.postDelayed(new Runnable() {
@Override
public void run() {
if (cycle >= C_STOP) {
closeActivity();
} else {
runCycle(cycle + 1, delay);
if (cycle >= C_MOVE) {
// Copy des hauteurs pour les marges
initFinalLogoMargeHeight();
// Decroissance du poid de layout superieur
MoveUpLogo();
// bouger la bar
if (cycle >= C_BAR) {
updateBottomBar(cycle - C_BAR);
}
findViewById(R.id.fl_front).requestLayout();
}
if (setupFinalView()) {
if ((cycle % 2) == 0)
updateImageViewLight(cycle / 2);
}
}
}
}, delay);
}
private boolean setupFinalView() {
if (!setupOk) {
finalImageView = (ImageView) findViewById(R.id.iv_home_hidden);
targetWidth = finalImageView.getWidth();
targetHeight = finalImageView.getHeight();
mImageViewRect = new Rect(0, 0, finalImageView.getWidth(), finalImageView.getHeight());
mImageView = (ImageView) findViewById(R.id.iv_slogan);
mImageView.setBackgroundResource(R.drawable.p_log_apple);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
ltm = (FrameLayout) findViewById(R.id.fl_logo_top_marge);
lbm = (FrameLayout) findViewById(R.id.fl_logo_bottom_marge);
lbb = ((FrameLayout) findViewById(R.id.fl_logo_bottom_bar));
if (targetWidth > 0 && targetHeight > 0) {
original = ImageManager.decodeSampledBitmapFromResource(getResources(), R.drawable.p_log_android, targetWidth, targetHeight);
result = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_4444);
setupOk = true;
}
}
return setupOk;
}
private void MoveUpLogo() {
ViewGroup.LayoutParams ltmp = ltm.getLayoutParams();
ltmp.height -= topMargeDec;
ViewGroup.LayoutParams lbmp = lbm.getLayoutParams();
lbmp.height += bottomMargeInc;
}
private void initFinalLogoMargeHeight() {
if (finalBottomMarge == 0) {
finalTopMarge = findViewById(R.id.fl_logo_top_marge_hidden).getHeight();
topMargeDec = (findViewById(R.id.fl_logo_top_marge).getHeight() - finalTopMarge) / C_BAR;
finalBottomMarge = findViewById(R.id.fl_logo_bottom_marge_hidden).getHeight() + findViewById(R.id.fl_bar_hidden).getHeight() + findViewById(R.id.iv_home_hidden).getHeight();
bottomMargeInc = (finalBottomMarge - findViewById(R.id.fl_logo_bottom_marge).getHeight()) / C_BAR;
}
}
private void updateBottomBar(int cycle) {
LinearLayout.LayoutParams lbbp = (LinearLayout.LayoutParams) lbb.getLayoutParams();
lbbp.height = cycle * bottomBarRatio;
lbb.setLayoutParams(lbbp);
}
private void closeActivity() {
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
Intent i = new Intent(SplashScreenActivity.this, MainActivity.class);
startActivity(i);
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
private int getNext(int index) {
if (index < (mSplashAnimFrames.length - 1))
index++;
else
index = mSplashAnimFrames.length - 1;
return mSplashAnimFrames[index];
}
public void updateImageViewLight(int index) {
mask = ImageManager.decodeSampledBitmapFromResource(getResources(), getNext(index), targetWidth, targetHeight);
Canvas mCanvas = new Canvas(result);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mCanvas.drawBitmap(original, null, mImageViewRect, null);
mCanvas.drawBitmap(mask, null, mImageViewRect, paint);
paint.setXfermode(null);
mImageView.setImageBitmap(result);
}
}
4 And the code of the ImageManager for understanding (I use UIL)
public class ImageManager {
private static Context context;
public static ImageLoader getImageLoader() {
return ImageLoader.getInstance();
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.outWidth = reqWidth;
options.outHeight = reqHeight;
options.inJustDecodeBounds = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return getResourceImageForCanvas(resId, new ImageSize(reqWidth, reqHeight));
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap getResourceImageForCanvas(int bitmapResourceId, ImageSize targetImageSize) {
DisplayImageOptions options = new DisplayImageOptions.Builder().bitmapConfig(Bitmap.Config.RGB_565).build();
return getImageLoader().loadImageSync("drawable://" + bitmapResourceId, targetImageSize, options);
//
}
public static void create(Context context) {
try {
ImageManager.context = context;
initImageLoader();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void initImageLoader() throws IOException {
// Create global configuration and initialize ImageLoader with this
// configuration
BitmapFactory.Options opt = new BitmapFactory.Options();
// opt.inScaled = false;
opt.inSampleSize = 1;
opt.inDither = true;
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPreferQualityOverSpeed = false;
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()./* cacheInMemory(true). */cacheOnDisk(true).decodingOptions(opt).imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
.bitmapConfig(Bitmap.Config.RGB_565).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context).defaultDisplayImageOptions(defaultOptions).memoryCacheSizePercentage(13).writeDebugLogs().build();
ImageLoader.getInstance().init(config);
}
}
STATUS:
The updateImageViewLight method may provably help other to deal with this kind of behavior (PortedDuff...) that wasn't easy to find.
The animation works fine on a powerful device, but often lag if the device or the app does something else.
I have tried to run this calculation in Async Task but it was less powerful than in the mainThread
QUESTIONS:
I'm looking for any educated advise on my implementation, which may help to improve :
But also :
Not yet fully answer, but it is a try, that need to be continued and commented.
First optimisation:
ImageView
extension) for the wave, that will refresh on onDraw
method static final
class membersonDraw
(Canvas
and Paint
) as well as unnecessary object update Here is the code of the CustomView that extends ImageView
public class WaveFillingImageView extends ImageView {
private final static int[] mSplashAnimFrames = {R.drawable.p_wave_spashscreen_00, R.drawable.p_wave_spashscreen_01, R.drawable.p_wave_spashscreen_02, R.drawable.p_wave_spashscreen_03, R.drawable.p_wave_spashscreen_04, R.drawable.p_wave_spashscreen_05, R.drawable.p_wave_spashscreen_06, R.drawable.p_wave_spashscreen_07, R.drawable.p_wave_spashscreen_08, R.drawable.p_wave_spashscreen_09,
R.drawable.p_wave_spashscreen_10, R.drawable.p_wave_spashscreen_11, R.drawable.p_wave_spashscreen_12, R.drawable.p_wave_spashscreen_13, R.drawable.p_wave_spashscreen_14, R.drawable.p_wave_spashscreen_15, R.drawable.p_wave_spashscreen_16, R.drawable.p_wave_spashscreen_17, R.drawable.p_wave_spashscreen_18, R.drawable.p_wave_spashscreen_19,
R.drawable.p_wave_spashscreen_20, R.drawable.p_wave_spashscreen_21, R.drawable.p_wave_spashscreen_22, R.drawable.p_wave_spashscreen_23, R.drawable.p_wave_spashscreen_24, R.drawable.p_wave_spashscreen_25, R.drawable.p_wave_spashscreen_26, R.drawable.p_wave_spashscreen_27, R.drawable.p_wave_spashscreen_28, R.drawable.p_wave_spashscreen_29,
R.drawable.p_wave_spashscreen_30, R.drawable.p_wave_spashscreen_31, R.drawable.p_wave_spashscreen_32, R.drawable.p_wave_spashscreen_33, R.drawable.p_wave_spashscreen_34, R.drawable.p_wave_spashscreen_35, R.drawable.p_wave_spashscreen_36, R.drawable.p_wave_spashscreen_37, R.drawable.p_wave_spashscreen_38, R.drawable.p_wave_spashscreen_39,
R.drawable.p_wave_spashscreen_40, R.drawable.p_wave_spashscreen_41, R.drawable.p_wave_spashscreen_42, R.drawable.p_wave_spashscreen_43, R.drawable.p_wave_spashscreen_44, R.drawable.p_wave_spashscreen_45, R.drawable.p_wave_spashscreen_46, R.drawable.p_wave_spashscreen_47, R.drawable.p_wave_spashscreen_48, R.drawable.p_wave_spashscreen_49,
R.drawable.p_wave_spashscreen_50, R.drawable.p_wave_spashscreen_51, R.drawable.p_wave_spashscreen_52, R.drawable.p_wave_spashscreen_53, R.drawable.p_wave_spashscreen_54, R.drawable.p_wave_spashscreen_55, R.drawable.p_wave_spashscreen_56, R.drawable.p_wave_spashscreen_57, R.drawable.p_wave_spashscreen_58, R.drawable.p_wave_spashscreen_59};
private Paint paint;
private long nextDrawTimeStamp;
private boolean init = false, isStarted = false;
private Bitmap original, result;
private Rect mImageViewRect;
private int index = 0;
private LogAndStat las;
private Canvas mCanvas;
private final int timeTick = 50;
public WaveFillingImageView(Context context) {
super(context);
init(context);
}
public WaveFillingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public WaveFillingImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@DebugLog
private void init(Context context) {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
las = new LogAndStat("View", mSplashAnimFrames.length);
}
public void start() {
isStarted = true;
nextDrawTimeStamp = System.currentTimeMillis();
invalidate();
}
@DebugLog
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Before Layout
if (getWidth() == 0 || getHeight() == 0)
return;
// Init variables
if (!init) {
las.logStamp("init onDraw");
original = ImageManager.decodeSampledBitmapFromResourcewithUIL(getResources(), R.drawable.p_log_android, getWidth(), getHeight());
result = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_4444);
mCanvas = new Canvas(result);
mImageViewRect = new Rect(0, 0, getWidth(), getHeight());
init = true;
las.logStamp("init onDraw");
}
// If tick reached for refresh
if (System.currentTimeMillis() >= nextDrawTimeStamp) {
nextDrawTimeStamp += timeTick;
las.logStamp(index);
Bitmap mask = ImageManager.decodeSampledBitmapFromResourcewithUIL(getResources(), getResourceForNextCycle(index), mImageViewRect.width(), mImageViewRect.height());
mCanvas.drawBitmap(original, null, mImageViewRect, null);
mCanvas.drawBitmap(mask, null, mImageViewRect, paint);
canvas.drawBitmap(result, 0, 0, null);
index++;
}
if (isStarted)
// Invalidate during animation to call again on Draw
if (index < mSplashAnimFrames.length) {
las.logStamp("invalidate");
invalidate();
} else {
las.logStats();
}
}
@DebugLog
private int getResourceForNextCycle(int index) {
if (index < (mSplashAnimFrames.length - 1))
index++;
else
index = mSplashAnimFrames.length - 1;
return mSplashAnimFrames[index];
}
@DebugLog
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}
Results:
onDraw
method that take time at the first cycleIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With