StatusLayout
首先附上github項目地址;https://github.com/csming1995/statuslayout
之前看過很多網(wǎng)上已有的做法享幽,大多都已經(jīng)將狀態(tài)都涵蓋了铲掐;這樣的做法,可能很難包裹所有的業(yè)務(wù)需求值桩;
于是摆霉,突發(fā)奇想,是否能夠提供給使用者更自由的使用方式奔坟;比如携栋,提供給使用者自定義某狀態(tài)布局,甚至自定義狀態(tài)及布局的自由咳秉;
這是一個復(fù)雜度不太高婉支,但是代碼設(shè)計感比較強一點的開源庫~;
先看一下源碼澜建;
public class StatusLayout extends FrameLayout{
private static final String TAG = "StatusLayout.FrameLayout";
/**
* DEFAULT EMPTY NET_ERROR 默認的三種狀態(tài)
* DEFAULT 為用戶第一次使用該組件時指定的屬性狀態(tài)
*/
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 用鍵值對存儲 狀態(tài)-視圖
* List 用于存儲子控件,即內(nèi)容
*/
private Map<Integer, View> mMapMessageViews;
private List<View> mNormalViews;
private LayoutInflater mLayoutInflater;
/**
* 默認頁
* 空數(shù)據(jù)頁
* 網(wǎng)絡(luò)錯誤頁
*/
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函數(shù)在被調(diào)用時,對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);
}
/**
* 設(shè)置為有數(shù)據(jù)狀態(tài)
* 使當前View的子View顯示
* 子View為RecyclerView
* @see #setContentView(boolean)
*/
public void showNormalView(){
hiddenStatusViews();
setContentView(true);
}
/**
* 設(shè)置子View的顯示或隱藏狀態(tài)
* 子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);
}
}
}
/**
* 無參調(diào)用的設(shè)置網(wǎng)絡(luò)錯誤頁
* 用于內(nèi)部調(diào)用
*/
private void setNetErrorMessageView(){
if (null == mDefaultNetErrorView){
mDefaultNetErrorView = (LinearLayout)mLayoutInflater.inflate(R.layout.status_layout_net_error_message, null);
}
mMapMessageViews.put(NET_ERROR, mDefaultNetErrorView);
}
/**
* 有參調(diào)用的設(shè)置網(wǎng)絡(luò)錯誤頁
* 提供給外部使用者
* @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);
}
/**
* 無參調(diào)用空數(shù)據(jù)頁面
* 用于內(nèi)部調(diào)用
*/
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);
}
/**
* 有參調(diào)用設(shè)置空數(shù)據(jù)頁
* 提供給外部使用者
* @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);
}
/**
* 有參調(diào)用 設(shè)置默認頁
* 用于內(nèi)部使用
* @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);
}
/**
* 外部添加狀態(tài)
* 若狀態(tài)與已有狀態(tài)碰撞
* 跳出錯誤
* @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);
}
/**
* 顯示指定狀態(tài)頁
* 并將其他頁面隱藏
* 用于內(nèi)部以及外部電泳
* @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);
}
}
}
- 首先是: 這三個方法初始化了三種基本布局向挖;這三個方法用于定義了每一種布局的默認狀態(tài)下的文字及圖片;首先在初始化的時候調(diào)用炕舵;
- 他們最終是將初始化后的布局何之,加入mMapMessageViews中保存;mMapMessageViews的鍵值對為:狀態(tài)-布局咽筋;我們后面在顯示的時候溶推,將會從這個map中,通過狀態(tài)key奸攻,獲取對應(yīng)的布局蒜危;
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
@Override
protected void onFinishInflate(){
super.onFinishInflate();
showDefaultView();
}
- showDefaultView()方法舞箍,和其他的showXxx()方法一樣:
public void showDefaultView(){
showStatusView(DEFAULT);
}
最終調(diào)用的是showStatusView()這個方法舰褪;而showStatusView()方法皆疹,傳入一個key疏橄,然后從mMapMessageViews中獲取對應(yīng)的布局,并隱藏其他布局略就,最后顯示當前布局捎迫;
public void showStatusView(int key){
setContentView(false);
View mMessageView = mMapMessageViews.get(key);
hiddenStatusViews();
addView(mMessageView);
mMessageView.setVisibility(VISIBLE);
}
然后這里有一個setContentView()函數(shù),他的意義在于表牢,控制子布局的顯示與隱藏窄绒;
因為,我們的布局崔兴,在有數(shù)據(jù)狀態(tài)下彰导,應(yīng)該顯示的是其子布局的內(nèi)容蛔翅;
例如:一個RecyclerView;
<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>
那么調(diào)用setContentView()位谋,就可以控制其子布局的顯示與隱藏山析;主要在于遍歷布局下的所有子布局,然后設(shè)置他們的顯示隱藏掏父;
/**
* 設(shè)置子View的顯示或隱藏狀態(tài)
* 子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);
}
}
}
以上就是布局內(nèi)容的顯示部分笋轨;
然后,關(guān)鍵的赊淑,如何提供給使用者自定義狀態(tài)及對應(yīng)布局的邏輯爵政,主要是維護了一個map,以及幾種狀態(tài)值;
private Map<Integer, View> mMapMessageViews;
/**
* DEFAULT EMPTY NET_ERROR 默認的三種狀態(tài)
* DEFAULT 為用戶第一次使用該組件時指定的屬性狀態(tài)
*/
private static final int DEFAULT = 1;
private static final int EMPTY = 2;
private static final int NET_ERROR = 3;
以上三種是默認值陶缺;
如果使用者需要自定義狀態(tài)及布局钾挟,則只能定義除了這三個數(shù)字以外的數(shù)字;
為此饱岸,我特意編寫了一個Exception類型:如果使用者自定義的key是1/2/3的話等龙,則拋出錯誤;
/**
* 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");
}
}
該開源庫已經(jīng)上傳到github上了伶贰;
https://github.com/csming1995/statuslayout
各種求star蛛砰;