Gone'd view 在 LayoutTransition.DISAPPEARING 动画师期间绘制在顶部
Gone'd view draws on top during LayoutTransition.DISAPPEARING animator
当我分别将可见性设置为 VISIBLE 和 GONE 时,我正在使用 LayoutTransition 淡入和淡出具有半透明背景的视图。标准的东西。我在那个过渡视图之上(在 XML 之后)有一个带有纯色背景的视图。我希望用户在整个过渡过程中看到纯色背景的顶视图保持不变,这与覆盖视图出现时运行的动画完全相反。
APPEARING
动画师按预期工作:用户可以在整个动画过程中看到顶视图。 DISAPPEARING
动画师没有按预期工作:叠加视图最终绘制在所有其他视图之上。
可能值得注意的是,即使您不设置自己的 LayoutTransition
而是依赖 XML 中的 android:animateLayoutChanges="true"
,也会发生这种情况;我添加了自己的以增加持续时间,从而更容易看到过渡。
对如何解决此问题有任何想法吗?我猜这很常见,而且我一定遗漏了一些明显的东西,因为这是默认行为。我已经尝试了一些事情,比如附加一个 AnimatorUpdateListener
来使每一帧的顶视图无效,设置我自己的 DISAPPEARING
ObjectAnimator
和一个更新侦听器,使每帧的顶视图无效,并替换具有 TextView
和其他视图类型的叠加视图以防万一 FrameLayout
以某种特殊方式表现。
如果我用常规 ObjectAnimator
替换过渡动画师,我会得到预期的行为,除了视图不是 GONE
并因此接受触摸事件和所有垃圾(这使得 "solution"站不住脚)。因此,我认为问题不仅仅是过渡视图具有关联的动画师。这似乎是 LayoutTransition
代码或调用 said.
的问题
主要活动:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View overlay = findViewById(R.id.overlay);
final LayoutTransition lt = new LayoutTransition();
lt.setDuration(LayoutTransition.APPEARING, 300);
lt.setStartDelay(LayoutTransition.APPEARING, 0);
lt.setDuration(LayoutTransition.DISAPPEARING, 1000);
lt.setStartDelay(LayoutTransition.DISAPPEARING, 0);
((ViewGroup) overlay.getParent()).setLayoutTransition(lt);
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (overlay.getVisibility() == View.VISIBLE) {
overlay.setVisibility(View.GONE);
} else {
overlay.setVisibility(View.VISIBLE);
}
overlay.postDelayed(this, 1500);
}
};
overlay.post(runnable);
}
}
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000"
android:text="THIS IS BEHIND THE OVERLAY AND THUS SHOULD TINT"
android:textColor="#ffffff"
/>
<FrameLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#7f00ff00"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_margin="64dp"
android:background="#ffffff"
android:gravity="center"
android:text="THIS VIEW IS IN FRONT OF THE OVERLAY AND THUS SHOULD NOT SUFFER TINTING"
android:textColor="#000000"
android:textSize="32sp"
/>
</RelativeLayout>
我的设备运行 API 22,我也将 targetSdkVersion
设置为 22。基本上我创建了一个全新的项目并修改了生成的 MainActivity
和 activity_main.xml
以几乎完全匹配这些粘贴的文件(为了简洁我只排除了 import
和 package
行).
我今天遇到了同样的问题,所以我查看了ViewGroup.java源代码。结果是消失的children总是在其他人身上画画。
这是 API 23 中 ViewGroup.dispatchDraw(Canvas)
的片段,我很确定它在 API 22 中几乎相同。
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
浏览量在 mDisappearingChildren
。
正如源代码所说,普通视图和瞬态视图先绘制,然后消失视图绘制。所以消失children总是吸取别人的教训。应用开发者无法更改顺序。
我的建议是不要用LayoutTransition
,自己写动画
编辑:
我发现了一个技巧,可以在其他视图之前绘制消失 children,反映需要。
您需要一个转储视图来使消失的 children 在 ViewGroup.drawChild(Canvas, View, long)
中替换它。
这里有一个例子。
public class TrickLayout extends FrameLayout {
private Field mDisappearingChildrenField;
private ArrayList<View> mSuperDisappearingChildren;
// The dump view to draw disappearing children
// Maybe you need more than one dump view
private View mDumpView;
private boolean mDoTrick;
public TrickLayout(Context context) {
super(context);
init(context);
}
public TrickLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TrickLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
try {
mDisappearingChildrenField = ViewGroup.class.getDeclaredField("mDisappearingChildren");
mDisappearingChildrenField.setAccessible(true);
} catch (NoSuchFieldException e) {
// Ignore
}
if (mDisappearingChildrenField != null) {
// You can add dump view in xml or somewhere else in code
mDumpView = new View(context);
addView(mDumpView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
}
@SuppressWarnings("unchecked")
private void getSuperDisappearingChildren() {
if (mDisappearingChildrenField == null || mSuperDisappearingChildren != null) {
return;
}
try {
mSuperDisappearingChildren = (ArrayList<View>) mDisappearingChildrenField.get(this);
} catch (IllegalAccessException e) {
// Ignore
}
}
private boolean iWantToDoTheTrick() {
// Do I need do the trick?
return true;
}
private boolean beforeDispatchDraw() {
getSuperDisappearingChildren();
if (mSuperDisappearingChildren == null ||
mSuperDisappearingChildren.size() <= 0 || getChildCount() <= 1) { // dump view included
return false;
}
return iWantToDoTheTrick();
}
private void afterDispatchDraw() {
// Clean up here
}
@Override
protected void dispatchDraw(Canvas canvas) {
mDoTrick = beforeDispatchDraw();
super.dispatchDraw(canvas);
if (mDoTrick) {
afterDispatchDraw();
mDoTrick = false;
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
ArrayList<View> disappearingChildren = mSuperDisappearingChildren;
if (mDoTrick) {
if (child == mDumpView) {
boolean more = false;
for (int i = disappearingChildren.size() - 1; i >= 0; i--) {
more |= super.drawChild(canvas, disappearingChildren.get(i), drawingTime);
}
return more;
} else if (disappearingChildren.contains(child)) {
// Skip
return false;
}
}
return super.drawChild(canvas, child, drawingTime);
}
}
当我分别将可见性设置为 VISIBLE 和 GONE 时,我正在使用 LayoutTransition 淡入和淡出具有半透明背景的视图。标准的东西。我在那个过渡视图之上(在 XML 之后)有一个带有纯色背景的视图。我希望用户在整个过渡过程中看到纯色背景的顶视图保持不变,这与覆盖视图出现时运行的动画完全相反。
APPEARING
动画师按预期工作:用户可以在整个动画过程中看到顶视图。 DISAPPEARING
动画师没有按预期工作:叠加视图最终绘制在所有其他视图之上。
可能值得注意的是,即使您不设置自己的 LayoutTransition
而是依赖 XML 中的 android:animateLayoutChanges="true"
,也会发生这种情况;我添加了自己的以增加持续时间,从而更容易看到过渡。
对如何解决此问题有任何想法吗?我猜这很常见,而且我一定遗漏了一些明显的东西,因为这是默认行为。我已经尝试了一些事情,比如附加一个 AnimatorUpdateListener
来使每一帧的顶视图无效,设置我自己的 DISAPPEARING
ObjectAnimator
和一个更新侦听器,使每帧的顶视图无效,并替换具有 TextView
和其他视图类型的叠加视图以防万一 FrameLayout
以某种特殊方式表现。
如果我用常规 ObjectAnimator
替换过渡动画师,我会得到预期的行为,除了视图不是 GONE
并因此接受触摸事件和所有垃圾(这使得 "solution"站不住脚)。因此,我认为问题不仅仅是过渡视图具有关联的动画师。这似乎是 LayoutTransition
代码或调用 said.
主要活动:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View overlay = findViewById(R.id.overlay);
final LayoutTransition lt = new LayoutTransition();
lt.setDuration(LayoutTransition.APPEARING, 300);
lt.setStartDelay(LayoutTransition.APPEARING, 0);
lt.setDuration(LayoutTransition.DISAPPEARING, 1000);
lt.setStartDelay(LayoutTransition.DISAPPEARING, 0);
((ViewGroup) overlay.getParent()).setLayoutTransition(lt);
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (overlay.getVisibility() == View.VISIBLE) {
overlay.setVisibility(View.GONE);
} else {
overlay.setVisibility(View.VISIBLE);
}
overlay.postDelayed(this, 1500);
}
};
overlay.post(runnable);
}
}
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000"
android:text="THIS IS BEHIND THE OVERLAY AND THUS SHOULD TINT"
android:textColor="#ffffff"
/>
<FrameLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#7f00ff00"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_margin="64dp"
android:background="#ffffff"
android:gravity="center"
android:text="THIS VIEW IS IN FRONT OF THE OVERLAY AND THUS SHOULD NOT SUFFER TINTING"
android:textColor="#000000"
android:textSize="32sp"
/>
</RelativeLayout>
我的设备运行 API 22,我也将 targetSdkVersion
设置为 22。基本上我创建了一个全新的项目并修改了生成的 MainActivity
和 activity_main.xml
以几乎完全匹配这些粘贴的文件(为了简洁我只排除了 import
和 package
行).
我今天遇到了同样的问题,所以我查看了ViewGroup.java源代码。结果是消失的children总是在其他人身上画画。
这是 API 23 中 ViewGroup.dispatchDraw(Canvas)
的片段,我很确定它在 API 22 中几乎相同。
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
浏览量在 mDisappearingChildren
。
正如源代码所说,普通视图和瞬态视图先绘制,然后消失视图绘制。所以消失children总是吸取别人的教训。应用开发者无法更改顺序。
我的建议是不要用LayoutTransition
,自己写动画
编辑:
我发现了一个技巧,可以在其他视图之前绘制消失 children,反映需要。
您需要一个转储视图来使消失的 children 在 ViewGroup.drawChild(Canvas, View, long)
中替换它。
这里有一个例子。
public class TrickLayout extends FrameLayout {
private Field mDisappearingChildrenField;
private ArrayList<View> mSuperDisappearingChildren;
// The dump view to draw disappearing children
// Maybe you need more than one dump view
private View mDumpView;
private boolean mDoTrick;
public TrickLayout(Context context) {
super(context);
init(context);
}
public TrickLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TrickLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
try {
mDisappearingChildrenField = ViewGroup.class.getDeclaredField("mDisappearingChildren");
mDisappearingChildrenField.setAccessible(true);
} catch (NoSuchFieldException e) {
// Ignore
}
if (mDisappearingChildrenField != null) {
// You can add dump view in xml or somewhere else in code
mDumpView = new View(context);
addView(mDumpView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
}
@SuppressWarnings("unchecked")
private void getSuperDisappearingChildren() {
if (mDisappearingChildrenField == null || mSuperDisappearingChildren != null) {
return;
}
try {
mSuperDisappearingChildren = (ArrayList<View>) mDisappearingChildrenField.get(this);
} catch (IllegalAccessException e) {
// Ignore
}
}
private boolean iWantToDoTheTrick() {
// Do I need do the trick?
return true;
}
private boolean beforeDispatchDraw() {
getSuperDisappearingChildren();
if (mSuperDisappearingChildren == null ||
mSuperDisappearingChildren.size() <= 0 || getChildCount() <= 1) { // dump view included
return false;
}
return iWantToDoTheTrick();
}
private void afterDispatchDraw() {
// Clean up here
}
@Override
protected void dispatchDraw(Canvas canvas) {
mDoTrick = beforeDispatchDraw();
super.dispatchDraw(canvas);
if (mDoTrick) {
afterDispatchDraw();
mDoTrick = false;
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
ArrayList<View> disappearingChildren = mSuperDisappearingChildren;
if (mDoTrick) {
if (child == mDumpView) {
boolean more = false;
for (int i = disappearingChildren.size() - 1; i >= 0; i--) {
more |= super.drawChild(canvas, disappearingChildren.get(i), drawingTime);
}
return more;
} else if (disappearingChildren.contains(child)) {
// Skip
return false;
}
}
return super.drawChild(canvas, child, drawingTime);
}
}