StatusLayout

Posted by Csming on 2017-11-26

StatusLayout

首先附上github项目地址;https://github.com/csming1995/statuslayout

之前看过很多网上已有的做法,大多都已经将状态都涵盖了;这样的做法,可能很难包裹所有的业务需求;

于是,突发奇想,是否能够提供给使用者更自由的使用方式;比如,提供给使用者自定义某状态布局,甚至自定义状态及布局的自由;


这是一个复杂度不太高,但是代码设计感比较强一点的开源库~;

先看一下源码;

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
public class StatusLayout extends FrameLayout{
private static final String TAG = "StatusLayout.FrameLayout";


/**
* DEFAULT EMPTY NET_ERROR 默认的三种状态
* DEFAULT 为用户第一次使用该组件时指定的属性状态
*/
private static final int DEFAULT = 1;
private static final int EMPTY = 2;
private static final int NET_ERROR = 3;
//rivate static final int LOADING = 3;


/**
* 属性值
*/
private String mInitMessage;
private Drawable mInitImage;
private String mInitStrInBtn;

/**
* Map 用键值对存储 状态-视图
* List 用于存储子控件,即内容
*/
private Map<Integer, View> mMapMessageViews;
private List<View> mNormalViews;

private LayoutInflater mLayoutInflater;


/**
* 默认页
* 空数据页
* 网络错误页
*/
private LinearLayout mDefaultView;//默认页
private LinearLayout mDefaultEmptyMessageView;
private LinearLayout mDefaultNetErrorView;

private Context mContext;

public StatusLayout(Context context){
this(context, null);
}

public StatusLayout(Context context, AttributeSet attrs){
this(context, attrs, 0);
}

public StatusLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init(attrs);
}


/**
* 一些初始化工作
* 初始化DefaultView
*/

private void init(AttributeSet attrs){
if (null == mNormalViews) mNormalViews = new ArrayList<>();

if (null == mMapMessageViews) mMapMessageViews = new HashMap<>();

if (null == mLayoutInflater){
mLayoutInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

TypedArray mValueArray = mContext.obtainStyledAttributes(attrs, R.styleable.StatusLayoutValue);

mInitMessage = mValueArray.getString(R.styleable.StatusLayoutValue_attr_message);
mInitImage = mValueArray.getDrawable(R.styleable.StatusLayoutValue_attr_image_src);
mInitStrInBtn = mValueArray.getString(R.styleable.StatusLayoutValue_attr_str_btn);

setEmptyMessageView();
setNetErrorMessageView();
setDefaultView(mInitMessage, mInitImage, mInitStrInBtn);
mValueArray.recycle();

}

/**
* 加载完布局后 使默认视图显示
*/
@Override
protected void onFinishInflate(){
super.onFinishInflate();
showDefaultView();
}

/**
* 测量
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}

/**
* 通过addView函数在被调用时,对child View进行初始化
* 获取子控件信息
* @param child
* @param params
*/
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
super.addView(child, params);
for(int i = 0; i < getChildCount(); i ++){
mNormalViews.add(getChildAt(i));
}
}

public void showDefaultView(){
showStatusView(DEFAULT);
}

public void showEmptyMessageView(){
showStatusView(EMPTY);
}

public void showNetErrorView(){
showStatusView(NET_ERROR);
}

/**
* 设置为有数据状态
* 使当前View的子View显示
* 子View为RecyclerView
* @see #setContentView(boolean)
*/
public void showNormalView(){
hiddenStatusViews();
setContentView(true);
}

/**
* 设置子View的显示或隐藏状态
* 子View存储于一个list中
* @param isShown
*/
private void setContentView(boolean isShown){
if (isShown){
for (View v : mNormalViews){
v.setVisibility(VISIBLE);
}
}else {
for (View v : mNormalViews){
v.setVisibility(GONE);
}
}
}

/**
* 无参调用的设置网络错误页
* 用于内部调用
*/
private void setNetErrorMessageView(){
if (null == mDefaultNetErrorView){
mDefaultNetErrorView = (LinearLayout)mLayoutInflater.inflate(R.layout.status_layout_net_error_message, null);
}
mMapMessageViews.put(NET_ERROR, mDefaultNetErrorView);
}

