使用先前启动的 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】:

是的.. 现在是您可以使用 onSaveInstanceStateonRestoreInstanceState 的时候了。

您可以在 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的调度算法

Activity的四种启动模式

深入理解Activity启动流程–Activity启动相关类的类图

activity怎么跳转到fragment

startActivityForResult被标记为弃用后,如何优雅的启动Activity?