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的,什么意思呢?就是“一刀切”,不让修改了。