zoukankan      html  css  js  c++  java
  • Android ActionBar全然解析,使用官方推荐的最佳导航栏(上)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/18234477


    本篇文章主要内容来自于Android Doc。我翻译之后又做了些加工。英文好的朋友也能够直接去读原文。

    http://developer.android.com/guide/topics/ui/actionbar.html


    Action Bar是一种新増的导航栏功能。在Android 3.0之后加入到系统的API其中,它标识了用户当前操作界面的位置,并提供了额外的用户动作、界面导航等功能。

    使用ActionBar的优点是,它能够给提供一种全局统一的UI界面,使得用户在使用不论什么一款软件时都懂得该怎样操作。而且ActionBar还能够自己主动适应各种不同大小的屏幕。

    下面是一张使用ActionBar的界面截图:




    其中。[1]是ActionBar的图标,[2]是两个actionbutton,[3]是overflowbutton。


    因为Action Bar是在3.0以后的版本号中加入的。假设想在2.x的版本号里使用ActionBar的话则须要引入Support Library,只是3.0之前版本号的市场占有率已经很小了,这里简单起见我们就不再考虑去做向下兼容,而是仅仅考虑4.0以上版本号的使用方法。


    加入和移除Action Bar


    ActionBar的加入很easy,仅仅须要在AndroidManifest.xml中指定Application或Activity的theme是Theme.Holo或其子类就能够了,而使用Eclipse创建的项目自己主动就会将Application的theme指定成Theme.Holo。所以ActionBar默认都是显示出来的。新建一个空项目并执行。效果例如以下图所看到的:




    而假设想要移除ActionBar的话通常有两种方式。一是将theme指定成Theme.Holo.NoActionBar,表示使用一个不包括ActionBar的主题,二是在Activity中调用下面方法:

    ActionBar actionBar = getActionBar();
    actionBar.hide();

    如今又一次执行一下程序,就能够看到ActionBar不再显示了,例如以下图所看到的:




    改动Action Bar的图标和标题


    默认情况下,系统会使用<application>或者<activity>中icon属性指定的图片来作为ActionBar的图标,可是我们也能够改变这一默认行为。假设我们想要使用另外一张图片来作为ActionBar的图标,能够在<application>或者<activity>中通过logo属性来进行指定。比方项目的res/drawable文件夹下有一张weather.png图片。就能够在AndroidManifest.xml中这样指定:
    <activity
        android:name="com.example.actionbartest.MainActivity"
        android:logo="@drawable/weather" >
    </activity>
    如今又一次执行一下程序,效果例如以下图所看到的:



    OK,ActionBar的图标已经改动成功了,那么标题中的内容该怎样改动呢?事实上也很easy,使用label属性来指定一个字符串就能够了。例如以下所看到的:
    <activity
        android:name="com.example.actionbartest.MainActivity"
        android:label="天气"
        android:logo="@drawable/weather" >
    </activity>
    如今又一次执行一下程序。结果例如以下图所看到的:



    加入Actionbutton


    ActionBar还能够依据应用程序当前的功能来提供与其相关的Actionbutton,这些button都会以图标或文字的形式直接显示在ActionBar上。

    当然,假设button过多,ActionBar上显示不完。多出的一些button能够隐藏在overflow里面(最右边的三个点就是overflowbutton)。点击一下overflowbutton就能够看到全部的Actionbutton了。


    当Activity启动的时候。系统会调用Activity的onCreateOptionsMenu()方法来取出全部的Actionbutton。我们仅仅须要在这种方法中去载入一个menu资源,并把全部的Actionbutton都定义在资源文件中面就能够了。

    那么我们先来看下menu资源文件该怎样定义。代码例如以下所看到的:
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        tools:context="com.example.actionbartest.MainActivity" >
    
        <item
            android:id="@+id/action_compose"
            android:icon="@drawable/ic_action_compose"
            android:showAsAction="always"
            android:title="@string/action_compose"/>
        <item
            android:id="@+id/action_delete"
            android:icon="@drawable/ic_action_delete"
            android:showAsAction="always"
            android:title="@string/action_delete"/>
        <item
            android:id="@+id/action_settings"
            android:icon="@drawable/ic_launcher"
            android:showAsAction="never"
            android:title="@string/action_settings"/>
    
    </menu>
    能够看到,这里我们通过三个<item>标签定义了三个Actionbutton。<item>标签中又有一些属性,其中id是该Actionbutton的唯一标识符。icon用于指定该button的图标,title用于指定该button可能显示的文字(在图标能显示的情况下,通常不会显示文字)。showAsAction则指定了该button显示的位置。主要有下面几种值可选:always表示永远显示在ActionBar中,假设屏幕空间不够则无法显示。ifRoom表示屏幕空间够的情况下显示在ActionBar中,不够的话就显示在overflow中,never则表示永远显示在overflow中。
    接着,重写Activity的onCreateOptionsMenu()方法。代码例如以下所看到的:
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	MenuInflater inflater = getMenuInflater();
    	inflater.inflate(R.menu.main, menu);
    	return super.onCreateOptionsMenu(menu);
    }
    这部分代码很easy,仅仅是调用了MenuInflater的inflate()方法来载入menu资源就能够了。如今又一次执行一下程序,结果例如以下图所看到的:



    能够看到,action_compose和action_delete这两个button已经在ActionBar中显示出来了。而action_settings这个button因为showAsAction属性设置成了never。所以被隐藏到了overflow其中,仅仅要点击一下overflowbutton就能够看到它了。


    这里我们注意到,显示在ActionBar上的button都仅仅有一个图标而已。我们在title中指定的文字并没有显示出来。没错,title中的内容通常情况下仅仅会在overflow中显示出来。ActionBar中因为屏幕空间有限,默认是不会显示title内容的。可是出于下面几种因素考虑,即使title中的内容无法显示出来,我们也应该给每一个item中都指定一个title属性:
    • 当ActionBar中的剩余空间不足的时候,假设Actionbutton指定的showAsAction属性是ifRoom的话。该Actionbutton就会出如今overflow其中,此时就仅仅有title能够显示了。

    • 假设Actionbutton在ActionBar中显示,用户可能通过长按该Actionbutton的方式来查看到title的内容。


    响应Actionbutton的点击事件


    当用户点击Actionbutton的时候。系统会调用Activity的onOptionsItemSelected()方法。通过方法传入的MenuItem參数,我们能够调用它的getItemId()方法和menu资源中的id进行比較,从而辨别出用户点击的是哪一个Actionbutton,比方:
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	switch (item.getItemId()) {
    	case R.id.action_compose:
    		Toast.makeText(this, "Compose", Toast.LENGTH_SHORT).show();
    		return true;
    	case R.id.action_delete:
    		Toast.makeText(this, "Delete", Toast.LENGTH_SHORT).show();
    		return true;
    	case R.id.action_settings:
    		Toast.makeText(this, "Settings", Toast.LENGTH_SHORT).show();
    		return true;
    	default:
    		return super.onOptionsItemSelected(item);
    	}
    }
    能够看到,我们让每一个Actionbutton被点击的时候都弹出一个Toast,如今又一次执行一下代码。结果例如以下图所看到的:




    通过Action Bar图标进行导航


    启用ActionBar图标导航的功能,能够同意用户依据当前应用的位置来在不同界面之间切换。比方,A界面展示了一个列表,点击某一项之后进入了B界面,这时B界面就应该启用ActionBar图标导航功能。这样就能够回到A界面。

    我们能够通过调用setDisplayHomeAsUpEnabled()方法来启用ActionBar图标导航功能。比方:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	setTitle("天气");
    	setContentView(R.layout.activity_main);
    	ActionBar actionBar = getActionBar();
    	actionBar.setDisplayHomeAsUpEnabled(true);
    }
    如今又一次执行一下程序,结果例如以下图所看到的:



    能够看到,在ActionBar图标的左側出现了一个向左的箭头,通常情况下这都表示返回的意思,因此最简单的实现就是在它的点击事件里面加入finish()方法就能够了,例如以下所看到的:
    	@Override
    	public boolean onOptionsItemSelected(MenuItem item) {
    		switch (item.getItemId()) {
    		case android.R.id.home:
    			finish();
    			return true;
    		……
    		}
    	}
    当点击ActionBar图标的时候,系统相同会调用onOptionsItemSelected()方法。而且此时的itemId是android.R.id.home,所以finish()方法也就是加在这里的了。
    如今看上去。ActionBar导航和Back键的功能貌似是一样的。

    没错,假设我们仅仅是简单地finish了一下。ActionBar导航和Back键的功能是全然一样的,但ActionBar导航的设计初衷并非这种,它和Back键的功能还是有一些差别的。举个样例吧。




    上图中的Conversation List是收件箱的主界面。如今我们点击第一封邮件会进入到Conversation1 details界面。然后点击下一封邮件会进入到Conversation 2 details界面,再点击下一封邮箱会进入到Conversation3 details界面。好的。这个时候假设我们按下Back键,应该会回到Conversation 2 details界面,再按一次Back键应该回到Conversation1 details界面。再按一次Back键才会回到Conversation List。

    而ActionBar导航则不应该表现出这种行为,不管我们当前在哪一个Conversation details界面。点击一下导航button都应该回到Conversation List界面才对。


    这就是ActionBar导航和Back键在设计上的差别。那么该怎样才干实现这种功能呢?事实上并不复杂。实现标准的ActionBar导航功能仅仅需三步走。

    第一步我们已经实现了,就是调用setDisplayHomeAsUpEnabled()方法。并传入true。


    第二步须要在AndroidManifest.xml中配置父Activity,例如以下所看到的:
    <activity
        android:name="com.example.actionbartest.MainActivity"
        android:logo="@drawable/weather" >
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.example.actionbartest.LaunchActivity" />
    </activity>
    能够看到,这里通过meta-data标签指定了MainActivity的父Activity是LaunchActivity。在Android 4.1版本号之后,也能够直接使用android:parentActivityName这个属性来进行指定。例如以下所看到的:
    <activity
        android:name="com.example.actionbartest.MainActivity"
        android:logo="@drawable/weather"
        android:parentActivityName="com.example.actionbartest.LaunchActivity" >
    </activity>
    第三步则须要对android.R.id.home这个事件进行一些特殊处理。例如以下所看到的:
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	switch (item.getItemId()) {
    	case android.R.id.home:
    		Intent upIntent = NavUtils.getParentActivityIntent(this);
    		if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
    			TaskStackBuilder.create(this)
    					.addNextIntentWithParentStack(upIntent)
    					.startActivities();
    		} else {
    			upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    			NavUtils.navigateUpTo(this, upIntent);
    		}
    		return true;
            ......
    	}
    }
    其中,调用NavUtils.getParentActivityIntent()方法能够获取到跳转至父Activity的Intent。然后假设父Activity和当前Activity是在同一个Task中的,则直接调用navigateUpTo()方法进行跳转,假设不是在同一个Task中的,则须要借助TaskStackBuilder来创建一个新的Task。

    这样,就依照标准的规范成功实现ActionBar导航的功能了。

    加入Action View


    ActionView是一种能够在ActionBar中替换Actionbutton的控件,它能够同意用户在不切换界面的情况下通过ActionBar完毕一些较为丰富的操作。比方说,你须要完毕一个搜索功能。就能够将SeachView这个控件加入到ActionBar中。


    为了声明一个ActionView,我们能够在menu资源中通过actionViewClass属性来指定一个控件,比如能够使用例如以下方式加入SearchView:
    <menu xmlns:android="http://schemas.android.com/apk/res/android" >
    
        <item
            android:id="@+id/action_search"
            android:icon="@drawable/ic_action_search"
            android:actionViewClass="android.widget.SearchView"
            android:showAsAction="ifRoom|collapseActionView"
            android:title="@string/action_search" />
        ......
    
    </menu>
    注意在showAsAction属性中我们还声明了一个collapseActionView,这个值表示该控件能够被合并成一个Actionbutton。
    如今又一次执行一下程序,效果例如以下图所看到的:



    OK,果然有一个搜索样式的Actionbutton出现了,如今点击一下这个搜索button。效果例如以下图所看到的:



    能够看到,这时SearchView就会展开占满整个ActionBar,而其他的Actionbutton因为将showAsAction属性设置成了ifRoom,此时都会隐藏到overflow其中。


    假设你还希望在代码中对SearchView的属性进行配置(比方加入监听事件等),全然没有问题。仅仅须要在onCreateOptionsMenu()方法中获取该ActionView的实例就能够了,代码例如以下所看到的:
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	MenuInflater inflater = getMenuInflater();
    	inflater.inflate(R.menu.main, menu);
    	MenuItem searchItem = menu.findItem(R.id.action_search);
    	SearchView searchView = (SearchView) searchItem.getActionView();
    	// 配置SearchView的属性
    	......
    	return super.onCreateOptionsMenu(menu);
    }
    在得到了SearchView的实例之后,就能够随意地配置它的各种属性了。关于SearchView的很多其他具体使用方法,能够參考官方文档 http://developer.android.com/guide/topics/search/search-dialog.html 。


    除此之外,有些程序可能还希望在ActionView展开和合并的时候显示不同的界面,事实上我们仅仅须要去注冊一个ActionView的监听器就能实现这种功能了,代码例如以下所看到的:
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	MenuInflater inflater = getMenuInflater();
    	inflater.inflate(R.menu.main, menu);
    	MenuItem searchItem = menu.findItem(R.id.action_search);
    	searchItem.setOnActionExpandListener(new OnActionExpandListener() {
    		@Override
    		public boolean onMenuItemActionExpand(MenuItem item) {
    			Log.d("TAG", "on expand");
    			return true;
    		}
    		
    		@Override
    		public boolean onMenuItemActionCollapse(MenuItem item) {
    			Log.d("TAG", "on collapse");
    			return true;
    		}
    	});
    	return super.onCreateOptionsMenu(menu);
    }
    能够看到,调用MenuItem的setOnActionExpandListener()方法就能够注冊一个监听器了。当SearchView展开的时候就会回调onMenuItemActionExpand()方法。当SearchView合并的时候就会调用onMenuItemActionCollapse()方法,我们在这两个方法中进行相应的UI操作就能够了。


    Overflowbutton不显示的情况


    尽管如今我们已经掌握了不少ActionBar的使用方法,可是当你真正去使用它的时候还是可能会遇到各种各样的问题,比方很多人都会碰到overflowbutton不显示的情况。明明是相同的一份代码。overflowbutton在有些手机上会显示,而在有些手机上偏偏就不显示,这是为什么呢?后来我总结了一下,overflowbutton的显示情况和手机的硬件情况是有关系的。假设手机没有物理Menu键的话,overflowbutton就能够显示,假设有物理Menu键的话。overflowbutton就不会显示出来。

    比方我们启动一个有Menu键的模拟器。然后将代码执行到该模拟器上,结果例如以下图所看到的:




    能够看到,ActionBar最右边的overflowbutton不见了!

    那么此时我们怎样查看隐藏在overflow中的Actionbutton呢?事实上很easy,按一下Menu键,隐藏的内容就会从底部出来了,例如以下图所看到的:




    看到这里相信不少朋友都想吐槽一下了,这显然是一种很蛋疼的设计,在不同手机上居然显示了不同的界面,而且操作方法也全然不一样,这样会给用户一种很不习惯的感觉。话说Google为什么要把ActionBar的overflow设计成这样我也不太理解,可是我们还是有办法改变这一默认行为的。

    实际上,在ViewConfiguration这个类中有一个叫做sHasPermanentMenuKey的静态变量,系统就是依据这个变量的值来推断手机有没有物理Menu键的。当然这是一个内部变量,我们无法直接訪问它。可是能够通过反射的方式改动它的值,让它永远为false就能够了,代码例如以下所看到的:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	......
    	setOverflowShowingAlways();
    }
    
    private void setOverflowShowingAlways() {
    	try {
    		ViewConfiguration config = ViewConfiguration.get(this);
    		Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
    		menuKeyField.setAccessible(true);
    		menuKeyField.setBoolean(config, false);
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
    这里我们在onCreate()方法的最后调用了setOverflowShowingAlways()方法,而这种方法的内部就是使用反射的方式将sHasPermanentMenuKey的值设置成false,如今又一次执行一下代码。结果例如以下图所看到的:



    能够看到。即使是在有Menu键的手机上。也能让overflowbutton显示出来了,这样就能够大大添加我们软件界面和操作的统一性。

    让Overflow中的选项显示图标


    假设你点击一下overflowbutton去查看隐藏的Actionbutton,你会发现这部分Actionbutton都是仅仅显示文字不显示图标的。例如以下图所看到的:



    这是官方的默认效果,Google觉得隐藏在overflow中的Actionbutton都应该仅仅显示文字。当然。假设你觉得这样不够美观,希望在overflow中的Actionbutton也能够显示图标。我们仍然能够想办法来改变这一默认行为。

    事实上,overflow中的Actionbutton应不应该显示图标,是由MenuBuilder这个类的setOptionalIconsVisible方法来决定的。假设我们在overflow被展开的时候给这种方法传入true。那么里面的每一个Actionbutton相应的图标就都会显示出来了。调用的方法当然仍然是用反射了。代码例如以下所看到的:
    @Override
    public boolean onMenuOpened(int featureId, Menu menu) {
    	if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {
    		if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
    			try {
    				Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
    				m.setAccessible(true);
    				m.invoke(menu, true);
    			} catch (Exception e) {
    			}
    		}
    	}
    	return super.onMenuOpened(featureId, menu);
    }
    能够看到,这里我们重写了一个onMenuOpened()方法,当overflow被展开的时候就会回调这种方法,接着在这种方法的内部通过返回反射的方法将MenuBuilder的setOptionalIconsVisible变量设置为true就能够了。


    如今又一次执行一下代码,结果例如以下图所看到的:



    好了。眼下为止我们已经把ActionBar的基础知识介绍完了,那么今天的解说就到这里,下篇文章中我会带领大家一起更深入地了解ActionBar,感兴趣的朋友请继续阅读 Android ActionBar全然解析。使用官方推荐的最佳导航栏(下)

    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码就可以关注:

            

  • 相关阅读:
    ASP.NET Web API 框架研究 Self Host模式下的消息处理管道
    ASP.NET Web API 框架研究 Web Host模式下的消息处理管道
    ASP.NET Web API 框架研究 核心的消息处理管道
    ASP.NET Web API 框架研究 Web Host模式路由及将请求转出到消息处理管道
    ASP.NET Web API 框架研究 ASP.NET Web API 路由
    ASP.NET Web API 框架研究 ASP.NET 路由
    ASP.NET Web API 入门 (API接口、寄宿方式、HttpClient调用)
    MVVM模式
    RESTful Web API 理解
    C# 函数式编程及Monads.net库
  • 原文地址:https://www.cnblogs.com/mqxnongmin/p/10736554.html
Copyright © 2011-2022 走看看