Files
PageLayoutDemo/README.md

196 lines
10 KiB
Markdown
Raw Permalink Normal View History

2018-09-09 22:56:43 +08:00
# PageLayoutDemo
一款简单的page切换 空布局、错误布局、加载布局支持一键配置、定义不需要xml编写
2018-09-10 10:33:36 +08:00
> **该功能是支持单独为某个布局设置状态改变的比如很多同学提到的我一个listview的数据没有获取到fun initPage(targetView: Any),这个targetView你只需要设置成你的listview或者包裹你listview的parent布局就OK了具体原理可以看下面的代码解析啊遍历获取索引然后记录索引值....**
2018-10-13 11:17:10 +08:00
**如果您想看JAVA版本点击[https://github.com/Hankkin/PageLayoutDemojava](https://github.com/Hankkin/PageLayoutDemojava)**
2018-09-09 22:56:43 +08:00
> 项目中我们经常会用到的加载数据加载完数据后显示内容如果没有数据显示一个空白页这是如果网络错误了显示一个网络错误页自定义一个PageLayout。
2018-09-10 14:37:30 +08:00
## DownLoad
[https://fir.im/pagelayout](https://fir.im/pagelayout)
<img width="300" height=“300” src="http://lc-47sd2ifv.cn-n1.lcfile.com/6c9753f0d3a452ef9bb2.png"></img>
2018-09-09 22:56:43 +08:00
## 绪论
Android中经常使用一个空白页和网络错误页用来提高用户体验给用户一个较好的感官如果获取到的数据为空那么会显示一个空白数据页如果在获取数据的过程中网络错误了会显示一个网络异常页像最近比较火的某东这样,见下图。网上也有一些开源的组件大部分都是自定义继承某个布局在xml中让其作为跟布局然后将自己的内容布局添加进去效果也都不错但是个人总觉得稍微有些麻烦不是那么灵活n多个xml布局都去定义写的心烦所以有了今天的主角。
<img width="173" height=“274” src="http://lc-47sd2ifv.cn-n1.lcfile.com/da31e9a1ddf099d15ec6.png"></img>
<img width="173" height=“274” src="http://lc-47sd2ifv.cn-n1.lcfile.com/fad14cf4faa9d673b358.png"></img>
## 思考
实现的思路实际上是和上面说的一样只不过换了一种方式我们手动获取到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。最后的样子是长这样的
2018-09-17 10:01:49 +08:00
| 方法 | 注释 |
| :------------------------- | ----------------------------- |
| showLoading() | 显示loading |
| showError() | 显示错误布局 |
| showEmpty() | 显示空布局 |
| hide() | 显示内容布局 |
| **Builder** | |
| setLoading() | setLoadingText() |
| setError() | setDefaultLoadingBlinkText() |
| setEmpty() | setLoadingTextColor() |
| setDefaultEmptyText() | setDefaultLoadingBlinkColor() |
| setDefaultEmptyTextColor() | setDefaultErrorText() |
| setDefaultErrorTextColor() | setEmptyDrawable() |
| setErrorDrawable() | |
2018-09-09 22:56:43 +08:00
**默认样式**
```
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)
2018-09-09 23:18:54 +08:00
.setEmpty(R.layout.layout_empty_demo,R.id.tv_page_empty_demo)
2018-09-09 22:56:43 +08:00
.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
> 考虑好了代码设计方式之后我们来具体实现功能首先需要考虑上面的56点
**contentView怎么添加**
**如果我想切换的跟布局不是个Activity或者Fragment怎么办**
#### 1.Activity
如果我们要切换的跟布局是个Activity时首先我们需要了解一下Android中的setContentView()方法很熟悉是我们新建完Activity后默认会在生命周期方法onCreate()中默认存在的那么setContentView()做了些什么呢?我们先看一张图:
![image](http://lc-47sd2ifv.cn-n1.lcfile.com/f07096d512318918580c.png)
> 一个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
}
```
这样我们就解决了上面的56的问题。
### 4.其他
- **因为错误布局中一般都包括一个点击重试的功能如果你需要自定义布局你可以在配置PageLayout之前设置好错误布局和点击事件然后setError进去同时也提供了一个默认方式的方法**
```
fun setError(errorView: Int, errorClickId: Int, onRetryClickListener: OnRetryClickListener)
```
- **考虑到此功能的APP统一性所以并没有提供过多的自定义功能如果你需要的话你都可以提前设置好View然后进行set**
- **之前和同事讨论xml形式和代码形式哪个更方便更灵活这些都属于个人喜好吧如果你更喜欢在xml里写的话你可以进行改造也挺简单目前没提供xml方式PageLayout的初衷就是模仿AlertDialog方式随时随地使用状态布局切换**
- **你也可以在BaseActivity和BaseFragment中进行PageLayout的初始化Demo中未使用自行解决**
## 效果图
![image](http://lc-47sd2ifv.cn-n1.lcfile.com/a2a787511ddb0461bfb1.gif)
代码已经上传到Github[https://github.com/Hankkin/PageLayoutDemo](https://github.com/Hankkin/PageLayoutDemo)
**Reading一款不错的Material Desgin风格的Kotlin版本的开源APP**
[https://github.com/Hankkin/Reading](https://github.com/Hankkin/Reading)
欢迎大家Follow、star、fork谢谢
如果有不合适的地方请提issues讨论指正