PageLayoutDemo

Introduction: 一款简单的 page 切换 空布局、错误布局、加载布局,支持一键配置、定义,不需要 xml 编写
More: Author   ReportBugs   
Tags:

一款简单的 page 切换 空布局、错误布局、加载布局,支持一键配置、定义,不需要 xml 编写

该功能是支持单独为某个布局设置状态改变的,比如很多同学提到的我一个 listview 的数据没有获取到,fun initPage(targetView: Any),这个 targetView 你只需要设置成你的 listview 或者包裹你 listview 的 parent 布局就 OK 了,具体原理可以看下面的代码解析啊,遍历获取索引,然后记录索引值....

如果您想看 JAVA 版本,点击https://github.com/Hankkin/PageLayoutDemojava

项目中我们经常会用到的加载数据,加载完数据后显示内容,如果没有数据显示一个空白页,这是如果网络错误了显示一个网络错误页,自定义一个 PageLayout。

DownLoad

https://fir.im/pagelayout

绪论

Android 中经常使用一个空白页和网络错误页用来提高用户体验,给用户一个较好的感官,如果获取到的数据为空,那么会显示一个空白数据页,如果在获取数据的过程中网络错误了,会显示一个网络异常页,像最近比较火的某东这样,见下图。网上也有一些开源的组件,大部分都是自定义继承某个布局在 xml 中让其作为跟布局,然后将自己的内容布局添加进去,效果也都不错,但是个人总觉得稍微有些麻烦,不是那么灵活,n 多个 xml 布局都去定义,写的心烦,所以有了今天的主角。

思考

实现的思路实际上是和上面说的一样,只不过换了一种方式,我们手动获取到 contentView,将它从 DecorView 中移除,然后交给 PageLayout 取管理。当时考虑的时候就是不想在每个 xml 中去写页面切换的布局,那么我们可不可以用 Java 代码去控制?带着下面几个问题一起来看一下。

  • 1.自定义一个布局让其作为跟布局
  • 2.提供切换加载 loading空白页 empty错误页 errror内容页 content功能
  • 3.怎么让其取管理上边的四个页面?
  • 4.contentView 怎么添加?
  • 5.如果我想切换的跟布局不是个 Activity 或者 Fragment 怎么办?
  • 6.因为切换页面状态的功能一般都是一个 APP 统一的,那么可不可以一键配置呢?

实现

1.代码设计

首先我们定义 PageLayout 继承 FrameLayout 或者 LinearLayou 或者其他的布局都可以,然后我们需要提供切换四个布局的功能,当然如果支持自定义就更好了,还有状态布局里面的一些属性,还方便一键配置,所以最后采用了 Builder 模式来创建,使用方式就和 Android 里面的AlertDialog一样,通过 Builder 去构建一个 PageLayout。最后的样子是长这样的:

方法 注释
showLoading() 显示 loading
showError() 显示错误布局
showEmpty() 显示空布局
hide() 显示内容布局
Builder
setLoading() setLoadingText()
setError() setDefaultLoadingBlinkText()
setEmpty() setLoadingTextColor()
setDefaultEmptyText() setDefaultLoadingBlinkColor()
setDefaultEmptyTextColor() setDefaultErrorText()
setDefaultErrorTextColor() setEmptyDrawable()
setErrorDrawable()

默认样式

PageLayout.Builder(this)
                .initPage(ll_default)
                .setOnRetryListener(object : PageLayout.OnRetryClickListener{
                    override fun onRetry() {
                        loadData()
                    }

                })
                .create()

自定义样式

PageLayout.Builder(this)
                .initPage(ll_demo)
                .setLoading(R.layout.layout_loading_demo)
                .setEmpty(R.layout.layout_empty_demo,R.id.tv_page_empty_demo)
                .setError(R.layout.layout_error_demo,R.id.tv_page_error_demo,object : PageLayout.OnRetryClickListener{
                    override fun onRetry() {
                        loadData()
                    }
                })
                .setEmptyDrawable(R.drawable.pic_empty)
                .setErrorDrawable(R.drawable.pic_error)
                .create()

2.设置 PageLayout

考虑好了代码设计方式之后,我们来具体实现功能,首先需要考虑上面的 5,6 点:

contentView 怎么添加?

如果我想切换的跟布局不是个 Activity 或者 Fragment 怎么办?

1.Activity

如果我们要切换的跟布局是个 Activity 时,首先我们需要了解一下 Android 中的 setContentView()方法,很熟悉,是我们新建完 Activity 后默认会在生命周期方法 onCreate()中默认存在的,那么 setContentView()做了些什么呢?我们先看一张图:

