Android学习笔记(十一)

Android权威编程指南第11章挑战练习解答及本章总结

本章总结

ViewPaper可以实现左右滑动屏幕显示不同的页面。与RecyclerView类似的是,同样需要Adapter提供视图和绑定数据。这里使用的是FragmentStatePaperAdapter。

  • getItem 获得的是临近视图的position,不是初始的position
  • gerCount 获得总视图数目

既然初始的position没有设置,就需要调用setCuurentItem来设置初始的视图。这里通过CrimeListFragment传入的Intent取得点击Crime的UUID,然后检索,设置初值position。

使用FragmentStatePagerAdapter方法,这种方法比FragmentPagerAdapter更节省内存。

恢复CrimeFragment的边距

方法一:

由于ViewPager的布局参数不支持边距设置,所以android:layout_margin="16dp" 并没有生效。这时候我们只需要把代码替换为android:padding="16dp"即可。

padding是内容与view的距离。margin是view与view的距离。

方法二:

我在解决第二个问题的时候将两个按钮写进了fragment_crime(后来发现是错误的),由于使用了Constraint布局,可以手动设置边距。

添加两个跳转按钮

我之前想在fragment_crime中写布局,通过CrimeFragment调用。但是FragmentStatePaperAdapter在CrimePaperActivity中,如果想通过fragment内发生跳转,需要把消息传给Activity,比较麻烦。在网上查找资料后,使用了下面这个方案。

首先在activity_crime_paper中更改布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

    <androidx.viewpager.widget.ViewPager
            android:id="@+id/crime_view_paper"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"></androidx.viewpager.widget.ViewPager>

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="16dp"
            android:weightSum="2">

        <Button
                android:id="@+id/preButton"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/跳到第一条"
                android:textAllCaps="false" />

        <Button
                android:id="@+id/lastButton"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/跳到最后一条"
                android:textAllCaps="false" />


    </LinearLayout>

</LinearLayout>

这里需要说明一下,首先使用LinerLayout线性布局。将ViewPager放在LinerLayout之中,再利用layout_weight分开布局为上下两个部分。

weightSum和layout_weight

在LinearLayout的XML中,举个例子:android:weightSum=”5″ 表示这个LinearLayout总共平分成5块大小区域;然后再LinearLayout里面的控件,使用android:layout_wetght=“1”,这表示它占用整个布局的1/5。

在图中的布局里,只设置了两个布局部分中ViewPaper的layout_weight的权重,没有在页面的LinerLayout中给出weightSum,也没有再设置按钮的layout_weight。当这样设置时,两个布局的分配取决于按钮的高度,这里两个布局共同的资源是height,也就是高度。所以会出现如图的两个部分。

如果还没明白我可以再举一个例子,首先下面这两个按钮已经配置了weightSum和layout_weight这两个属性。

这样显示出来就是1:1分配

需要注意的是每个按钮的width需要设置成0,因为是按比例分配的,如果不为零还需要加上内容的宽度,就不是按比例分配了。

这样完成了题目中的要求,我现在要改写这个布局文件,按照缺失weightSum属性来演示。

可以看见布局是按照第二个按钮的长度分配的,因为水平是warp_content。如果我们把第一个weight也去掉,就变成普通的布局:

跳转逻辑

布局设计完成,需要在CrimePaperActivity中写跳转逻辑

跳转逻辑设置setCurrentItem即可,顾名思义“设置当前项目”。

这里有个小细节,此函数是有两个变量的,第一个变量显示当前显示的position,第二个是动画,设置成0即可立即跳转。有兴趣可以试一下。

设置按钮禁用

addOnPageChangeListener

和clickListener类似,这个方法用于监听跳转页面的手指动作。

此方法是在状态改变的时候调用,其中state这个参数有三种状态:

  • SCROLL_STATE_DRAGGING(1)表示用户手指“按在屏幕上并且开始拖动”的状态(手指按下但是还没有拖动的时候还不是这个状态,只有按下并且手指开始拖动后log才打出。)
  • SCROLL_STATE_IDLE(0)滑动动画做完的状态。
  • SCROLL_STATE_SETTLING(2)在“手指离开屏幕”的状态。

一个完整的滑动动作,三种状态的出发顺序为(1,2,0),当viewPager状态改变时候的代码块:

打印结果:

onPageScrolled(int position, float positionOffset, int positionOffsetPixels)

当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到调用。其中三个参数的含义分别为:

  • position :当前页面,即你点击滑动的页面(从A滑B,则是A页面的position。官方说明:Position index of the first page currently being displayed. Page position+1 will be visible if positionOffset is nonzero.)
  • positionOffset:当前页面偏移的百分比
  • positionOffsetPixels:当前页面偏移的像素位置

页面滚动时侯log演示如下:

日志打印:

onPageSelected(int position)

此方法是页面跳转完后得到调用,position是你当前选中的页面的Position(位置编号)(从A滑动到B,就是B的position)

我们只设置onPageSelected就可以

首先将按钮设置成可点,如果满足第一个或者最后一个就禁用相应按钮。

实现效果:

可以看到,向前跳转没有动画。

修复小bug

刚看到Shopkeeper的文章,当首先点击第一页时有bug,所以在匹配position时加上判断

任务完成

你可能注意到按钮的类型是final

因为在匿名内部类中调用外部属性,外部属性需要加final。

解释:

这要从闭包说起,匿名内部类和外部方法形成了一个闭包,因此,匿名内部类能够访问外部方法的变量,看起来是一种“天经地义”的事情,Java语言当然也需要实现这种特性,但是这里遇到了一个问题。

匿名内部类的生命周期可能比外部的类要长,因此访问外部局部变量有可能是访问不到的。

那怎么办呢?Java语言为了实现这种特性, 只好将外部的局部变量偷偷的赋值了一份给匿名内部类。那这样匿名内部类就可以肆无忌惮的访问外部局部变量了。

问题又来了,这种通过赋值的形式有一个缺陷,匿名内部类不可以修改“原来的局部变量”,因为是一份“复制品”,修改复制品对原变量没什么影响啊。

那怎么办? Java语言干脆强制要求被匿名内部类访问的外部局部变量必须是final的,什么意思呢?就是“一刀切”,不让修改了。

发表评论