Я использую библиотеку Rebound для Facebook, чтобы воспроизвести надувные анимации, увиденные в их голосовой версии. Проблема в том, что большую часть времени анимация заикается. Несколько снимков объяснят это лучше. Вот маслянисто-гладкая анимация чатов:
И вот моя попытка (обратите внимание, как анимация для белого View
пропускает почти все кадры):
Время от времени он работает плавно:
Ниже приведен код, который я использую в настоящее время (весь проект работает на Github, если вы хотите быстро его настроить). Я предполагаю, что это связано с тем, что аппаратное ускорение не корректно включено в моем View
. В моей SpringSystem
есть 2 Spring
, одна для «пузыря» (значок Android) и другая для контента (белый View
который отображается при нажатии на пузырь). Любая помощь в решении этой проблемы будет очень признательна. Благодарю.
AndroidManifest.xml
:
<application android:hardwareAccelerated="true" ...> ... </application>
AppService.java
:
// the following code is in AppService#onCreate() // AppService extends android.app.Service // full code at https://github.com/vickychijwani/BubbleNote mContent.setLayerType(View.LAYER_TYPE_HARDWARE, null); final Spring bubbleSpring = system.createSpring(); bubbleSpring.setCurrentValue(1.0); bubbleSpring.addListener(new SpringListener() { @Override public void onSpringUpdate(Spring spring) { float value = (float) spring.getCurrentValue(); params.x = (int) (mPos[0] * value); params.y = (int) (mPos[1] * value); mWindowManager.updateViewLayout(mBubble, params); // fire the second animation when this one is about to end if (spring.isOvershooting() && contentSpring.isAtRest()) { contentSpring.setEndValue(1.0); } } // ... }); final Spring contentSpring = system.createSpring(); contentSpring.setCurrentValue(0.0); contentSpring.addListener(new SpringListener() { @Override public void onSpringUpdate(Spring spring) { // always prints false?! Log.d(TAG, "hardware acc = " + mContent.isHardwareAccelerated()); float value = (float) spring.getCurrentValue(); // clamping is required to prevent flicker float clampedValue = Math.min(Math.max(value, 0.0f), 1.0f); mContent.setScaleX(value); mContent.setScaleY(value); mContent.setAlpha(clampedValue); } // ... });
Я понял это, просмотрев исходный код рамки.
TL; DR : добавить WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
к флагам макета, когда вы вручную прикрепляете View
к Window
/ WindowManager
; Set android:hardwareAccelerated=true
в манифесте не будет работать.
Я вручную привязываю свой View
к WindowManager
(потому что мне нужно создать свой пользовательский интерфейс в Service
для эмуляции чат-головок) так:
// code at https://github.com/vickychijwani/BubbleNote/blob/eb708e3910a7279c5490f614a7150009b59bad0b/app/src/main/java/io/github/vickychijwani/bubblenote/BubbleNoteService.java#L54 mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); mBubble = (LinearLayout) inflater.inflate(R.layout.bubble, null, false); // ... final WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); // ... mWindowManager.addView(mBubble, params);
Пойдем копаем …
Я начал отладку в View#draw(...)
, затем поднял стек вызовов на ViewRootImpl#draw(boolean)
. Здесь я натолкнулся на этот фрагмент кода:
if (!dirty.isEmpty() || mIsAnimating) { if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. mIsAnimating = false; mHardwareYOffset = yoff; mResizeAlpha = resizeAlpha; mCurrentDirty.set(dirty); dirty.setEmpty(); attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty); } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface // for instance) so we should just bail out. Locking the surface with software // rendering at this point would lock it forever and prevent hardware renderer // from doing its job when it comes back. // Before we request a new frame we must however attempt to reinitiliaze the // hardware renderer if it's in requested state. This would happen after an // eglTerminate() for instance. if (attachInfo.mHardwareRenderer != null && !attachInfo.mHardwareRenderer.isEnabled() && attachInfo.mHardwareRenderer.isRequested()) { try { attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight, mHolder.getSurface()); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } mFullRedrawNeeded = true; scheduleTraversals(); return; } if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { return; } } }
В моем случае ViewRootImpl#drawSoftware()
, в котором используется средство визуализации программного обеспечения. Хм … это означает, что HardwareRenderer
имеет значение null
. Поэтому я пошел искать точку ViewRootImpl#enableHardwareAcceleration(WindowManager.LayoutParams)
HardwareRenderer
, которая находится в ViewRootImpl#enableHardwareAcceleration(WindowManager.LayoutParams)
:
// Try to enable hardware acceleration if requested final boolean hardwareAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; if (hardwareAccelerated) { // ... mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent); // ... }
Ага! Там наш преступник!
В этом случае Android не будет автоматически устанавливать FLAG_HARDWARE_ACCELERATED
для этого Window
, хотя я установил android:hardwareAccerelated=true
в манифесте. Поэтому исправление просто:
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); mBubble = (LinearLayout) inflater.inflate(R.layout.bubble, null, false); // ... final WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, // NOTE WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); // ... mWindowManager.addView(mBubble, params);
Хотя анимация все еще не так гладко, как Facebook. Интересно, почему … (прежде чем кто-нибудь спросит: нет, во время анимации нет обильных журналов, и да, я пробовал с выпуском)