【问题标题】:Android force Fragment redraw when screen change orientation屏幕改变方向时Android强制片段重绘
【发布时间】:2017-07-31 15:25:44
【问题描述】:

我的应用由导航抽屉和 4 个片段构成。 该应用程序的旧版本使用活动,因此我需要在片段中转换该活动。

目前一切正常,但在一个 Fragment 我有 80 个 Buttons 用户可以设置文本和背景颜色,调用 DialogActivity 的方法是唯一一个管理所有 FragmentsMainActivityonActivityResultsFragments 内的对话框调用以管理用户更改。

当屏幕方向变为横向时会出现问题。 如果我按Buttons 并设置文本和颜色所有作品纵向但如果改变屏幕方向与横向我会得到类似“阴影”的背景和按钮我没有改变当我单击它们时它们的属性,但如果我再次旋转屏幕,更改的按钮变得可见。 奇怪的是,在片段背景中,我看到了正确更新的按钮,但没有在顶部...(我发布了一张照片,很难解释)

我更改的旧按钮仍然更改,因为我将其保存到 DB,但是对于横向我无法更新其他按钮...

代码:

MainActivity.java

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {


    static String clickedButtonViewId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);


        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();

        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        if (findViewById(R.id.content_frame) != null){

            getSupportFragmentManager().beginTransaction()
                    .add(R.id.content_frame, new OrarioFragment()).commit();
        }
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        FragmentManager fragmentManager = getSupportFragmentManager();

        if (id == R.id.nav_orario) {

            fragmentManager.beginTransaction()
                    .replace(R.id.content_frame, new OrarioFragment())
                    .commit();

        } else if (id == R.id.nav_calendario) {

            fragmentManager.beginTransaction()
                    .replace(R.id.content_frame, new CalendarioFragment())
                    .commit();

        } else if (id == R.id.nav_voti) {

            fragmentManager.beginTransaction()
                    .replace(R.id.content_frame, new VotiFragment())
                    .commit();

        } else if (id == R.id.nav_registrazioni) {

            fragmentManager.beginTransaction()
                    .replace(R.id.content_frame, new RegistrazioniFragment())
                    .commit();

        } else if (id == R.id.nav_share) {

        } else if (id == R.id.nav_send) {

        }

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }


    public void addMateria(View v){

        /* Prendo il nome della risorsa cosi nel ricompilare il progetto non perdo *
         * tutti i riferimenti ai bottoni salvati nel database                     */

        clickedButtonViewId = getResources().getResourceEntryName(v.getId());

        //StartActivityForResult perche mi aspetto la materia inserita dall'altra activity
        Intent myIntent = new Intent(MainActivity.this, ActivityAddMateria.class);
        startActivityForResult(myIntent, 1);
        //onStop();
    }

    //Take back data from ActivityAddMateria
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == 1) {
            if (resultCode == RESULT_OK) {

                MySQLiteHelper db = new MySQLiteHelper(this);

                //Cambio subito il Button
                int resId = getResources().getIdentifier(clickedButtonViewId, "id", getPackageName());
                final Button clickedtextView = (Button) findViewById(resId);

                String result = data.getStringExtra("result"); //Take the materia from Dialog
                int color = data.getIntExtra("color", 1); //Take the color from Dialog

                //Controllo se il Button è già presente nel db se presente aggiorno se non presente inserisco
                boolean modifica = db.Exists(clickedButtonViewId);

                //Se voglio ripristinare il bottone di default
                if (color == getResources().getColor(R.color.blue_orario)) {

                    //Ripristino la grafica di Default
                    Drawable style = setButtonColor(color);
                    clickedtextView.setBackground(style);
                    clickedtextView.setText("New");

                    //Se la materia è nel database la cancello
                    if (modifica) {

                        db.deleteSingleMateria(clickedButtonViewId);

                    }

                } else {
                    //Quando inserisco un normale bottone colorato
                    if (!modifica) {

                        //Materia da inserire in un nuovo spazio
                        db.addMateriaToDb(new Materia(clickedButtonViewId, result, color));

                    } else {

                        //Materia già presente nel Button quindi aggiorno la materia
                        db.updateMateria(new Materia(clickedButtonViewId, result, color));
                        Toast.makeText(getApplicationContext(), "Materia modificata!",
                                Toast.LENGTH_LONG).show();
                    }

                    //Inserisco la materia nel DB dei voti_media
                    db.addMateriaVotiFromOrario(new MaterieVoti(result, 0.0));

                    clickedtextView.setText(result);
                    //clickedtextView.setBackgroundColor(color);
                    //clickedtextView.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
                    Drawable style = setButtonColor(color);
                    clickedtextView.setBackground(style);
                }
            }

            if (resultCode == RESULT_CANCELED) {
                //Nessuna materia inserita
            }

        }
    }//onActivityResult

编辑

好的,我发现了问题。

在 MainActivity 我有这行代码来强制显示第一个片段

 if (findViewById(R.id.content_frame) != null){

            getSupportFragmentManager().beginTransaction()
                    .add(R.id.content_frame, new OrarioFragment()).commit();
        }

当屏幕方向改变时,MainActivity 会重新创建,并且如果我使用 .add() 会在旧片段上加载相同的片段

