Как мы можем оптимизировать (cpu & ram) эту андроидную пользовательскую анимацию всплеска?

Я разработал пользовательскую анимацию для моей активности Splash Screen:

=> Вот анимация, которая показывает, что происходит:

Введите описание изображения здесь

Конечно, мое настоящее приложение:

  • С разными картинками (fullhd)
  • Немного медленнее по сравнению с GIF: 3s для 60 промежуточных экранов.

Мой дизайнер предоставил мне 60 png файлов.

=> Один пример для иллюстрации:

Введите описание изображения здесь

Моя цель:

  1. Начинайте с центрального логотипа (здесь с SO) с нижним изображением (Apple)
  2. Запустить анимацию морфинга
  3. На экране, как на домашней странице приложения

Чтобы запустить это, у меня есть один многослойный макет для SpashScreenActivity с:

  • BackGround (невидимый): макет homePage (MainActivity)
  • MiddleGround: с ImageView Apple, который заменен Droid и нижним баром, которые растут
  • FrontGround: логотип в ImageView с его слоганом в TextView

Вот xml-код для макета SpashScreen:

<?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 Код xml для верхней части макета (через 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 Вот код действия.

  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 И код ImageManager для понимания (я использую 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); } } 

ПОЛОЖЕНИЕ ДЕЛ:

Метод updateImageViewLight может, по-видимому, помочь другим справиться с таким поведением (PortedDuff …), которое было нелегко найти.

Анимация отлично работает на мощном устройстве, но часто отстает, если устройство или приложение делают что-то еще.

Я попытался запустить этот расчет в Async Task, но он был менее мощным, чем в mainThread

ВОПРОСОВ:

Я ищу любой образованный совет по моей реализации, который может помочь улучшить:

  • Потребление памяти
  • использование процессора

Но также :

  • Потенциальная утечка
  • Читаемость кода

Еще не ответил полностью, но это попытка, которую нужно продолжить и прокомментировать.

Первая оптимизация:

  • Создайте настраиваемый вид (здесь расширение ImageView ) для волны, которая будет обновляться по методу onDraw
  • Define const member как static final члены класса
  • Как и большинство из возможных, избегайте создания экземпляров объектов в onDraw ( Canvas and Paint ), а также ненужного обновления объекта

Вот код CustomView, который расширяет 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); } } 

Результаты:

  • Анимация затуманивается и, кажется, берет меньше ресурсов. Однако он полностью не синхронизирован с анимацией компоновки. Это нужно сделать. Любые лучшие практики?
  • Это, безусловно, стадия init метода onDraw которая занимает время в первом цикле
  • Работа над временным холстом для окончательного рисования холста на основном холсте