Android编程权威指南总结(三)

第十二章     对话框

一、创建DialogFragment

        建议将AlertDialog封装在DialogFragment(Fragment的子类)实例中使用。当然,不使用DialogFragment也可以显示AlertDialog视图,但是不推荐这样做。使用FragmentManager管理对话框,可以更加灵活的显示对话框。

        另外,如果旋转设备,单独使用的AlertDialog会消失,而封装在fragment中的AlertDialog则不会有此问题(旋转后对话框会被重建恢复)。

        DialogFragment类有这个方法:public  Dialog  onCreateDialog(Bundle  savedInstanceState),为了在屏幕上显示DialogFragment,托管activity的FragmentManager会调用这个方法。在继承DialogFragment的子类中重写此方法,之后创建AlertDialog。

        要将DialogFragment添加给FragmentManager管理并放置在屏幕上,可调用fragment实例的两个方法:public  void  show(FragmentManager  manager , String  tag);public  void  show(FragmentTransaction  transaction , String  tag)。String参数可唯一识别FragmentManager队列中的DialogFragment。两个方法都可用,如果使用第二个方法,你自己负责创建并提交事务;如果使用第一个方法,系统会自己创建并提交事务。

二、Fragment之间的数据传递

       假设两个Fragment:CrimeFragment和DatePickerFragment。

       CrimeFragment要将日期传递给DatePickerFragment,可以创建一个newInstance(Date)方法,将Date作为argument附加给fragment。

       DatePickerFragment将新日期返回给CrimeFragment,需要将日期打包给extra并附加到 Intent 上,然后调用CrimeFragment.onActivityResult(......)方法,并传入准备好的参数。

       我们可以看到,我们没有调用托管activity的Activity.onActivityResult(......) 方法,而是调用了 Fragment.onActivityResult(......) 方法。

       1、传递数据给 DatePickerFragment

        要传递数据给 DatePickerFragment,需要将数据保存在 DatePickerFragment 的argument bundle 中,DatePickerFragment就能直接获取它。

       2、返回数据给 CrimeFragment

       要让CrimeFragment接收 DatePickerFragment 返回的数据,首先需要清除它们之间的关系。

       如果是Activity之间的回传,我们调用startActivityForResult(......)方法,ActivityManager 负责跟踪管理activity父子关系。回传数据后,子activity被销毁,但ActivityManager知道接收数据的是哪个activity。

      (1)设置目标Fragment

       类似于activity之间的关联,可将 CrimeFragment 设置为 DatePickerFragment 的目标fragment。这样,在CrimeFragment和DatePickerFragment被销毁重建后,系统会重新关联它们。调用如下 Fragment 方法可建立这种关联:public  void  setTargetFragment (Fragment  fragment ,int  requestCode)。

       该方法的两个参数:目标fragment和请求代码,由FragmentManager负责跟踪管理,我们可调用fragment(设置目标fragment 的 fragment)的 getTargetFragment() 方法和 getTargetRequestCode() 方法获取它们。

       (2)传递数据给目标Fragment

        让 DatePickerFragment 类调用 CrimeFragment.onActivityResult(int,int,Intent)方法。

        处理由同一activity托管的两个fragment间的数据返回时,可借用 Fragment.onActivityResult(......) 方法:

private void sendResult(int resultCode, Date date) { 
    if (getTargetFragment() == null) { 
        return; 
    } 

    Intent intent = new Intent(); 
    intent.putExtra(EXTRA_DATE, date); 
    getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent); 
 }

         之后在 CrimeFragment 中,覆盖 onActivityResult(......) 方法,从Intent 中获取数据。      

 

第十三章     工具栏

一、AppCompat

1、使用AppCompat库

如果想给老项目添加AppCompat库,需要:

  • 添加AppCompat依赖项
  • 使用一种AppCompat主题
  • 确保所有Activity都是AppCompatActivity子类

AppCompat库自带以下三种主题:

  • Theme.AppCompat:黑色主题
  • Theme.AppCompat.Light:浅色主题
  • Theme.AppCompat.Light.DarkActionBar:带黑色工具栏的浅色主题

应用级别的主题设置在 AndroidManifest.xml 文件中进行。

二、工具栏菜单

工具栏菜单由菜单项组成,它占据着工具栏的右上方区域。菜单项的操作应用于当前屏幕,甚至整个应用。

1、在xml文件中定义菜单

菜单是一种类似于布局的资源。创建菜单定义文件并将其放置在res/menu目录下,Android会自动生成相应的资源ID。随后,在代码中实例化菜单时,就可以直接使用。

菜单文件:

