Android Toolbar菜单动态切换item的图标

警示:本文所述的方法过于繁琐,逻辑混乱,代码难以适应复杂情况,并且几乎不可扩展,本文之所以没删是我为了记录自己的踩坑记录,因此如果有人想参照这方面的知识,请参考我的另一篇文章:

Toolbar菜单动态改变item的图标(二)



大家都知道,Fragment的启动速度比Activity快很多,因此在开发中如果每一个界面都使用一个Activity显然不那么好,这时候我们一般用Activity来充当管理的角色,界面的内容都放在Fragment中。可是由于每个Fragment都对应一个功能界面,因此每个Fragment的顶部工具栏都应该是不同的,但是ActionBar或者ToolBar都是属于Activity的,这时候我们就需要在切换Fragment的时候使Toolbar也做出相应的动态切换。

我们来举一个简单的例子,打开个人信息Activity(MyInformationActivity),布局文件如下。



    
    

    

        

    

布局很简单,里面只放置了一个Toolbar和第一个Fragment (MyInformationFragment),因此,只要这个Activity一启动,首先展示在用户眼前并和用户交互的就是MyInformationFragment,MyInforamtionFragment的布局文件我就不放了,直接放效果图,如下:

Android Toolbar菜单动态切换item的图标_第1张图片

展示的用户信息除了头像以外,主要就是姓名,性别,友好度这三项。大家可以注意到 ,toolbar的标题是“我的信息”,右上角的MenuItem是一支笔的图片。我们要实现的是点击右上角的menuitem,创建第二个Fragment (AlterMyInformationFragment) 使其到达Activity的最顶端,覆盖当前的MyInforamtionFragment。于是,我们在onOptionsItemSelected(MenuItem item)方法中进行监听,当用户点击右上角的笔图标时,启动新的Fragment,MyInformationActivity的代码如下:

public class MyInformationActivity extends AppCompatActivity {

    private Toolbar toolbar;
    private AlterMyInformationFragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_information);
        toolbar = (Toolbar) findViewById(R.id.my_information_toolbar);
        toolbar.setTitle("我的信息");
        setSupportActionBar(toolbar);
        getSupportActionBar().setHomeButtonEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    private void setAlterMyInformationFragment() {
        fragment = new AlterMyInformationFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.alter_my_information_frame, fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_my_information, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                break;
            case R.id.alter_information:
                setAlterMyInformationFragment();
                break;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }


}

注:android.R.id.home是左上角后退按钮的id。


MyInformationActivity的Menu布局文件如下所示:




    

可以看到,MyInformationActivity的Toolbar中只有一个item。




现在,我们在动态变更菜单前还要做一个事情,一般toolbar最左边的后退按钮相当于back键的功能,即点击以后销毁当前的Activity,但是,我们现在的问题是,如果当前Activity展示的是MyInformationFragment,那自然没有问题,单击后退键,即销毁当前活动,但是如果我们当前处在和AlterMyInforamtionFragment的交互状态,单击后退键把当前Activity销毁了,显然是不合理的,正确的情况是应该销毁AlterMyInforamtionFragment,使用户回到和MyInformationFragment交互的状态。因此,我们需要在Activity中设置一个变量status来进行标记,用来反映当前和用户交互的Fragment到底是哪一个。因此,新增代码后Activity的代码如下所示:

public class MyInformationActivity extends AppCompatActivity {

    private static final int MY_FRAGMENT = 0;
    private static final int ALTER_FRAGMENT = 1;

    private int status;

