美文网首页
Android编程权威指南(第二版)学习笔记(十七)—— 第17

Android编程权威指南(第二版)学习笔记(十七)—— 第17

作者: kniost | 来源:发表于2017-05-17 15:43 被阅读83次

    本章介绍了如何写一个双版面 fragment 的布局,并对符合要求的设备进行适配,还介绍了回调接口的使用。
    GitHub 地址:
    完成17章

    对平板设备来说,使用主从用户界面将会得到更好的体验,在这章我们将对其使用,传递数据的方式进行探究。

    1. 增加布局灵活性

    要实现双版面的布局,需要完成如下任务:

    1. 修改 SingleFragmentActivity,使其不再硬编码实例化布局
    2. 创建包含两个 fragment 容器的布局
    3. 修改 CrimeListActivity,实现在手机设备上实例化单版面布局,在平板设备上实例化双版面布局

    1.1 修改抽象类 SingleFragmentActivity

    在其中加入一个 protected 方法,返回 activity 需要的 ResId,这样对于继承 SingleFragmentActivity 的 activity 可以重写该函数以返回自己需要的 ResId。

    @LayoutRes
    protected int getLayoutResId() {
        return R.layout.activity_fragment;
    }
    

    1.2 使用别名资源

    我们想让最小屏幕宽度 600dp 的设备使用双版面界面,其他的使用单版面界面,那么对于不同的设备,使用的布局就不同。要让不同的设备使用不同的布局资源,有两种方法:

    1. 让 res/layout/目录中的文件使用资源修饰符。如果想使用activity_masterdetail.xml布局文件, 就需要将activity_fragment.xml的内容复制到res/layout/activity_masterdetail.xml中,将activity_twopane.xml的内容复制到res/layout-sw600dp/activity_masterdetail.xml中。这样做最明显的缺点就是数据冗余,因为每个布局文件都要复制一份。

    2. 使用别名资源。别名资源是一种指向其他资源的特殊资源。它存放在 res/values/目录下,并按照约定定义在 refs.xml 文件中。比如在默认的 values 文件夹下面新建一个 refs.xml,然后写入代码:

      <?xml version="1.0" encoding="utf-8"?>
      <resources>
          <item name="activity_masterdetail" type="layout">@layout/activity_fragment</item>
      </resources>
      

      再新建一个最小宽度600dp 的 refs.xml(即在 values-sw600dp 目录下),写入双版面的 layout 资源:

      <?xml version="1.0" encoding="utf-8"?>
      <resources>
          <item name="activity_masterdetail" type="layout">@layout/activity_twopane</item>
      </resources>
      

      这样,在 CrimeListActivity 中只要引用 R.layout.activity_masterdetail 即可

    2. Activity:Fragment 的托管者

    为了保证 fragment 的独立性,即不需要了解其托管者的工作,但要想在 fragment 生命周期没有结束的时候传递数据出去,就要使用回调接口。

    回调就相当于一个委托,首先 fragment 自己定义回调的接口,托管的 acitivity 来实现这个接口,接着 fragment 需要持有实现了自己定义接口的对象,以便自己可以实时调用。

    对于一个回调接口而言,fragment 只要求实现这个接口的类在函数里要做的是什么,却不知道实现类到底会做什么,每个实现类有自己的方法来实现。

    2.1 CrimeListFragment 的回调接口

    对于 CrimeListFragment,其所能响应的就是点击列表中的某一项,那么它的回调接口定义如下:

    public interface Callbacks {
        void onCrimeSelected(Crime crime);
    }
    

    然后应该在需要托管的 Activity 中实现该接口,在这里是 CrimeListActivity:

    // 省略 implement 以节约版面
    @Override
    public void onCrimeSelected(Crime crime) {
        // 如果发现布局里没有包含详情 fragment 容器的 id,
        // 就启动单独的 activity 用于显示详情
        if (findViewById(R.id.detail_fragment_container) == null) {
            Intent intent = CrimePagerActivity.newIntent(this, crime.getId());
            startActivity(intent);
        } else {
        // 否则就将 detail 页面放到 fragment 容器中
            Fragment newDetail = CrimeFragment.newInstance(crime.getId());
    
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.detail_fragment_container, newDetail)
                    .commit();
        }
    }
    

    在 CrimeListFragment 中持有实现接口的 activity 的引用,然后在生命周期末去除引用以便内存的回收

    // CrimeListFragment
    private Callbacks mCallbacks;
    
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mCallbacks = (Callbacks) context;
    }
    
    // 中间的函数……
    
    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = null;
    }
    

    最后修改 onClick 事件,调用 mCallbacks.onCrimeSelected(Crime crime) 即可。这样以后,在双版面视图中点击列表中的某一项,在详情版面中就会显示相应的信息。

    但是有一个问题,那就是在详情页(CrimeFragment)更改信息,在列表页没有任何响应,因为 CrimeListFragment 不会暂停,所以也就不会刷新,所以下一步要在 CrimeFragment 中定义回调接口, 让托管 activity 去更新 CrimeListFragment。

    2.2 CrimeFragment 的回调接口

    首先定义回调接口,这里想让托管者做的就是在 Crime 详情进行更新时更新列表

    // CrimeFragment
    private Callbacks mCallbacks;
    
    public interface Callbacks {
        void onCrimeUpdated(Crime crime);
    }
    
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mCallbacks = (Callbacks) context;
    }
    
    // 中间的函数……
    
    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = null;
    }
    

    在 CrimeListActivity 中实现该接口:

    @Override
    public void onCrimeUpdated(Crime crime) {
        CrimeListFragment listFragment = (CrimeListFragment)
                getSupportFragmentManager()
                        .findFragmentById(R.id.fragment_container);
        listFragment.updateUI();
    }
    

    由于只要托管 CrimeFragment 的 activity 都应该实现其回调接口,所以在 CrimePagerActivity 中提供一个空的接口实现

    之后在每次数据发生更改时都调用 mCallbacks.onCrimeUpdated(mCrime);即可。书上将更新模型层也放到了一起。

    3. 挑战的后遗症:删除 Crime

    还记得我们在 ToolBar 那一章加入的挑战吗,就是删除一个 Crime,对于 CriminalIntent 这个应用来说,双版面和单版面的删除操作应该有着不同的结果,但这些行为在书上没有定义,所以我们再自己想一种解决方案,以便确立如何写接下来的补充程序。

    1. 双版面的界面下,点击删除应该要让左边的列表中去掉删除的那一项,并且详情页也要改为已存在的某一项的详情,为了方便实现,我们在这里改为已存在的第一项。如果只有最后一项并且点击了删除,那么右边应该要变成空白。
    2. 单版面的界面下,点击删除就直接删去该条记录,然后结束 activity。

    在这里我在 CrimeFragment 的 Callbacks 接口中加入了 onCrimeDelete(Crime crime) 方法与 onCrimeAllDeleted(Crime crime) 方法,在 CrimeListActivity 中实现如下:

    @Override
    public void onCrimeDeleted(Crime crime) {
        // 如果只是删除了一个,而还有其他的 Crime 的话,
        // 就相当于选中一个 Crime,这里传过来的应该是第一个 Crime
        onCrimeSelected(crime);
    }
    
    @Override
    public void onCrimeAllDeleted(Crime crime) {
        // 如果全部删除,就直接将该 fragment 移去
        CrimeFragment fragment = (CrimeFragment)
                getSupportFragmentManager()
                        .findFragmentById(R.id.detail_fragment_container);
        if (fragment != null) {
            getSupportFragmentManager()
                    .beginTransaction()
                    .remove(fragment)
                    .commit();
        }
        // 并且更新列表页
        onCrimeUpdated(crime);
    }
    

    在 CrimePagerActivity 中也要实现这两个方法,但是对于这个 activity 来说只要进行 finish() 即可。

    在删除按钮的选中监听中:

    CrimeLab.get(getActivity()).deleteCrime(mCrime);
    if (CrimeLab.get(getActivity()).getCrimes().isEmpty()) {
        mCallbacks.onCrimeAllDeleted(mCrime);
    } else {
        mCrime = CrimeLab.get(getActivity()).getCrimes().get(0);
        mCallbacks.onCrimeDeleted(mCrime); // 这里相当于选中第一个
        updateCrime(); // 这里面升级了数据层并且更新了列表
    }
    return true;
    

    整个程序就此完成啦~


    GitHub Page: kniost.github.io
    简书:http://www.jianshu.com/u/723da691aa42

    相关文章

      网友评论

          本文标题:Android编程权威指南(第二版)学习笔记(十七)—— 第17

          本文链接:https://www.haomeiwen.com/subject/czmdxxtx.html