前言
在Android開發中我們經常遇到這樣的場景:去加載網絡數據時需要顯示進度條,提示用戶正在加載,加載失敗需要給用戶提示加載失敗,還需要能夠重新加載,數據為空也要給用戶相應的提示。對于一個初入編程之世的程序員來說這肯定是令人頭(dan)疼(teng)的問題,在開發中他們會這么做:在一個XML布局文件中添加加載成功,失敗,數據為空的布局,在請求過程中通過設置View的“顯示”和“隱藏”來達到這種效果,每當看到這樣寫,我的內心是崩潰的(當然,之前我也是這樣的O.o),后來隨著公司的項目功能擴展也來越龐大,我覺得不能再用這樣扯淡的方法來解決這種操蛋的問題,就決定自定義一個控件解決該問題,經過幾天的思考ProgressStateLayout誕生了。
思路
提起自定義控件,我們首先就會想到自定義控件的基本流程:onFinishInflate(),onMeasure(),onLayout(),onDraw(),onAttachedToWindow()當然,有這樣的想法很正確,但 不一定每個方法都要重寫,這需要結合實際的業務需求。在ProgressStateLayout中需要封裝四中狀態(四個View),這時簡單的View已經不能滿足我們的需求,我們需要通過ViewGroup來實現該功能。
1:ProgressStateLayout需要繼承RelativeLayout。
2:通過LayoutInflater實例化失,數據為空,加載進度的View。
3:重寫addView()方法,把View添加到ViewGroup中。
4:通過switchState()方法來實現不同狀態的相互切換。
具體實現
ProgressStateLayout需要繼承RelativeLayout,然后修改里面的構造方法。讓一個參數的調用兩個參數的,兩個參數的調用三個參數的,在三個參數的構造方法中添加一個Init()方法用于一些初始化操作。
private LayoutParamslayoutParams;
private LayoutInflaterinflater;
private Listviews=null;
private static final StringTAG_LOADING="loading";
private static final StringTAG_EMPTY="empty";
private static final StringTAG_ERROR="error";
private View viewLoading,viewEmpty,viewError;
一個方參數的構造方法
public ProgressStateLayout(Context context) {
this(context,null);
}
兩個參數的構造方法
public ProgressStateLayout(Contextcontext, AttributeSet attrs){
this(context, attrs,0);
}
三個參數的構造方法
public ProgressStateLayout(Context context, AttributeSet attrs,intdefStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
寫個接口,用于“重新加載”按鈕的點擊回調
private ReloadListenerlistener;
//重新加載按鈕的接口,用于監聽重新加載按鈕的監聽回調
public interface ReloadListener {
void onClick();
}
枚舉類型用于標示四種不同的狀態(當然你也可以用int,String類型來表示,這取決于個人的習慣,枚舉類型能夠很好的規范參數的形式,個人比較喜歡用枚舉,至少代碼風格會顯得高大上一點哈哈)
//四種不同狀態
private enum Type {
LOADING,EMPTY,CONTENT,ERROR;
}
init()初始化方法,你會注意到這里實例化了一個List集合,你會想這個集合什么鬼?,當然有用了,別想多了,這個List不是用來保存四個狀態的View,是用來保存ViewGroup里面加載成功的View除了正在加載,數據為空,加載失敗
/**
*初始化操作
*/
public voidinit() {
views=newArrayList();
inflater= (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutParams=newLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.addRule(CENTER_IN_PARENT);
}
設置“正在加載”的View
private void setLoadingView() {
if(viewLoading==null) {
viewLoading=inflater.inflate(R.layout.layout_loading,null);
viewLoading.setTag(TAG_LOADING);
viewLoading.requestLayout();
addView(viewLoading,layoutParams);
}else{
viewLoading.setVisibility(View.VISIBLE);
}}
設置“數據為空”的View
private void setEmptyView(intresid, String msg) {
if(viewEmpty==null) {
viewEmpty=inflater.inflate(R.layout.layout_empty,null);
if(resid !=0) {
ImageView imageView = (ImageView)viewEmpty.findViewById(R.id.img_nodata);
imageView.setImageResource(resid);}
if(!TextUtils.isEmpty(msg)) {
TextView tv_msg = (TextView) findViewById(R.id.text_nodata_tips);
tv_msg.setText(msg);}
viewEmpty.setTag(TAG_EMPTY);
viewEmpty.requestLayout();
addView(viewEmpty,layoutParams);}else{
viewEmpty.setVisibility(View.VISIBLE);
}}
設置“加載失敗”的View
private void setErrorView(int resid, String title, String msg, String btntext) {
if (viewError == null) {
viewError = inflater.inflate(R.layout.layout_error, null);
if (resid != 0) {
ImageView img = (ImageView) findViewById(R.id.img_nodata);
img.setImageResource(resid);}
if (!TextUtils.isEmpty(title)) {
TextView tv_title = (TextView) findViewById(R.id.tv_title);
tv_title.setText(title);}
if (!TextUtils.isEmpty(msg)) {
TextView tv_msg = (TextView) findViewById(R.id.tv_msg);
tv_msg.setText(title);}
Button btn_reload = (Button) viewError.findViewById(R.id.btn_reload);
if (!TextUtils.isEmpty(btntext)) {
btn_reload.setText(btntext);}
btn_reload.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onClick();}}});
viewError.requestLayout();
viewError.setTag(TAG_ERROR);
addView(viewError, layoutParams);} else {
viewError.setVisibility(View.VISIBLE);}
}
設置“加載成功”后ViewGroup里面內容
private void setContentView(booleanflag) {
for(View v :views) {
v.setVisibility(flag ? View.VISIBLE: View.GONE);
}}
設置類型狀態的隱藏
private void hideLoadingView() {
if(viewLoading!=null) {
viewLoading.setVisibility(View.GONE);}}
private void hideEmptyView() {
if(viewEmpty!=null) {
viewEmpty.setVisibility(View.GONE);}}
private void hideErrorView() {
if(viewError!=null) {
viewError.setVisibility(View.GONE);}}
對外提供方法用于類型的切換,可以傳遞信息參數,如果不傳,使用默認
public void showLoading() {
switchState(Type.LOADING,
0,null,null,null);}
public void showError(intresid, String title, String msg,
String btntext, ReloadListener listener) {
this.listener= listener;
switchState(Type.ERROR, resid, title, msg, btntext);}
public void showEmpty(intresid, String msg) {
switchState(Type.EMPTY, resid, msg,null,null);}
public void showContent() {
switchState(Type.CONTENT,0,null,null,null);}
public void showError(ReloadListener listener) {
this.listener= listener;
switchState(Type.ERROR,0,null,null,null);}
public void showEmpty() {
switchState(
Type.EMPTY,0,null,null,null);
}
切換狀態方法switchState()
/**
*改變狀態方法
*@paramtype
*/
private void switchState(Type type,intresid, String title, String msg, String btntext) {
switch(type) {
caseLOADING:
hideEmptyView();
hideErrorView();
setContentView(false);
setLoadingView();
break;
caseEMPTY:
hideErrorView();
hideLoadingView();
setContentView(false);
setEmptyView(resid, title);
break;
caseERROR:
hideEmptyView();
hideLoadingView();
setContentView(false);
setErrorView(resid, title, msg, btntext);
break;
caseCONTENT:
hideEmptyView();
hideLoadingView();
hideErrorView();
setContentView(true);
break;}}
重寫addView()方法,這里需要為不同類型的View設置Tag標示,用于區分不同狀態的view和父布局內的控件。
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
super.addView(child, params);
//把ProgressStateView內的子控件內容添加到list集合中,保證不同狀態間相互切換內容的隱藏與顯示
if(child.getTag() ==null|| (!child.getTag().equals(TAG_LOADING) &&
!child.getTag().equals(TAG_EMPTY) && !child.getTag().equals(TAG_ERROR))) {
views.add(child);}
}
how to use
截圖:
正在加載:
數據為空:
請求失敗:
請求成功:
示例代碼請點擊:ProgressLayout
結束語
本人是一個技術渣渣,自學Android起步,對Android的極深奧義尚未了解,通過對在開發中的問題進行整理總結,希望對開發中遇到同樣問題的小伙伴有所幫助,第一次在這里寫文章,文中有很多瑕疵,還請多多關照。