    private Toolbar toolbar;
    private AlterMyInformationFragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_information);
        toolbar = (Toolbar) findViewById(R.id.my_information_toolbar);
        toolbar.setTitle("我的信息");
        setSupportActionBar(toolbar);
        getSupportActionBar().setHomeButtonEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        status = MY_FRAGMENT;
    }

    private void setAlterMyInformationFragment() {
        fragment = new AlterMyInformationFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.alter_my_information_frame, fragment);
        transaction.addToBackStack(null);
        status = ALTER_FRAGMENT;
        transaction.commit();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_my_information, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                switch (status) {
                    case MY_FRAGMENT:
                        finish();
                        break;
                    case ALTER_FRAGMENT:
                        FragmentManager fragmentManager = getFragmentManager();
                        FragmentTransaction transaction = fragmentManager.beginTransaction();
                        transaction.remove(fragment);
                        status = MY_FRAGMENT;
                        transaction.commit();
                        break;
                    default:
                        break;
                }
                break;
            case R.id.alter_information:
                setAlterMyInformationFragment();
                break;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }

}
我们用两个常量来表示当前到底处在哪一个Fragment,MY_FRAGMENT表示MyInformationFragment,ALTER_FRAGMENT表示AlterMyInformationFragment。我们可以看到Activity启动的时候自动给status赋值MY_FRAGMENT,在setAlterMyInformationFragment执行的时候给status赋值ALTER_FRAGMENT。于是我们在监听后退键的时候就可以进行判断了,如果当前和用户交互的是MyInformationFragment,执行finish()销毁Activity,如果和用户交互的是AlterMyInformationFragment,则销毁Fragment,让用户回到和MyInformationFragment交互的状态。


我们处理完了后退键的问题,就该来解决动态变更toolbar的问题了。切换了Fragment,Toolbar如何相应的发生改变呢?大家平常使用菜单的时候,主要使用两个方法onCreateOptionView(Menu menu)和onOptionsItemSelected(MenuItem item),前者是加载菜单布局的,只会在Activity启动的时候调用一次,后者是对Menu中的item进行事件监听。也就是说,我们是不可能在onCreateOptionView(Menu menu)中对menu进行动态变更的。那我们来试试在onOptionsItemSelected(MenuItem item)中进行。于是我在启动AlterMyInforamtionFragment的代码后面加了这么两行:

item.setIcon(R.drawable.ic_send_white_24dp);
toolbar.setTitle("修改个人信息");
这样启动AlterMyInforamtionFragment后的界面如下图所示:

Android Toolbar菜单动态切换item的图标_第2张图片
demo的界面还是很简单,用户可以自己修改自己的姓名和性别,看起来我们貌似实现了,这时候我们遇到一个问题,现在右上角的item的功能不再是启动新的Fragment了,而应该是用户修改好个人信息后进行提交,于是我们再次修改代码,让onOptionsItemSelected(MenuItem item)中的代码变成这样:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                switch (status) {
                    case MY_FRAGMENT:
                        finish();
                        break;
                    case ALTER_FRAGMENT:
                        FragmentManager fragmentManager = getFragmentManager();
                        FragmentTransaction transaction = fragmentManager.beginTransaction();
                        transaction.remove(fragment);
                        status = MY_FRAGMENT;
                        transaction.commit();
                        break;
                    default:
                        break;
                }
                break;
            case R.id.alter_information:
                switch (status) {
                    case MY_FRAGMENT:
                        setAlterMyInformationFragment();
                        item.setIcon(R.drawable.ic_send_white_24dp);
                        toolbar.setTitle("修改个人信息");
                        break;
                    case ALTER_FRAGMENT:
                        fragment.postAlterInformation();
                        break;
                    default:
                        break;
                }
                break;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }
我们再次通过status来判断当前和用户交互的Fragment,从而决定这个item在不同状态下的功能。在ALTER_FRAGMENT状态下调用AlterMyInformationFragment类中的postAlterInformation()方法把修改好的信息通过网络发送给服务器。

看起来我们已经实现这个功能了,但是还有很重要的一点没有做,那就是点击后退键的时候我们的toolbar应该变回之前的样子,但是我们现在还没有实现。那应该怎么做呢?修改toolbar的title很容易执行,只要在监听android.R.id.home的ALTER_FRAGMENT的switch分支下加一条

