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

来源:互联网 发布:java反序列化实例 编辑:程序博客网 时间:2024/05/16 23:39

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

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



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

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context=".myinformation.MyInformationActivity">    <android.support.v7.widget.Toolbar        android:id="@+id/my_information_toolbar"        android:layout_width="match_parent"        android:layout_height="?attr/actionBarSize"        android:background="?attr/colorPrimary"        app:theme="@style/WhiteToolBar">    </android.support.v7.widget.Toolbar>    <FrameLayout        android:id="@+id/alter_my_information_frame"        android:layout_width="match_parent"        android:layout_height="match_parent">        <fragment            android:id="@+id/my_information"            android:name="com.icon.app.myinformation.MyInformationFragment"            android:layout_width="match_parent"            android:layout_height="match_parent" />    </FrameLayout></LinearLayout>
布局很简单,里面只放置了一个Toolbar和第一个Fragment (MyInformationFragment),因此,只要这个Activity一启动,首先展示在用户眼前并和用户交互的就是MyInformationFragment,MyInforamtionFragment的布局文件我就不放了,直接放效果图,如下:

展示的用户信息除了头像以外,主要就是姓名,性别,友好度这三项。大家可以注意到 ,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布局文件如下所示:

<?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:app="http://schemas.android.com/apk/res-auto"    tools:context=".myinformation.MyInformationActivity">    <item        android:id="@+id/alter_information"        android:orderInCategory="100"        app:showAsAction="ifRoom"        android:icon="@drawable/ic_create_white_24dp"/></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后的界面如下图所示:


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的生命周期回调,可以更简单易懂的解决这个问题,过段时间我会把最新的方法再写一篇博客。




1 0
原创粉丝点击