StatusLayout
首先附上github專案地址;github.com/csming1995/…
之前看過很多網上已有的做法,大多都已經將狀態都涵蓋了;這樣的做法,可能很難包裹所有的業務需求;
於是,突發奇想,是否能夠提供給使用者更自由的使用方式;比如,提供給使用者自定義某狀態佈局,甚至自定義狀態及佈局的自由;
這是一個複雜度不太高,但是程式碼設計感比較強一點的開源庫~;
先看一下原始碼;
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,獲取對應的佈局;
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);
}複製程式碼
最終呼叫的是showStatusView()這個方法;而showStatusView()方法,傳入一個key,然後從mMapMessageViews中獲取對應的佈局,並隱藏其他佈局,最後顯示當前佈局;
public void showStatusView(int key){
setContentView(false);
View mMessageView = mMapMessageViews.get(key);
hiddenStatusViews();
addView(mMessageView);
mMessageView.setVisibility(VISIBLE);
}複製程式碼
然後這裡有一個setContentView()函式,他的意義在於,控制子佈局的顯示與隱藏;
因為,我們的佈局,在有資料狀態下,應該顯示的是其子佈局的內容;
例如:一個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>複製程式碼
那麼呼叫setContentView(),就可以控制其子佈局的顯示與隱藏;主要在於遍歷佈局下的所有子佈局,然後設定他們的顯示隱藏;
/**
* 設定子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,以及幾種狀態值;
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的話,則丟擲錯誤;
/**
* 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上了;
各種求star;