toolbar.setTitle("我的信息");
就可以了,但是变更图标似乎不那么容易,有些人说,再加一条
item.setIcon("R.drawable.ic_create_white_24dp");
不就行了吗,但是我们仔细看,我们之所以在启动AlterMyInforamtionFragment的时候能通过这条代码改变item的图标,是因为我们这行代码写在case R.id.alter_information这条switch分支下,此时的item代表的是右上角我们要修改的item。但是如果你要android.R.id.home这条switch分支下使用参数item,这个item代表的是左上角的后退键,即使执行上面那条代码,也不会有任何效果。但是,我们确实必须在点击后退键的时候改变右上角的item的图标,这就麻烦了,我找了item能调用的所有方法,没有发现能通过什么方式指定它的id,让它在非case R.id.alter_information分支下也代表这个item。后来我在网上查阅后发现,我们应该改变一种思路,因为onCreateOptionView(Menu menu)和onOptionsItemSelected(MenuItem item)这两个方法可能不够用了。

我在网上看到很多文章发现,onPrepareOptionsMenu(Menu menu)这个方法也可以被调用多次,那我们就通过它来改变item的图标。于是MyInformationActivity的代码就变成了如下这样

public class MyInformationActivity extends AppCompatActivity {

    private static final int MY_FRAGMENT = 0;
    private static final int ALTER_FRAGMENT = 1;

    private int status;

    private Toolbar toolbar;
    private AlterMyInformationFragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_information);
        toolbar = (Toolbar) findViewById(R.id.my_information_toolbar);
        toolbar.setTitle("我的信息");
        setSupportActionBar(toolbar);
        getSupportActionBar().setHomeButtonEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        status = MY_FRAGMENT;
    }

    private void setAlterMyInformationFragment() {
        fragment = new AlterMyInformationFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.alter_my_information_frame, fragment);
        transaction.addToBackStack(null);
        status = ALTER_FRAGMENT;
        transaction.commit();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_my_information, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                switch (status) {
                    case MY_FRAGMENT:
                        finish();
                        break;
                    case ALTER_FRAGMENT:
                        FragmentManager fragmentManager = getFragmentManager();
                        FragmentTransaction transaction = fragmentManager.beginTransaction();
                        transaction.remove(fragment);
                        status = MY_FRAGMENT;
                        transaction.commit();
                        invalidateOptionsMenu();
                        toolbar.setTitle("我的信息");
                        break;
                    default:
                        break;
                }
                break;
            case R.id.alter_information:
                switch (status) {
                    case MY_FRAGMENT:
                        setAlterMyInformationFragment();
                        item.setIcon(R.drawable.ic_send_white_24dp);
                        toolbar.setTitle("修改个人信息");
                        break;
                    case ALTER_FRAGMENT:
                        fragment.postAlterInformation();
                        break;
                    default:
                        break;
                }
                break;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        menu.findItem(R.id.alter_information).setIcon(R.drawable.ic_create_white_24dp);
        return super.onPrepareOptionsMenu(menu);
    }

}

调用invalidateOptionsMenu()方法的地方,onPrepareOptionsMenu(Menu menu)就会被调用一次,值得说明的是,网上许多文章的教程写的是,如果你使用的是系统原生的ActionBar,应调用mActivity.getWindow().invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); 如果使用的是ActionBarSherlock就应调用invalidateOptionsMenu(),目前大家可能使用ActionBar的逐渐减少,都逐步转向了Toolbar,Toolbar和ActionBarSherlock一样,都是调用invalidateOptionsMenu()方法。


2016年10月7日最新更新:又经过了两个月的学习,我又遇到了更为复杂的问题,比如一个activity可能会管理三个甚至更多的Fragment,这时候我这篇博客写的解决方法就不太灵了,会让代码既复杂又臃肿,而且难以阅读。不过我已经找到了新的解决方法,通过冲分利用Fragment的返回栈模拟以及Fragment的生命周期回调,可以更简单易懂的解决这个问题,过段时间我会把最新的方法再写一篇博客。




你可能感兴趣的:(UI)