/**
* 有参调用的设置网络错误页
* 提供给外部使用者
* @param message
* @param image
*/
public void setNetErrorMessageView(String message, Drawable image){
if(null == mDefaultNetErrorView){
mDefaultNetErrorView = (LinearLayout)mLayoutInflater.inflate(R.layout.status_layout_net_error_message, null);
}else {
mDefaultNetErrorView = (LinearLayout)mMapMessageViews.get(NET_ERROR);
}

TextView mTvNetError = (TextView)mDefaultNetErrorView.findViewById(R.id.tv_net_error_view);
ImageView mIvNetError = (ImageView)mDefaultNetErrorView.findViewById(R.id.iv_net_error_view);
if (null != message){
mTvNetError.setText(message);
mTvNetError.setVisibility(VISIBLE);
}else {
mTvNetError.setVisibility(GONE);
}
if (null != image){
mIvNetError.setImageDrawable(image);
mIvNetError.setVisibility(VISIBLE);
}else {
mIvNetError.setVisibility(GONE);
}

mMapMessageViews.put(NET_ERROR, mDefaultNetErrorView);
}

/**
* 无参调用空数据页面
* 用于内部调用
*/

private void setEmptyMessageView(){
if (null == mDefaultEmptyMessageView) {
mDefaultEmptyMessageView = (LinearLayout)mLayoutInflater.inflate(R.layout.status_layout_empty_message, null);
}
mMapMessageViews.put(EMPTY, mDefaultEmptyMessageView);
}

public void setEmptyMessageView(int messageId, int imageId, int messageInBtnId){
String messageInBtn = mContext.getString(messageInBtnId);
setEmptyMessageView(messageId, imageId, messageInBtn);
}

public void setEmptyMessageView(int messageId, int imageId, String messageInBtn){
Drawable image = ContextCompat.getDrawable(mContext, imageId);
setEmptyMessageView(messageId, image, messageInBtn);
}

public void setEmptyMessageView(int messageId, Drawable image, String messageInBtn){
String message = mContext.getString(messageId);
setEmptyMessageView(message, image, messageInBtn);
}

/**
* 有参调用设置空数据页
* 提供给外部使用者
* @param message
* @param image
* @param messageInBtn
*/

public void setEmptyMessageView(String message, Drawable image, String messageInBtn){
if(null == mDefaultEmptyMessageView){
mDefaultEmptyMessageView = (LinearLayout)mLayoutInflater.inflate(R.layout.status_layout_empty_message, null);
}else {
mDefaultEmptyMessageView = (LinearLayout)mMapMessageViews.get(EMPTY);
}

TextView mTvEmpty = (TextView)mDefaultEmptyMessageView.findViewById(R.id.tv_empty_view);
ImageView mIvEmpty = (ImageView)mDefaultEmptyMessageView.findViewById(R.id.iv_empty_view);
Button mBtnEmpty = (Button)mDefaultEmptyMessageView.findViewById(R.id.btn_empty_view);
if (null != message){
mTvEmpty.setText(message);
mTvEmpty.setVisibility(VISIBLE);
}else {
mTvEmpty.setVisibility(GONE);
}
if (null != image){
mIvEmpty.setImageDrawable(image);
mIvEmpty.setVisibility(VISIBLE);
}else {
mIvEmpty.setVisibility(GONE);
}
if (null != messageInBtn) {
mBtnEmpty.setText(message);
mBtnEmpty.setVisibility(VISIBLE);
}else {
mBtnEmpty.setVisibility(GONE);
}
mMapMessageViews.put(EMPTY, mDefaultEmptyMessageView);
}
/**
* 有参调用 设置默认页
* 用于内部使用
* @param message
* @param image
* @param messageInBtn
*/
private void setDefaultView(String message, Drawable image, String messageInBtn){
if(null == mDefaultView){
mDefaultView = (LinearLayout)mLayoutInflater.inflate(R.layout.status_layout_default_message, null);
}else {
mDefaultView = (LinearLayout)mMapMessageViews.get(DEFAULT);
}

TextView mTvDefault = (TextView)mDefaultView.findViewById(R.id.tv_default_view);
ImageView mIvDefault = (ImageView)mDefaultView.findViewById(R.id.iv_default_view);
Button mBtnDefault = (Button)mDefaultView.findViewById(R.id.btn_default_view);
if (null != message){
mTvDefault.setText(message);
mTvDefault.setVisibility(VISIBLE);
}else {
mTvDefault.setVisibility(GONE);
}
if (null != image){
mIvDefault.setImageDrawable(image);
mIvDefault.setVisibility(VISIBLE);
}else {
mIvDefault.setVisibility(GONE);
}
if (null != messageInBtn) {
mBtnDefault.setText(message);
mBtnDefault.setVisibility(VISIBLE);
}else {
mBtnDefault.setVisibility(GONE);
}
mMapMessageViews.put(DEFAULT, mDefaultView);
}