那么,当应用开始避免这个问题时,如何设置片段显示?

我管理抽屉有问题?

【问题讨论】:

  • 显示您的代码。
  • @Bryan 添加了代码,如果需要更多,请告诉 :)
  • 你也可以分享你的布局吗?
  • @JRG 全部两个还是只有 main_activity 或 fragmet?
  • 两者都会让事情变得清晰

标签: android android-fragments navigation-drawer screen-orientation


【解决方案1】:

解释保存状态的 Android 文档和一篇关于在活动和片段中保存状态的非常好的文章。

保存和恢复 Activity 状态 在某些情况下,您的 Activity 会由于正常的应用行为而被破坏,例如当用户按下“后退”按钮或您的 Activity 通过调用发出自己的破坏信号时完成()方法。如果 Activity 处于 Stopped 状态且长时间未使用,或者前台 Activity 需要更多资源,系统也可能会销毁包含您的 Activity 的进程以恢复内存。

当您的 Activity 因用户按下 Back 或 Activity 自行结束而被销毁时,系统对该 Activity 实例的概念将永远消失,因为该行为表明不再需要该 Activity。但是,如果系统由于系统约束(而不是正常的应用程序行为)而破坏了 Activity,那么虽然实际的 Activity 实例已经消失,但系统会记住它的存在,因此如果用户导航回它,系统会创建一个新的活动实例使用一组保存的数据来描述活动被销毁时的状态。系统用来恢复之前状态的保存数据称为实例状态,是存储在 Bundle 对象中的键值对的集合。

默认情况下,系统使用 Bundle 实例状态来保存有关活动布局中每个 View 对象的信息(例如输入到 EditText 小部件中的文本值)。 因此,如果您的活动实例被销毁并重新创建,则布局的状态将恢复到之前的状态,而无需您编写任何代码。但是,您的 Activity 可能有更多您想要恢复的状态信息,例如跟踪用户在 Activity 中的进度的成员变量。

保存您的活动状态 当您的活动开始停止时,系统会调用 onSaveInstanceState() 方法,以便您的活动可以使用键值对集合保存状态信息。此方法的默认实现保存有关活动视图层次结构状态的临时信息,例如 EditText 小部件中的文本或 ListView 小部件的滚动位置。您的应用应在 onPause() 方法之后和 onStop() 之前实现 onSaveInstanceState() 回调。不要在 onPause() 中实现这个回调。

注意:您必须始终调用 onSaveInstanceState() 的超类实现,以便默认实现可以保存视图层次结构的状态。

要为您的 Activity 保存额外的状态信息,您必须重写 onSaveInstanceState() 并将键值对添加到在您的 Activity 意外销毁时保存的 Bundle 对象。例如:

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...


@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);


    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

注意:为了让 Android 系统恢复 Activity 中视图的状态,每个视图必须有一个唯一的 ID,由 android:id 属性提供。

要保存持久性数据,例如用户偏好或数据库数据,您应该在活动处于前台时抓住适当的机会。如果没有这样的机会出现,您应该在 onStop() 方法期间保存这些数据。

恢复您的活动状态 当您的 Activity 在之前被销毁后重新创建时,您可以从系统传递给您的 Activity 的 Bundle 中恢复您保存的状态。 onCreate() 和 onRestoreInstanceState() 回调方法都接收包含实例状态信息的同一个 Bundle。

因为无论系统是在创建您的活动的新实例还是重新创建之前的活动,都会调用 onCreate() 方法,因此您必须在尝试读取之前检查状态 Bundle 是否为空。如果为 null,则系统正在创建 Activity 的新实例,而不是恢复之前被破坏的实例。

例如,下面的代码 sn-p 展示了如何在 onCreate() 中恢复一些状态数据:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first


    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        // Restore value of members from saved state
        mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
        mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance
    }
    ...
}

您可以选择实现 onRestoreInstanceState(),而不是在 onCreate() 期间恢复状态,系统在 onStart() 方法之后调用它。系统只有在有保存状态需要恢复时才会调用onRestoreInstanceState(),所以不需要检查Bundle是否为null:

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);


    // Restore state members from saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

注意:始终调用 onRestoreInstanceState() 的超类实现,以便默认实现可以恢复视图层次结构的状态。

【讨论】:

  • 是的,但是为什么当屏幕方向改变时我有一个像照片一样的“阴影”以及为什么我看不到按钮随着屏幕横向而改变。如果我回到肖像所有工作都完美,那就是风景问题,不要认为问题出在 InstanceState.... 不是吗?使用 instanceState 我无法避免横向的“阴影”效果
  • 两个方向的布局是否相同,或者您有方向的布局和 layout-land 文件夹?
  • 相同的布局,我没有针对不同屏幕方向的两种布局。屏幕方向改变时我可能需要销毁片段吗?也许“阴影”效果是两个片段 UI 的重叠?因为背景中的阴影如果我更改带有横向的按钮,我会在阴影背景中看到更新,但不是在顶部(很难解释:D)
  • 在纵向模式下,我可以实时看到变化
  • 这看起来很奇怪,为什么当颜色是 orario 时你将 button_style 设置为 getResources().getDrawable(R.drawable.buttons) 而对于所有其他颜色你都有一个彩色按钮?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多