使用先前启动的 Activity 转换恢复 Activity 时查看可见性状态丢失
Posted
技术标签:
【中文标题】使用先前启动的 Activity 转换恢复 Activity 时查看可见性状态丢失【英文标题】:View visibility state loss when resuming activity with previously started Activity transition 【发布时间】:2018-11-30 20:04:57 【问题描述】:解释:
我启动一个 Activity B,并从 Activity A 进行 Activity 转换。
在启动新的 Activity B 后,我更改了 B 中某些视图 (View.GONE
) 的可见性状态。
问题是:
当启动一个新的 Activity C 并返回到 Activity B(或强制 B 中的 onPause)时,可见性状态发生变化的视图再次出现,没有任何代码或其他方式接触视图。
下面的视频用图片解释了这个问题:https://youtu.be/oqCZo5CSkQk
当不使用过渡时,一切都按预期工作。有没有人知道,如何在恢复活动时防止视图状态丢失?我用ActivityOptionsCompat
错了吗?
我使用支持库:
'com.android.support:support-v4:27.1.1'
和
'com.android.support:appcompat-v7:27.1.1'
但旧版本和不同手机制造商(Pixel、三星等)也会出现此问题。
这里是重现问题的代码:
布局
活动 A:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_
android:layout_
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.AppCompat.Title"
android:layout_
android:layout_
android:layout_margin="24dp"
android:gravity="center"
android:text="Activity A | this starts the transition to another activity"/>
<android.support.v7.widget.AppCompatImageView
android:id="@+id/imageToAnimate"
android:layout_
android:layout_
app:srcCompat="@android:drawable/star_big_on"/>
<Button
android:id="@+id/start_next_activity"
android:layout_
android:layout_
android:layout_marginTop="30dp"
android:text="start Another Activity"/>
</LinearLayout>
活动 B:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_
android:layout_
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
style="@style/TextAppearance.AppCompat.Title"
android:layout_
android:layout_
android:layout_margin="24dp"
android:gravity="center"
android:text="Activity B | with progressbar"/>
<android.support.v7.widget.AppCompatImageView
android:id="@+id/imageToAnimate"
android:layout_
android:layout_
android:transitionName="toAnimate"
app:srcCompat="@android:drawable/star_big_on"/>
<ProgressBar
android:id="@+id/progress"
android:layout_
android:layout_
android:layout_marginTop="24dp"
android:visibility="gone"/>
<TextView
android:id="@+id/dismiss_text"
style="@style/TextAppearance.AppCompat.Subhead"
android:layout_
android:layout_
android:layout_marginBottom="24dp"
android:text="Also a text to dismiss"/>
<Button
android:id="@+id/show"
android:layout_
android:layout_
android:text="SHOW"/>
<Button
android:id="@+id/hide_gone"
android:layout_
android:layout_
android:text="SET GONE"/>
<Button
android:id="@+id/hide_invisible"
android:layout_
android:layout_
android:text="SET INVISIBLE"/>
<Button
android:id="@+id/start_activity_c"
android:layout_
android:layout_
android:text="START activity"/>
</LinearLayout>
活动 C:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:gravity="center"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.AppCompat.Title"
android:layout_
android:layout_
android:text="Activity C"
android:textSize="40sp"/>
</LinearLayout>
活动源代码
活动 A:
public class DebugActivityA extends AppCompatActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
findViewById(R.id.start_next_activity).setOnClickListener(v -> startWithTransition());
private void startWithTransition()
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(DebugActivityA.this,
findViewById(R.id.imageToAnimate),
"toAnimate");
startActivity(new Intent(DebugActivityA.this, DebugActivityB.class), options.toBundle());
活动 B:
public class DebugActivityB extends AppCompatActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
View progressbar = findViewById(R.id.progress);
View dismissText = findViewById(R.id.dismiss_text);
progressbar.setVisibility(View.VISIBLE);
findViewById(R.id.show).setOnClickListener(v ->
progressbar.setVisibility(View.VISIBLE);
dismissText.setVisibility(View.VISIBLE);
);
findViewById(R.id.hide_gone).setOnClickListener(v ->
progressbar.setVisibility(View.GONE);
dismissText.setVisibility(View.GONE);
);
findViewById(R.id.hide_invisible).setOnClickListener(v ->
progressbar.setVisibility(View.INVISIBLE);
dismissText.setVisibility(View.INVISIBLE);
);
findViewById(R.id.start_activity_c).setOnClickListener(this::startOtherActivity);
private void startOtherActivity(View view)
startActivity(new Intent(this, DebugActivityC.class));
活动 C:
public class DebugActivityC extends AppCompatActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c);
【问题讨论】:
我认为您在 Android 中发现了一个错误。似乎 Android 正在强制他们保存(在幕后)的布局状态,以便共享元素转换工作。而当你的 Activity B 重新启动时,他们并不关心 real 布局状态,他们强制执行之前保存的内容,因此 Activity B 的退出动画将起作用。在列表中,这就是它的样子...... :) 你确定onDestroy
没有被调用吗?在某些情况下可以。
你也可以分享你的清单文件吗?
@Kenny Seyffarth 你有机会看看我在回答帖子中提到的观察吗?
我认为您应该在 Activity B 中使用 Start the Activity startActivityForResult (Intent intent, int requestCode)
和 Receive the Result onActivityResult(int requestCode, int resultCode, Intent data)
【参考方案1】:
您可以在离开活动时使用onSaveInstanceState
方法保存UI 的状态,然后使用onRestoreInstanceState
将其恢复到该状态。
将onSaveInstanceState
添加到活动B:
@Override
public void onSaveInstanceState(Bundle savedInstanceState)
super.onSaveInstanceState(savedInstanceState);
// Save UI state changes to the savedInstanceState.
// This bundle will be passed to onCreate if the process is
// killed and restarted.
savedInstanceState.putInt("progressbarVisibility", progressbar.getVisibility());
savedInstanceState.putInt("dismissTextVisibility", dismissText.getVisibility());
然后添加Activity B的修改onCreate
方法:
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi_play);
View progressbar = findViewById(R.id.progress);
View dismissText = findViewById(R.id.dismiss_text);
if (savedInstanceState != null)
dismissText.setVisibility(savedInstanceState.getInt("dismissTextVisibility", View.VISIBLE));
progressbar.setVisibility(savedInstanceState.getInt("progressbarVisibility", View.VISIBLE));
// The rest of your code.
【讨论】:
我不会重新创建我的活动。我简单地恢复活动(回到活动堆栈或在任务管理器中选择应用程序后恢复它)。所以使用 savedInstanceState 的 onCreate 不会被调用。 @KennySeyffarth 你找到解决办法了吗? 我对此完全没有解决方案,我向谷歌提交了一个错误票,但他们may not be able to handle this bug
因为更高优先级的票。 issuetracker.google.com/issues/112158868 无论如何,对我来说,解决方案是更改为带有片段的单个活动概念,所以这里不会再出现这个问题了。【参考方案2】:
可能有办法解决这个问题。
与其更改 onCreate
中的视图可见性(这只会在新创建 Activity
时调用),不如尝试根据 Activity B
本身中的标志来操纵 onResume
中视图的可见性。
要么使用
SharedPreference
,甚至基于简单的布尔标志
现在,当Activity B
恢复可见性时,将根据来自onResume
的这些标志本身重新应用相同的状态。
如果使用SharedPreference
,则在推送新Activity C
时更新新值。
在 onResume
中,检查值并更改目标视图。
一旦Activity B
从后台堆栈中删除,即清除标志(onDestroy
of Activity B
)
【讨论】:
【参考方案3】:这可能是也可能不是共享转换库问题,我想出了 使用以下方法来解决可见性问题。差不多用了一天!!
让我在这里分享我的观察。
如果您更改共享过渡视图的可见性状态,则不会存在此类问题。
如果您有这样的需求,希望在使用共享过渡时更改某些视图的可见性,请将它们设为共享视图。
ViewCompat.setTransitionName(<view>, <uniqueString>);
特此分享修改后的文件,我已经验证并解决了我们的问题。
活动A
public class ActivityA extends AppCompatActivity
private View startNextActivity;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
startNextActivity = findViewById(R.id.start_next_activity);
startNextActivity.setOnClickListener(v -> startWithTransition());
private void startWithTransition()
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(this,
new Pair<View, String>(startNextActivity, ActivityB.VIEW_NAME_IMAGEVIEW)
new Pair<View, String>(startNextActivity, ActivityB.VIEW_NAME_PROGRESSBAR),
new Pair<View, String>(startNextActivity, ActivityB.VIEW_NAME_DETAIL_TEXTVIEW));
startActivity(new Intent(ActivityA.this, ActivityB.class), options.toBundle());
活动B
public class ActivityB extends AppCompatActivity
public static final String VIEW_NAME_PROGRESSBAR = "progressbar";
public static final String VIEW_NAME_IMAGEVIEW = "toAnimate";
public static final String VIEW_NAME_DETAIL_TEXTVIEW = "detailTextView";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
View progressbar = findViewById(R.id.progress);
View dismissText = findViewById(R.id.dismiss_text);
View animateView = findViewById(R.id.imageToAnimate);
// Key lines to fix our issue
ViewCompat.setTransitionName(animateView, VIEW_NAME_IMAGEVIEW);
ViewCompat.setTransitionName(progressbar, VIEW_NAME_PROGRESSBAR);
ViewCompat.setTransitionName(dismissText, VIEW_NAME_DETAIL_TEXTVIEW);
progressbar.setVisibility(View.VISIBLE);
findViewById(R.id.show).setOnClickListener(v ->
progressbar.setVisibility(View.VISIBLE);
dismissText.setVisibility(View.VISIBLE);
);
findViewById(R.id.hide_gone).setOnClickListener(v ->
progressbar.setVisibility(View.GONE);
dismissText.setVisibility(View.GONE);
);
findViewById(R.id.hide_invisible).setOnClickListener(v ->
progressbar.setVisibility(View.INVISIBLE);
dismissText.setVisibility(View.INVISIBLE);
);
findViewById(R.id.start_activity_c).setOnClickListener(this::startOtherActivity);
private void startOtherActivity(View view)
startActivity(new Intent(this, ActivityC.class));
希望对你有所帮助。
祝你有美好的一天!!
【讨论】:
【参考方案4】:是的.. 现在是您可以使用 onSaveInstanceState 和 onRestoreInstanceState 的时候了。
您可以在 onSaveInstanceState state 中保存您的可见性状态并恢复 onRestoreInstanceState。 Activity 总是调用 onSaveInstanceState 和 onRestoreInstanceState 来完美运行。
编码愉快!!!
【讨论】:
【参考方案5】:当您启动活动 C 并返回时,将重新绘制活动 B 的 XML,因此使用默认值 VIEW.Visible。随后,视图再次弹回(出现)。
您需要以某种方式保存视图的状态。试试:
savedInstanceState 或 SharedPreferences【讨论】:
【参考方案6】:为我这个老问题提供反馈。
我向 google 提交了 bugticket:https://issuetracker.google.com/issues/112158868,但他们无法修复此问题,因为他们优先考虑其他 bug。
我的解决方案是切换到带有喷气背包导航的单一活动概念 (https://developer.android.com/jetpack/compose/navigation)。
所以我现在用片段进行过渡。
感谢您的回复。
【讨论】:
以上是关于使用先前启动的 Activity 转换恢复 Activity 时查看可见性状态丢失的主要内容,如果未能解决你的问题,请参考以下文章
如果后台堆栈中已经存在,则恢复 Activity 而不是启动
深入理解Activity启动流程–Activity Task的调度算法