前言
在做項目的時候,經常會遇到列表數據為空的時候展示的空布局,如果你用的是ListView ,目測會經常使用ListView的一個方法setEmptyView ,如果你用的是RecyclerView,你也許會用自定義View來實現,但是,這些方法雖然使用起來簡單,但是如果你提供一個復雜的布局,例如:
在數據加載失敗后,添加一個Button讓用戶可以選擇重新加載數據。
你肯定會說,findviewbyId找到這個button,給它設置點擊事件,一個兩個可以接受,但是,界面多了呢? 那你說了那么多,有沒有好的解決辦法呢? 當然有 而且是幾行代碼搞定的
自定義View
接下來就是重頭戲 開始編碼了 ,首先我們需要繼承FrameLayout來實現這樣的布局
123456789101112131415
public class EmptyLayout extends FrameLayout{public EmptyLayout(Context context){this(context, null);}public EmptyLayout(Context context, AttributeSet attrs){this(context, attrs, 0);}public EmptyLayout(Context context, AttributeSet attrs, int defStyleAttr){}}
為了靈活性,我自定義屬性來添加所需要的布局,values下面新建attrs.xml
12345678
然后我們以此找到這些布局,並且添加進去
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
public class EmptyLayout extends FrameLayout{private Context mContext;private View mEmptyView;private View mBindView;private View mErrorView;private Button mBtnReset;private View mLoadingView;private View loadingView;private TextView mEmptyText;private TextView tvLoadingText;public EmptyLayout(Context context){this(context, null);}public EmptyLayout(Context context, AttributeSet attrs){this(context, attrs, 0);}public EmptyLayout(Context context, AttributeSet attrs, int defStyleAttr){super(context, attrs, defStyleAttr);this.mContext=context;LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);//居中params.gravity = Gravity.CENTER;TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.EmptyLayout, 0, 0);//數據為空時的布局int emptyLayout = ta.getResourceId(R.styleable.EmptyLayout_elEmptyLayout, R.layout.layout_empty);mEmptyView = View.inflate(context, emptyLayout, null);mEmptyText =(TextView)mEmptyView.findViewById(R.id.tvEmptyText);addView(mEmptyView,params);//加載中的布局int loadingLayout = ta.getResourceId(R.styleable.EmptyLayout_elLoadingLayout, R.layout.layout_loading);mLoadingView = View.inflate(context, loadingLayout, null);tvLoadingText =(TextView)mLoadingView.findViewById(R.id.tvLoadingText);addView(mLoadingView,params);//錯誤時的布局int errorLayout = ta.getResourceId(R.styleable.EmptyLayout_elErrorLayout, R.layout.layout_error);mErrorView = View.inflate(context, errorLayout, null);mBtnReset =(Button)mErrorView.findViewById(R.id.btnReset);addView(mErrorView, params);//全部隱藏setGone();}/*** 全部隱藏*/private void setGone(){mEmptyView.setVisibility(View.GONE);mErrorView.setVisibility(View.GONE);mLoadingView.setVisibility(View.GONE);}}
簡單說下幾個變量的作用
mEmptyView 表示數據為空的時候展示給用戶
mEmptyText 數據為空提示的文字
mErrorView 加載錯誤展示給用戶
mBtnReset 加載錯誤重新加載的按鈕
mLoadingView 加載中展示給用戶
tvLoadingText 加載中提示的文字
mBindView 我們要綁定的view
好了,首先我們找到布局,然后添加進去,如果沒有,添加默認的布局。至此,布局已經完成,那怎么控制呢?我們想要的是什么效果呢?
在數據正在加載的時候調用loading方法,顯示正在加載中的文本。
在數據加載成,隱藏該view。
在數據加載失敗,顯示加載失敗的文本,並提供一個按鈕去刷新數據。
ok,我們按照這個條目一個個的來實現,首先是loading。
1234567891011
public void showLoading(String text){if (mBindView != null) mBindView.setVisibility(View.GONE);if (!TextUtils.isEmpty(text)) tvLoadingText.setText(text);setGone();mLoadingView.setVisibility(View.VISIBLE);}public void showLoading(){showLoading(null);}
首先判斷下我們要綁定view是不是為空,不為空則隱藏它,隱藏其他布局,然后展示loadingview
那加載失敗了呢?同樣簡單!
12345678910
public void showError(){showError(null);}public void showError(String text){if (mBindView != null) mBindView.setVisibility(View.GONE);if (!TextUtils.isEmpty(text)) mBtnReset.setText(text);setGone();mErrorView.setVisibility(View.VISIBLE);}
這個同上
繼續看看加載成功的方法,這個更簡單啦。
12345
public void showSuccess(){if (mBindView != null) mBindView.setVisibility(View.VISIBLE);setGone();}
至此,我們整個效果就完成了,在加載數據的時候調用showLoading方法來顯示加載中的文本,加載失敗后,調用showError來顯示加載失敗的文本和刷新的按鈕,在加載成功后直接隱藏控件
控件倒是完成了,我們還不知道mBindView怎么來的,其實也很簡單。我們在代碼中需要調用bindView(View view)方法來指定。
1234
public void bindView(View view){mBindView = view;}
那么問題來了,我加載失敗后,按鈕的點擊事件怎么做呢?有人會說用反射,這樣既省了代碼行數,看着又舒服,但是這樣是有個問題存在的,大家都知道,一個項目的上線,都會進行混淆代碼的,為了就是防止他人剽竊我們的勞動成果,可是混淆過后哪些class全部變成a,b,c ,這樣如果用反射的話就會導致點擊事件失效,因為找不到這個類,所以,我們還是老老實實的用onclick事件吧
1234
public void setOnButtonClick(OnClickListener listener){mBtnReset.setOnClickListener(listener);}
這樣,一個簡單的EmptyLayout就誕生了,接下來我們來看看怎么使用
先看xml布局
123456789101112131415161718192021222324
在看看Activity中怎么調用
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
public class MainActivity extends Activity{private EmptyLayout emptyLayout;private RecyclerView recyclerView;private List list = new ArrayList<>();private MyAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();loadData();}private Handler mHandler = new Handler();private void initView(){emptyLayout = (EmptyLayout) findViewById(R.id.emptyLayout);recyclerView = (RecyclerView) findViewById(R.id.recyclerView);recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));recyclerView.setAdapter(adapter = new MyAdapter(list));//綁定emptyLayout.bindView(recyclerView);emptyLayout.setOnButtonClick(new View.OnClickListener() {@Overridepublic void onClick(View v){//重新加載數據loadData();}});}private void loadData(){//模擬加載數據emptyLayout.showLoading("正在加載,請稍后");mHandler.postDelayed(new Runnable() {@Overridepublic void run(){Random r = new Random();int res = r.nextInt(10);if (res % 2 == 0) {// 失敗emptyLayout.showError("加載失敗,點擊重新加載"); // 顯示失敗} else {// 成功emptyLayout.showSuccess();for (int i = 0; i < 15; i++) {list.add("測試" + i);}adapter.notifyDataSetChanged();}}}, 3000);}}
首頁我們找到控件,然后給recyclerView設置adapter,然后我們調用emptyLayout.bindView(recyclerView);來設置要綁定的view,當然這里是recyclerView,接下來,通過emptyLayout.setOnButtonClick()來設置重新加載的時候執行哪個方法,在loadData()中延遲3秒獲取數據,數據成功失敗都是隨機的,當失敗的時候會調用emptyLayout.showError(),成功就調用emptyLayout.showSuccess();就這么簡單,來看看運行效果
效果展示