image

一个 Activity 是通过 ActivityThread 创建出来的,创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联,setContentView()是通过 getWindow()调用的,这里的 window 实际初始化的时候初始化为 PhoneWindow,也就是说 Activity 会调用 PhoneWindow 的 setContentView()将 layout 布局添加到 DecorView 上,而此时的 DecorView 就是那个最底层的 View。然后通过 LayoutInflater.infalte()方法加载布局生成 View 对象并通过 addView()方法添加到 Window 上,(一层一层的叠加到 Window 上)所以,Activity 其实不是显示视图,Window 才是真正的显示视图。

再来看上面的那张图,可以说 DecorView 是一个界面的真正跟布局,TitleView 我们可以通过设置 theme 样式显示隐藏的,状态布局切换时我们不考虑 TitleView,我们只需要考虑 ContentView,而 ContentView 也就是android.R.id.content,知道了这些我们来看看怎么获取将 contenView 交给 PageLayout 管理。

2.Fragment、View

如果我们要切换的跟布局是个 Fragment、View 时,我们只需要获取到它的 parent

3.PageLayout 设置跟布局

获取到了 contentView 跟布局后,我们要移除自己的显示内容的布局,并把这个布局交给 PageLayout,下面看一下代码,注释的很详细了

 /**
         * set target view for root
         */
        fun initPage(targetView: Any): Builder {
            var content: ViewGroup? = null
            when (targetView) {
                is Activity -> {    //如果是 Activity,获取到 android.R.content
                    mContext = targetView
                    content = (mContext as Activity).findViewById(android.R.id.content)
                }
                is Fragment -> {    //如果是 Fragment 获取到 parent
                    mContext = targetView.activity!!
                    content = (targetView.view)?.parent as ViewGroup
                }
                is View -> {        //如果是 View,也取到 parent
                    mContext = targetView.context
                    try {
                        content = (targetView.parent) as ViewGroup
                    } catch (e: TypeCastException) {
                    }
                }
            }
            val childCount = content?.childCount
            var index = 0
            val oldContent: View
            if (targetView is View) {   //如果是某个线性布局或者相对布局时,遍历它的孩子,找到对应的索引,记录下来
                oldContent = targetView
                childCount?.let {
                    for (i in 0 until childCount) {
                        if (content!!.getChildAt(i) === oldContent) {
                            index = i
                            break
                        }
                    }
                }

            } else {    //如果是 Activity 或者 Fragment 时,取到索引为第一个的 View
                oldContent = content!!.getChildAt(0)
            }
            mPageLayout.mContent = oldContent   //给 PageLayout 设置 contentView
            mPageLayout.removeAllViews()    
            content?.removeView(oldContent)     //将本身 content 移除,并且把 PageLayout 添加到 DecorView 中去
            val lp = oldContent.layoutParams
            content?.addView(mPageLayout, index, lp)
            mPageLayout.addView(oldContent)
            initDefault()   //设置默认状态布局
            return this
        }

这样我们就解决了上面的 5,6 的问题。

4.其他

  • 因为错误布局中一般都包括一个点击重试的功能,如果你需要自定义布局,你可以在配置 PageLayout 之前,设置好错误布局和点击事件,然后 setError 进去,同时也提供了一个默认方式的方法
fun setError(errorView: Int, errorClickId: Int, onRetryClickListener: OnRetryClickListener)
  • 考虑到此功能的 APP 统一性,所以并没有提供过多的自定义功能,如果你需要的话,你都可以提前设置好 View,然后进行 set
  • 之前和同事讨论,xml 形式和代码形式哪个更方便更灵活,这些都属于个人喜好吧,如果你更喜欢在 xml 里写的话,你可以进行改造,也挺简单,目前没提供 xml 方式,PageLayout 的初衷就是模仿 AlertDialog 方式,随时随地使用状态布局切换
  • 你也可以在 BaseActivity 和 BaseFragment 中进行 PageLayout 的初始化,Demo 中未使用,自行解决

效果图

image

代码已经上传到 Githubhttps://github.com/Hankkin/PageLayoutDemo

Reading:一款不错的 Material Desgin 风格的 Kotlin 版本的开源 APP https://github.com/Hankkin/Reading

欢迎大家 Follow、star、fork,谢谢 如果有不合适的地方,请提 issues 讨论指正

Support Me
Apps
About Me
Google+: Trinea trinea
GitHub: Trinea