Skip to content

Commit efce592

Browse files
committed
完善配置功能
1 parent 4425292 commit efce592

File tree

10 files changed

+154
-113
lines changed

10 files changed

+154
-113
lines changed

README.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ try {
2525
android:layout_width="match_parent"
2626
android:layout_height="match_parent"
2727
app:stateful_failure_layout="@layout/state_failure_layout">
28-
29-
<!--默认是success状态-->
28+
<!--注意上方的stateful_failure_layout属性,它引用了另外一个布局作为自己的failure状态下的视图-->
29+
30+
<!--没有指定state,则默认是success状态-->
3031
<RecyclerView
3132
android:layout_width="match_parent"
3233
android:layout_height="match_parent" />
@@ -45,18 +46,27 @@ try {
4546
app:stateful_animation_show="@anim/stateful_loading_show"
4647
app:stateful_animation_hide="@anim/stateful_loading_hide"
4748
app:layout_state="loading" />
48-
49+
4950
</StatefulLayout>
5051
```
51-
* 如果想统一修改除success以外所有状态的默认视图,可在当前正在应用的主题资源中指定:
52+
* 如果想统一修改除success以外所有状态的默认视图,可在当前正在应用的主题资源中添加以下属性并指定新的`Style`:
5253
```xml
53-
<!--默认视图-->
54-
<item="stateful_empty_layout">@layout/state_empty_layout</item>
55-
<item="stateful_loading_layout">@layout/state_loading_layout</item>
56-
<item="stateful_failure_layout">@layout/state_failure_layout</item>
57-
<!--包括默认动画效果-->
58-
<item="stateful_animation_show">@layout/state_show_anim</item>
59-
<item="stateful_animation_hide">@layout/state_hide_anim</item>
54+
<style name="...">
55+
...
56+
<!--为statefulLayoutStyle属性设置新的样式-->
57+
<item name="statefulLayoutStyle">@style/ExampleStatefulLayoutStyle</item>
58+
</style>
59+
60+
<!--添加新的样式,继承自默认的样式-->
61+
<style name="ExampleStatefulLayoutStyle" parent="StatefulLayoutStyle">
62+
<!--修改默认视图-->
63+
<item name="stateful_empty_layout">@layout/empty_layout</item>
64+
<item name="stateful_loading_layout">@layout/loading_layout</item>
65+
<item name="stateful_failure_layout">@layout/failure_layout</item>
66+
<!--修改默认动画效果-->
67+
<item="stateful_animation_show">@layout/state_show_anim</item>
68+
<item="stateful_animation_hide">@layout/state_hide_anim</item>
69+
</style>
6070
```
6171

6272
### 引入

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22
buildscript {
33
ext {
4-
kotlin_version = '1.4.32'
4+
kotlin_version = '1.5.21'
55
compile_sdk_version = 30
66
target_sdk_version = 30
77
}
@@ -10,7 +10,7 @@ buildscript {
1010
jcenter()
1111
}
1212
dependencies {
13-
classpath "com.android.tools.build:gradle:4.1.3"
13+
classpath "com.android.tools.build:gradle:4.2.2"
1414
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
1515
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1616
}

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip

stateful.layout/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
apply plugin: 'com.android.library'
22
apply plugin: 'kotlin-android'
3-
apply plugin: 'kotlin-parcelize'
43
apply plugin: 'com.github.dcendents.android-maven'
54

65
group = 'com.github.xiazunyang'

stateful.layout/src/main/java/cn/numeron/stateful/layout/StatefulLayout.kt

Lines changed: 113 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,123 +2,132 @@ package cn.numeron.stateful.layout
22

33
import android.content.Context
44
import android.util.AttributeSet
5+
import android.view.LayoutInflater
56
import android.view.View
67
import android.view.ViewGroup
78
import android.view.animation.AnimationUtils
89
import android.widget.FrameLayout
910
import cn.numeron.common.State
1011

11-
class StatefulLayout @JvmOverloads constructor(c: Context, a: AttributeSet? = null, i: Int = 0) : FrameLayout(c, a, i) {
12-
13-
private var previousState: State? = null
12+
class StatefulLayout @JvmOverloads constructor(
13+
c: Context,
14+
a: AttributeSet? = null,
15+
i: Int = R.attr.statefulLayoutStyle,
16+
s: Int = R.style.StatefulLayoutStyle
17+
) : FrameLayout(c, a, i, s) {
1418

1519
var state: State = State.Empty
1620
set(value) {
1721
if (value != field) {
18-
previousState = field
1922
field = value
2023
}
2124
changeView()
2225
}
2326

24-
private val emptyViews
25-
get() = findChildrenByState(State.Empty)
26-
27-
private val loadingViews
28-
get() = findChildrenByState(State.Loading)
29-
30-
private val failureViews
31-
get() = findChildrenByState(State.Failure)
32-
33-
private val contentViews
34-
get() = findChildrenByState(State.Success)
35-
36-
private val defaultState: Int
3727
private val emptyLayoutId: Int
3828
private val loadingLayoutId: Int
3929
private val failureLayoutId: Int
4030

41-
private var animationEnabled: Boolean
31+
private var animationEnabled = true
4232

4333
init {
44-
val tArray = c.obtainStyledAttributes(a, R.styleable.StatefulLayout)
34+
val tArray = c.obtainStyledAttributes(a, R.styleable.StatefulLayout, i, s)
4535

4636
emptyLayoutId = tArray.getResourceId(
4737
R.styleable.StatefulLayout_stateful_empty_layout,
4838
R.layout.state_empty_layout
4939
)
50-
5140
failureLayoutId = tArray.getResourceId(
5241
R.styleable.StatefulLayout_stateful_failure_layout,
5342
R.layout.state_failure_layout
5443
)
55-
5644
loadingLayoutId = tArray.getResourceId(
5745
R.styleable.StatefulLayout_stateful_loading_layout,
5846
R.layout.state_loading_layout
5947
)
6048

61-
//分配默认状态
62-
defaultState = tArray.getInt(R.styleable.StatefulLayout_state, 0)
49+
//获取默认状态
50+
val defaultState = tArray.getInt(R.styleable.StatefulLayout_state, 0)
51+
//设置默认的状态
52+
state = State.values().first {
53+
it.ordinal == defaultState
54+
}
6355
//是否启动状态切换动画
64-
animationEnabled = tArray.getBoolean(R.styleable.StatefulLayout_animation_enabled, true)
56+
animationEnabled =
57+
tArray.getBoolean(R.styleable.StatefulLayout_animation_enabled, animationEnabled)
6558

6659
tArray.recycle()
6760
}
6861

6962
override fun onFinishInflate() {
7063
super.onFinishInflate()
71-
if (emptyViews.isEmpty()) {
72-
View.inflate(context, emptyLayoutId, this)
64+
//根据拥有的状态,决定加载剩余的状态布局
65+
val (hasEmpty, hasLoading, hasFailure) = checkState()
66+
val inflater = LayoutInflater.from(context)
67+
if (!hasEmpty) {
68+
val emptyLayout = inflater.inflate(emptyLayoutId, this, false)
69+
addView(emptyLayout, resetLayoutParamsState(emptyLayout, State.Empty))
7370
}
74-
if (loadingViews.isEmpty()) {
75-
View.inflate(context, loadingLayoutId, this)
71+
if (!hasLoading) {
72+
val loadingLayout = inflater.inflate(loadingLayoutId, this, false)
73+
addView(loadingLayout, resetLayoutParamsState(loadingLayout, State.Loading))
7674
}
77-
if (failureViews.isEmpty()) {
78-
View.inflate(context, failureLayoutId, this)
75+
if (!hasFailure) {
76+
val failureLayout = inflater.inflate(failureLayoutId, this, false)
77+
addView(failureLayout, resetLayoutParamsState(failureLayout, State.Failure))
7978
}
80-
state = State.values().first {
81-
it.ordinal == defaultState
79+
}
80+
81+
private fun checkState(): Triple<Boolean, Boolean, Boolean> {
82+
var hasEmpty = false
83+
var hasLoading = false
84+
var hasFailure = false
85+
repeat(childCount) {
86+
val childView = getChildAt(it)
87+
val layoutParams = childView.layoutParams as LayoutParams
88+
val state = layoutParams.state
89+
if (!hasEmpty) {
90+
hasEmpty = state == State.Empty
91+
}
92+
if (!hasLoading) {
93+
hasLoading = state == State.Loading
94+
}
95+
if (!hasFailure) {
96+
hasFailure = state == State.Failure
97+
}
8298
}
99+
return Triple(hasEmpty, hasLoading, hasFailure)
83100
}
84101

85-
private fun findChildrenByState(state: State): List<View> {
86-
return (0..childCount)
87-
.mapNotNull(::getChildAt)
88-
.filter {
89-
val layoutParams = it.layoutParams
90-
layoutParams is LayoutParams && layoutParams.state == state
102+
private fun resetLayoutParamsState(view: View, state: State): LayoutParams {
103+
val oldLayoutParams = view.layoutParams
104+
if (oldLayoutParams is LayoutParams) {
105+
if (oldLayoutParams.state != state) {
106+
oldLayoutParams.state = state
91107
}
108+
return oldLayoutParams
109+
}
110+
val layoutParams = generateLayoutParams(oldLayoutParams)
111+
layoutParams.state = state
112+
return layoutParams
92113
}
93114

94115
private fun changeView() {
95-
if (previousState != State.Loading && state == State.Loading) {
96-
loadingViews.forEach(::show)
97-
} else {
98-
loadingViews.forEach(::hide)
99-
}
100-
if (previousState != State.Success && state == State.Success) {
101-
contentViews.forEach(::show)
102-
} else {
103-
contentViews.forEach(::hide)
104-
}
105-
if (previousState != State.Failure && state == State.Failure) {
106-
failureViews.forEach(::show)
107-
} else {
108-
failureViews.forEach(::hide)
109-
}
110-
if (previousState != State.Empty && state == State.Empty) {
111-
emptyViews.forEach(::show)
112-
} else {
113-
emptyViews.forEach(::hide)
116+
repeat(childCount) { index ->
117+
val childView = getChildAt(index)
118+
val childLayoutParams = childView.layoutParams as LayoutParams
119+
if (childLayoutParams.state == this.state) {
120+
show(childView, childLayoutParams)
121+
} else {
122+
hide(childView, childLayoutParams)
123+
}
114124
}
115125
}
116126

117-
private fun show(view: View) {
127+
private fun show(view: View, layoutParams: LayoutParams) {
118128
if (view.visibility != VISIBLE) {
119129
view.visibility = VISIBLE
120-
val layoutParams = view.layoutParams
121-
if (animationEnabled && layoutParams is LayoutParams) {
130+
if (animationEnabled) {
122131
view.clearAnimation()
123132
view.startAnimation(
124133
AnimationUtils.loadAnimation(
@@ -130,11 +139,10 @@ class StatefulLayout @JvmOverloads constructor(c: Context, a: AttributeSet? = nu
130139
}
131140
}
132141

133-
private fun hide(view: View) {
134-
if (view.visibility != GONE) {
135-
view.visibility = GONE
136-
val layoutParams = view.layoutParams
137-
if (animationEnabled && layoutParams is LayoutParams) {
142+
private fun hide(view: View, layoutParams: LayoutParams) {
143+
if (view.visibility == VISIBLE) {
144+
view.visibility = if (layoutParams.invisible) INVISIBLE else GONE
145+
if (animationEnabled) {
138146
view.clearAnimation()
139147
view.startAnimation(
140148
AnimationUtils.loadAnimation(
@@ -146,61 +154,77 @@ class StatefulLayout @JvmOverloads constructor(c: Context, a: AttributeSet? = nu
146154
}
147155
}
148156

149-
override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams {
157+
override fun checkLayoutParams(p: ViewGroup.LayoutParams?): Boolean {
158+
return p is LayoutParams
159+
}
160+
161+
override fun generateDefaultLayoutParams(): LayoutParams {
150162
return LayoutParams(
151163
ViewGroup.LayoutParams.MATCH_PARENT,
152164
ViewGroup.LayoutParams.MATCH_PARENT
153165
)
154166
}
155167

156-
override fun generateLayoutParams(attrs: AttributeSet?): FrameLayout.LayoutParams {
168+
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
157169
return LayoutParams(context, attrs)
158170
}
159171

160-
override fun generateLayoutParams(lp: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
172+
override fun generateLayoutParams(lp: ViewGroup.LayoutParams?): LayoutParams {
161173
return when (lp) {
162-
is LayoutParams -> LayoutParams(lp)
163174
is MarginLayoutParams -> LayoutParams(lp)
164175
is ViewGroup.LayoutParams -> LayoutParams(lp)
165176
else -> generateDefaultLayoutParams()
166177
}
167178
}
168179

169-
inner class LayoutParams : FrameLayout.LayoutParams {
180+
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
181+
super.addView(child, index, params)
182+
if (params is LayoutParams) {
183+
//根据当前的状态与child的布局属性,决定child是否显示
184+
child.visibility = when {
185+
state == params.state -> VISIBLE
186+
params.invisible -> INVISIBLE
187+
else -> GONE
188+
}
189+
}
190+
}
191+
192+
class LayoutParams : FrameLayout.LayoutParams {
170193

171-
val state: State
194+
var invisible = false
195+
var state = State.Success
172196
var showAnimationResId = R.anim.anim_state_layout_show
173197
var hideAnimationResId = R.anim.anim_state_layout_hide
174198

175199
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
176200
val typedArray =
177201
context.obtainStyledAttributes(attrs, R.styleable.StatefulLayout_Layout)
202+
invisible = typedArray.getBoolean(
203+
R.styleable.StatefulLayout_Layout_stateful_invisible,
204+
invisible
205+
)
178206
val stateValue = typedArray.getInt(R.styleable.StatefulLayout_Layout_layout_state, 3)
179-
this.state = State.values()[stateValue]
180-
showAnimationResId = typedArray.getResourceId(R.styleable.StatefulLayout_Layout_stateful_animation_show, showAnimationResId)
181-
hideAnimationResId = typedArray.getResourceId(R.styleable.StatefulLayout_Layout_stateful_animation_hide, hideAnimationResId)
207+
state = State.values()[stateValue]
208+
showAnimationResId = typedArray.getResourceId(
209+
R.styleable.StatefulLayout_Layout_stateful_animation_show,
210+
showAnimationResId
211+
)
212+
hideAnimationResId = typedArray.getResourceId(
213+
R.styleable.StatefulLayout_Layout_stateful_animation_hide,
214+
hideAnimationResId
215+
)
182216
typedArray.recycle()
183217
}
184218

185-
constructor(width: Int, height: Int, s: State) : super(width, height) {
186-
this.state = s
219+
constructor(width: Int, height: Int, state: State) : super(width, height) {
220+
this.state = state
187221
}
188222

189-
constructor(width: Int, height: Int) : this(width, height, State.Success)
223+
constructor(width: Int, height: Int) : super(width, height)
190224

191-
constructor(source: ViewGroup.LayoutParams) : super(source) {
192-
this.state = State.Success
193-
}
194-
195-
constructor(source: MarginLayoutParams) : super(source) {
196-
this.state = State.Success
197-
}
225+
constructor(source: ViewGroup.LayoutParams) : super(source)
198226

199-
constructor(source: LayoutParams) : super(source) {
200-
this.state = source.state
201-
this.showAnimationResId = source.showAnimationResId
202-
this.hideAnimationResId = source.hideAnimationResId
203-
}
227+
constructor(source: MarginLayoutParams) : super(source)
204228

205229
}
206230

stateful.layout/src/main/res/layout/state_empty_layout.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
android:layout_width="match_parent"
55
android:layout_height="match_parent"
66
android:gravity="center"
7-
android:text="@string/stateful_empty"
7+
android:text="@string/stateful_layout_empty"
88
app:layout_state="empty" />

0 commit comments

Comments
 (0)