今日份学习,综合之前学习过的知识,学习google推出的总结性APP - Sunflower

https://github.com/android/sunflower

查漏补缺的同时将此APP进行本地化适配,因为使用的是wikimedia的源获取数据。

SunFlower

首先拉代码,跑起来,忘记有代码,然后看一下整个APP的结构。

首页的上半部分,一个title + header是固定,这块可以写在Activity中,header中有两个button,点击切换Fragment。

我的花园-Fragment: 用了一个GridView来展示添加的花园,这块的数据应该是是存入数据库的。

我的花园-植物详情-Fragment:点击我的植物后会进入到植物详情的Fragment,展示植物的详细信息,有一个返回按钮和分享按钮。

植物目录-Fragment: 同样是一个GridView来展示植物列表,这块的数据应该是从网络上拉取,缓存到数据库的。

植物目录-植物详情-Fragment:植物详情的Fragment就是展示从wiki上拉下来的植物数据,和我的花园的植物详情可以用一个Fragment。

植物目录-植物详情-添加植物-button:植物图片的下方有一个加号,可以进行植物的添加,植物添加后入库,然后我的花园页面显示。

还有一个给自己添加的植物浇水的功能,由于等待浇水的时间都比较长,所以先开发这些,后面再来浇水。梳理完毕,剩下的就是开整,使用现成的项目学习的话比较省事的是不用寻找静态资源。

IGarden

新建项目,起名就要IGarden吧,毕竟整个项目是个花园。为了方便,首先吧Sunflower需要的所有依赖以及静态资源全部导入。

CoordinatorLayout

这个Layout是2015年I/O大会上发布的Android Design Support Library,主要就是更好的使用material design。CoordinatorLayout是库中的一个FrameLayout,继承自ViewGroup。

顾名思义,协调者布局。为啥叫协调者?有点懵逼。CoordinatorLayout是一个"super-powered FrameLayout"。看了不少博文,终于还是有一点理解了,协调者协调的是child之间的联动,比如说有一个TopBar, TopBar下面有一个RecyclerView,当RecyclerView被滑动的时候如果我想隐藏TopBar怎么办?这时候就可以用协调者布局了。

那CoordinatorLayout是如何做到协调的呢?在CoordinatorLayout内部,每一个child都必须有一个Behavior,CoordinatorLayout根据这个Behavior去进行协调。那么Behavior到底是啥呢?

其实Behavior里面有一系列的方法,如下面所示

1
onStartNestedScroll(), onNestedScrollAccepted(),onStopNestedScroll(),onNestedScroll(), onNestedPreScroll(),onNestedFling(),onNestedPreFling()

所以说怎么协调的就很明了了,实际上就是使用触摸操作来控制View的滑动(我自己的理解)。

NestedScrolling

在Behavior方法中,这些方法的都有一个Nested的的标志,这个Nested又是干什么的呢?不得不说,Android东西可真多。先看下面的博文,但是下面的博文又说先让了解时间分发机制。

https://www.jianshu.com/p/aff5e82f0174

Android事件分发机制

关于Android事件,最常见的应该就是点击事件吧,对于一个View可以设置它的点击事件监听器。但是除了这个,还有巨多事件,从手指接触屏幕到手指离开屏幕的这一过程产生的一系列事件都叫做事件列。

具体的有下面的几个:

  • MotionEvent.ACTION_DOWN 按下View开始

  • MotionEvent.ACTION_UO 抬起View

  • MotionEvent.ACTION_MOVE 滑动View

  • MotionEvent.ACTION_CANCEL 结束时间,非人为原因

ViewPager2

有点难以理解Sunflower如何做到在两个fragment之间滑动的,所以看了代码,发现是使用了ViewPager2+TabLayout来实现的。整体的代码实现其实不难,下面直接上代码吧。

首先在布局文件中定义TabLayout和ViewPage2,在ViewPager中可以添加控件,到ViewPager2就不行了,只能和TabLayout平级。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/header_title"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:gravity="center"
            android:text="@string/app_name"
            android:textAppearance="?attr/textAppearanceHeadline5"
            android:textColor="@color/sunflower_white"
            android:textSize="30sp" />

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tab_layout"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            style="@style/Widget.MaterialComponents.TabLayout.Colored"
            app:tabIconTint="@drawable/tab_icon_color_selector"
            app:tabTextColor="?attr/colorPrimaryDark" />

        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>

为了方便,直接使用了线性布局。

由于ViewPager2里面使用RecyclerView,所以它一样要使用Adapter,下面就是Pager的Adapter的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const val GARDEN_PAGE_INDEX = 0
const val PLANT_LIST_PAGE_INDEX = 1

class PagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {

    private val tabFragmentsCreators: Map<Int, () -> Fragment> = mapOf(
        GARDEN_PAGE_INDEX to { GardenFragment() },
        PLANT_LIST_PAGE_INDEX to { PlantListFragment() }
    )

    override fun getItemCount(): Int = tabFragmentsCreators.size

    override fun createFragment(position: Int): Fragment {
        return tabFragmentsCreators[position]?.invoke() ?: throw IndexOutOfBoundsException()
    }
}

首先我们注意到这个PagerAdapter继承了FragmentStateAdapter,来实现滑动的效果。PageAdapter中定义了一个Map<Int, Fragment>以及Fragment的索引,当切换Fragment的时候即调用createFragment方法进行新的Fragment的创建。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        val binding: FragmentMyGardenBinding = DataBindingUtil.inflate(inflater,
            R.layout.fragment_my_garden, container, false)
        val viewPager = binding.viewPager
        val tabLayout = binding.tabLayout

        viewPager.adapter = PagerAdapter(this)

        TabLayoutMediator(tabLayout, viewPager) {
            tab, pos ->
                tab.text = getTabTitle(pos)
                tab.setIcon(getTabIcon(pos))
        }.attach()

在Fragment中使用起来也比较简单,首先绑定viewPager2的控件和tabLayout的控件,然后使用TabLayoutMediator来关联viewPager2和TabLayout,最后要加上attach(),这样才能完成二者的绑定。

这样就完成了ViewPager2+TabLayout的简单使用,在原项目中还加入了AppBarLayout,这个暂时不知道干什么用的,仅用上面的即可完成需求的实现。