Android пропускает onDraw (), когда я запускаю свою анимацию в обратном порядке

У меня есть реализация раздвижных фрагментов DevByte . Кроме того, чтобы скопировать фрагмент в поле зрения, я хочу нарисовать тень над содержимым, которое оно закрывает. Я изменил FractionalLinearLayout из видео, чтобы measure себя в два раза больше ширины экрана и layout его дочерние элементы в правой половине. Во время цикла анимации я рисую черный прямоугольник увеличения альфа в левой половине и устанавливаю координату X на отрицательное значение, чтобы вывести правую половину в поле зрения.

Моя проблема заключается в том, что это отлично работает, когда я перемещаю содержимое в представлении, но не удается, когда я выворачиваю содержимое обратно из поля зрения. По пути я получаю именно то поведение, которое я хочу: перевод и затемняющая тень. По пути я получаю только перевод, и тень остается всего лишь его первым цветом кадра за всю анимацию.

Что я вижу в моем протоколировании, так это то, что по пути setPercentOnScreen() и onDraw() называются чередующимися, как и ожидалось. Однако на выходе я получаю один вызов setPercentageOnScreen() , за которым следует один вызов onDraw() , за которым следуют только вызовы setPercentOnScreen() . Android оптимизирует чертеж, но я не могу понять, почему.

Updated: Интересно, что я вижу это поведение только на эмуляторе под управлением Android 4.4. Эмуляторы, работающие с 4.0.3 и 4.3, запускают анимацию по назначению в обоих направлениях. Проблема со старым Nexus 7, другой эмулятор 4.4 – нет. Кажется, что он совместим с устройством, но отличается от устройства.

Обновлено: я извлек образец проекта и поместил его в GitHub: barend / android-slidefragment . Файл readme на GitHub содержит результаты тестирования на десятках устройств. Для эмуляторов проблема коррелирует с функцией Enable Host GPU, но только на Jelly Bean и KitKat; А не ICS.

Обновлено еще раз: дальнейшее тестирование показывает, что проблема возникает на физических устройствах, на которых запущен Jelly Bean и выше, а также на эмуляторах ARM с включенным «Использовать хост-GPU» с включенным Jelly Bean или выше. Это не происходит на эмуляторах x86 и на эмуляторах ARM без «Использовать хост-GPU», независимо от версии Android. Точную таблицу моих тестов можно найти в проекте github, приведенном выше.

 // Imports left out public class HorizontalSlidingLayout extends FrameLayout { /** * The fraction by which the content has slid into view. Legal range: from 0.0 (all content * off-screen) to 1.0 (all content visible). */ private float percentOnScreen; private int screenWidth, screenHeight, shift; private Paint shadowPaint; // Constructors left out, all three call super, then init(). private void init() { if (isInEditMode()) { // Ensure content is visible in edit mode. percentOnScreen = 1.0f; } else { setWillNotDraw(false); percentOnScreen = 0.0f; shadowPaint = new Paint(); shadowPaint.setAlpha(0x00); shadowPaint.setColor(0x000000); shadowPaint.setStyle(Paint.Style.FILL); } } /** Reports our own size as (2w, h) and measures all children at (w, h). */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { screenWidth = MeasureSpec.getSize(widthMeasureSpec); screenHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(2 * screenWidth, screenHeight); for (int i = 0, max = getChildCount(); i < max; i++) { View child = getChildAt(i); child.measure(widthMeasureSpec, heightMeasureSpec); } } /** Lays out the children in the right half of the view. */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { for (int i = 0, max = getChildCount(); i < max; i++) { View child = getChildAt(i); child.layout(screenWidth, top, right, bottom); } } /** * Draws a translucent shadow in the left half of the view, darkening by * {@code percentOnScreen}, then lets superclass draw children in the right half. */ @Override protected void onDraw(Canvas canvas) { // Maintain 30% translucency if (percentOnScreen < 0.7f) { shadowPaint.setAlpha((int) (percentOnScreen * 0xFF)); } android.util.Log.i("Slider", "onDraw(" + percentOnScreen + ") -> alpha(" + shadowPaint.getAlpha() + ')'); canvas.drawRect(shift, 0, screenWidth, screenHeight, shadowPaint); super.onDraw(canvas); } @SuppressWarnings("unused") public float getPercentOnScreen() { return percentOnScreen; } /** Repeatedly invoked by an Animator. */ @SuppressWarnings("unused") public void setPercentOnScreen(float fraction) { this.percentOnScreen = fraction; shift = (int)(fraction < 1.0 ? fraction * screenWidth : screenWidth); setX(-shift); android.util.Log.i("Slider", "setPOS(" + fraction + ") -> invalidate(" + shift + ',' + screenWidth + ')'); invalidate(shift, 0, screenWidth, screenHeight); //invalidate() // Makes no difference } } 

Странно, что это нарушает симметрию. Я делаю то же самое в обратном порядке, когда выдвигаюсь так же, как и при скольжении, но поведение отличается. Я, вероятно, не замечаю что-то глупое. Есть идеи?

Это ошибка в логике недействительности / перерисовки для аппаратно-ускоренных просмотров в Android. Я ожидал бы увидеть ту же ошибку в JB по умолчанию, но только на ICS, если вы выберете аппаратное ускорение (hw accel включен по умолчанию как JB).

Проблема заключается в том, что когда виды удаляются из иерархии, а затем анимируются (например, что происходит в транзакции фрагмента в вашем приложении или в LayoutTransition, когда удаленное представление исчезает, или в AlphaAnimation, которое уменьшает удаленное представление) , Они не участвуют в той же логике недействительности / перерисовки, что и обычные / родительские представления. Они перерисовываются правильно (таким образом, мы видим, что фрагмент выскальзывает), но они не перерисовываются , поэтому, если их содержимое фактически изменится в течение этого периода, они не будут повторно отображаться с этими изменениями. Эффект ошибки в вашем приложении заключается в том, что фрагмент отображается правильно, но тень не нарисована, потому что это требует, чтобы представление было перерисовано, чтобы получить эти изменения.

То, как вы нарушаете представление, является правильным, но ошибка означает, что недействительность не имеет никакого эффекта.

Ошибка не появилась раньше, потому что, я считаю, нередко исчезают взгляды, чтобы изменить их внешний вид, когда они анимируются (они обычно просто скользят или исчезают, что прекрасно работает).

Ошибка должна быть исправлена ​​в будущей версии (у меня уже есть исправление). Между тем, обходным путем для вашей конкретной ситуации является добавление недействительности родительского контейнера; Это заставит представление перерисовать и правильно отобразить тень:

 if (getParent() instanceof ViewGroup) { ((ViewGroup) getParent()).invalidate(); }