/**
* 外部添加状态
* 若状态与已有状态碰撞
* 跳出错误
* @param key
* @param view
* @throws IllegalNumException
*/
public void addStatus(int key, View view) throws IllegalNumException {
if(1 == key||2 == key||3 == key) {
throw new IllegalNumException();
}
mMapMessageViews.put(key, view);
}

/**
* 显示指定状态页
* 并将其他页面隐藏
* 用于内部以及外部电泳
* @param key
*/
public void showStatusView(int key){
setContentView(false);
View mMessageView = mMapMessageViews.get(key);
hiddenStatusViews();
addView(mMessageView);
mMessageView.setVisibility(VISIBLE);
}

/**
* 隐藏mMapMessageViews的所有页面
*/

private void hiddenStatusViews(){
for (View v : mMapMessageViews.values()){
removeView(v);
}
}

}
  • 首先是: 这三个方法初始化了三种基本布局;这三个方法用于定义了每一种布局的默认状态下的文字及图片;首先在初始化的时候调用;
  • 他们最终是将初始化后的布局,加入mMapMessageViews中保存;mMapMessageViews的键值对为:状态-布局;我们后面在显示的时候,将会从这个map中,通过状态key,获取对应的布局;
1
2
3
4
5
6
7
8
9
10
public void setEmptyMessageView();
public void setEmptyMessageView(int messageId, int imageId, int messageInBtnId);
public void setEmptyMessageView(int messageId, int imageId, String messageInBtn);
public void setEmptyMessageView(int messageId, Drawable image, String messageInBtn);
public void setEmptyMessageView(String message, Drawable image, String messageInBtn);

public void setNetErrorMessageView();
public void setNetErrorMessageView(String message, Drawable image);

private void setDefaultView(String message, Drawable image, String messageInBtn);
  • 然后: 在onFinishInflate()布局加载完成后,先显示defaultView
1
2
3
4
5
@Override
protected void onFinishInflate(){
super.onFinishInflate();
showDefaultView();
}
  • showDefaultView()方法,和其他的showXxx()方法一样:
1
2
3
public void showDefaultView(){
showStatusView(DEFAULT);
}

最终调用的是showStatusView()这个方法;而showStatusView()方法,传入一个key,然后从mMapMessageViews中获取对应的布局,并隐藏其他布局,最后显示当前布局;

1
2
3
4
5
6
7
public void showStatusView(int key){
setContentView(false);
View mMessageView = mMapMessageViews.get(key);
hiddenStatusViews();
addView(mMessageView);
mMessageView.setVisibility(VISIBLE);
}

然后这里有一个setContentView()函数,他的意义在于,控制子布局的显示与隐藏;

因为,我们的布局,在有数据状态下,应该显示的是其子布局的内容;

例如:一个RecyclerView;

1
2
3
4
5
6
7
8
9
10
11
12
<com.csm.Component.StatusLayout
android:id="@+id/statuslayout_demo"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:attr_message="@string/str_there_has_nothing"
app:attr_image_src="@mipmap/ic_launcher">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_demo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher_round"/>
</com.csm.Component.StatusLayout>

那么调用setContentView(),就可以控制其子布局的显示与隐藏;主要在于遍历布局下的所有子布局,然后设置他们的显示隐藏;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 设置子View的显示或隐藏状态
* 子View存储于一个list中
* @param isShown
*/
private void setContentView(boolean isShown){
if (isShown){
for (View v : mNormalViews){
v.setVisibility(VISIBLE);
}
}else {
for (View v : mNormalViews){
v.setVisibility(GONE);
}
}
}

以上就是布局内容的显示部分;


然后,关键的,如何提供给使用者自定义状态及对应布局的逻辑,主要是维护了一个map,以及几种状态值;

1
2
3
4
5
6
7
8
9
private Map<Integer, View> mMapMessageViews;

/**
* DEFAULT EMPTY NET_ERROR 默认的三种状态
* DEFAULT 为用户第一次使用该组件时指定的属性状态
*/
private static final int DEFAULT = 1;
private static final int EMPTY = 2;
private static final int NET_ERROR = 3;

以上三种是默认值;

如果使用者需要自定义状态及布局,则只能定义除了这三个数字以外的数字;

为此,我特意编写了一个Exception类型:如果使用者自定义的key是1/2/3的话,则抛出错误;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/**
* Created by csm on 2017/7/7.
*/

public class IllegalNumException extends Exception {
public IllegalNumException(){}

public IllegalNumException(String gripe){
super(gripe);
}

@Override
public void printStackTrace(){
super.printStackTrace();
System.out.print("You can't choice 1,2,3 as your status key");
}
}

该开源库已经上传到github上了;

https://github.com/csming1995/statuslayout

各种求star;
二维码~