<menu xmlns:android="http://schemas.android.com/apk/res/android" 
      xmlns:app="http://schemas.android.com/apk/res-auto"> 
     <item 
         android:id="@+id/new_crime" 
         android:icon="@android:drawable/ic_menu_add" 
         android:title="@string/new_crime" 
         app:showAsAction="ifRoom|withText"/> 
</menu>

showAsAction属性用于指定菜单项是显示在工具栏上,还是隐藏于溢出菜单。该属性当前设置为 ifRoom 和 withText 的组合值。因此,只要空间足够,菜单项图标及其文字描述都会显示在工具栏上。如果空间仅够显示菜单项图标,文字描述就不会显示。如果空间大小不够显示任何项,菜单项就会隐藏到溢出菜单中。如果溢出菜单包含其他项,它们就会以三个点表示(位于工具栏最右端)。

属性 showAsAction 还有另外两个可选值:always 和 never。不推荐使用always,应尽量使用 ifRoom 属性值,让操作系统决定如何显示菜单项。对于那些很少用到的菜单项,never属性值是个不错的选择。总之,为了避免用户界面混乱,工具栏上只应放置常用菜单项。

(1)app命名空间

         注意,不同于常见的android命名空间声明,该菜单文件使用xmlns标签定义了全新的app命名空间。指定showAsAction属性时,就用了这个新定义的命名空间。

         处于兼容性考虑,AppCompat库需要使用app命名空间。操作栏API随Android 3.0引入。为了支持各种旧系统版本设备,早期创建的AppCompat库捆绑了兼容版操作栏。这样一来,不管新旧,所有的设备都能用上操作栏。在运行Android 2.3或更早版本系统的设备上,菜单及其相应的XML文件确实是存在的,但是 android:showAsAction 属性是随着操作栏的发布才添加的。

         AppCompat库不希望使用原生showAsAction属性,因此,它提供了定制版 showAsAction 属性(app:showAsAction)。

(2)使用 Android Asset Studio

         应用使用的图标有两种:系统图标和项目资源图标。系统图标是Android操作系统内置的图标。android:icon的属性值@android:drawable/ic_menu_add 就引用了系统图标。

         在应用原型设计阶段,使用系统图标不会有什么问题;而在应用发布时,无论用户运行什么设备,最好能统一应用的界面风格。要知道,不同设备或操作系统版本间,系统图标的显示风格差别很大。有些设备的系统图标甚至与应用的整体风格完全不搭。

         一种解决方案是创建定制图标。这需要针对不同屏幕显示密度或各种可能的设备配置,准备不同版本的图标。

         另一种解决方案是找到适合应用的系统图标,将它们直接复制到项目的 drawable 资源目录中。系统图标可在Android SDK的安装目录下找到。如果是Mac计算机,路径通常为/Users/user/Library/Android/sdk;如果是Windows计算机,默认的路径是\Users\user\sdk。此外,还可以打开项目结构窗口,选择SDK Location来确认SDK的具体存放路径。打开SDK目录,可找到包括ic_menu_add 在内的Android系统资源。资源的具体目录是/platforms/android-25/data/res,路径中的数字25代表Android的API级别。

          还有第三种,也是最容易的解决方案:使用Android Studio内置的Android Asset Studio工具。在项目工具窗口中,右键单击drawable目录,选择new——>Image Asset菜单项。在 Icon Type一栏选 Action Bar and Tab Icons,在Name一栏输入自己定义的名称,Asset Type 处选 Clip Art,最后,更新Theme为HOLO_DARK。工具栏使用了深色系主题,那图标图像就应选浅色。

2、创建菜单

      在代码中,Activity类提供了管理菜单的回调函数。需要选项菜单时,Android会调用Activity的 onCreateOptionsMenu(Menu)方法。而Fragment也有一套自己的选项菜单回调函数,以下为创建菜单和响应菜单项选择事件的两个回调方法:

  • public  void  onCreateOptionsMenu(Menu  menu,MenuInflater  inflater)
  • public  boolean  onOptionsItemSelected(MenuItem  item)

      Fragment.onCreateOptionsMenu(Menu,MenuInflater)方法是由FragmentManager负责调用的。因此,当activity接收到操作系统的onCreateOptionsMenu(......)方法回调请求时,我们必须明确告诉FragmentManager:其管理的fragment应接收onCreateOptionsMenu(......)方法的调用指令。要通知FragmentManager,需调用以下方法:public  void  setHasOptionsMenu(boolean  hasMenu)

三、实现层级式导航

      后退键导航又称为临时性导航,只能返回到上一次浏览过的用户界面;而层级式导航可在应用内逐级向上导航。有了层级式导航,用户可点击工具栏左边的向上按钮向上导航。

      打开 AndroidManifest.xml,在 Activity 中添加 parentActivityName 属性,开启项目的层级导航。

<activity 
    android:name=".CrimePagerActivity" 
    android:parentActivityName=".CrimeListActivity"> 
</activity>

1、层级式导航的工作原理

      用户点击向上按钮自本Activity向上导航时,如下的 intent 会被创建:

Intent intent = new Intent(this, CrimeListActivity.class); 
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
startActivity(intent); 
finish();

      FLAG_ACTIVITY_CLEAR_TOP指示Android在回退栈中寻找指定的Activity实例。如果实例存在,则弹出栈内所有其他Activity,让启动的目标Activity出现在栈顶(显示在屏幕上)。

四、可选菜单项

      有一个方法:getString(int  resId,Object....formatArgs),接受字符串资源中占位符的替换值

例如:
字符串资源中有占位符:
<string name="subtitle_format">%1$d crimes</string>
方法:getString(int resId, Object...formatArgs)中resId是代表资源id,formatArgs就代表想要替换成的数据

1、切换菜单项标题

      调用 onOptionsItemSelected(MenuItem)方法的时候,传入的参数是用户点击的 MenuItem。虽然可以在这个方法里更新菜单项的文字,但设备旋转并重建工具栏时,子标题的变化会丢失。

      比较好的方法是在 onCreateOptionsMenu(......)方法内更新菜单项,并在用户点击子标题菜单项时重建工具栏。对于用户选择菜单项或重建工具栏的场景,都可以使用这段菜单项更新代码:

首先,新增跟踪记录子标题状态的成员变量:
private boolean mSubtitleVisible;

接着,用户点击菜单项时,在 onCreateOptionsMenu(...)方法内更新子标题,同时重建菜单项:
@Override 
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
    super.onCreateOptionsMenu(menu, inflater); 
    inflater.inflate(R.menu.fragment_crime_list, menu); 
    
    MenuItem subtitleItem = menu.findItem(R.id.show_subtitle);
    if (mSubtitleVisible) {
        subtitleItem.setTitle(R.string.hide_subtitle);
    } else {
        subtitleItem.setTitle(R.string.show_subtitle);
    }
} 

@Override 
public boolean onOptionsItemSelected(MenuItem item) { 
    switch (item.getItemId()) { 
        case R.id.new_crime: 
        ... 
        case R.id.show_subtitle: 
            mSubtitleVisible = !mSubtitleVisible;
            getActivity().invalidateOptionsMenu();
            
            updateSubtitle(); 
            return true; 
        default: 
            return super.onOptionsItemSelected(item); 
    } 
}

最后,根据 mSubtitleVisible 变量值,联动菜单项标题与子标题:
private void updateSubtitle() { 
    CrimeLab crimeLab = CrimeLab.get(getActivity()); 
    int crimeCount = crimeLab.getCrimes().size(); 
    String subtitle = getString(R.string.subtitle_format, crimeCount); 
    
    if (!mSubtitleVisible) {
         subtitle = null;
    }
 
    AppCompatActivity activity = (AppCompatActivity) getActivity(); 
    activity.getSupportActionBar().setSubtitle(subtitle); 
}

五、工具栏与操作栏

工具栏与操作栏有什么区别?

      首先,两者给我们最直观的印象就是工具栏界面更美观。

      除了视觉上的差异,在使用上工具栏比操作栏更灵活。比如,整个应用只能配置一个操作栏且位置及尺寸必须固定(在屏幕顶部),工具栏就没有这些限制。可以在屏幕的任何位置摆放工具栏,甚至可以在同一屏配置多个工具栏。

      此外,工具栏还能支持内嵌视图和调整高度。

六、挑战练习:复数字符串资源

      只有一条 crime 记录的时候,显示总记录数的子标题依然会显示 1 crimes,想要显示单数形式 1 crime。

      实现思路:

首先,在strings.xml文件中定义复数字符串资源:
<plurals name="subtitle_plural"> 
    <item quantity="one">%1$d crime</item> 
    <item quantity="other">%1$d crimes</item> 
</plurals>

然后,使用getQuantityString方法正确处理单复数问题:
int crimeSize = crimeLab.getCrimes().size(); 
String subtitle = getResources() 
    .getQuantityString(R.plurals.subtitle_plural, crimeSize, crimeSize);

      这个代码写法我测试过,但是并不管用,一条记录的时候依旧显示的是复数形式。这个还需调研。

      有篇帖子讲Android中的单复数功能:https://blog.csdn.net/ouyang_peng/article/details/88529707?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值