簡單介紹一下這個控件程储,像我們在實際的開發(fā)過程中章鲤,經(jīng)常性的會遇到這樣的場景,比如進(jìn)入一個頁面先出來加載動畫斟或,然后請求數(shù)據(jù)集嵌,如果網(wǎng)絡(luò)異常就顯示網(wǎng)絡(luò)異常的布局,數(shù)據(jù)異常怜珍、數(shù)據(jù)為空也有相應(yīng)的布局凤粗,以及當(dāng)我們請求成功完畢數(shù)據(jù)后嫌拣,根據(jù)返回的數(shù)據(jù)值去區(qū)分不同VIP等級的用戶顯示不同的頁面,這里我放了兩張圖捶索,我的女神灰瞻,迪麗熱巴和俞飛鴻辅甥,就當(dāng)做我們在業(yè)務(wù)開發(fā)中的 Layout 燎竖,把布局全部寫在 xml构回,然后控制顯示隱藏就有點不優(yōu)雅了,基于這個問題脐供,就有了這個控件茁肠。
下面是自定義 View 的自定義屬性:
<declare-styleable name="EasyStateView">
// 是否使用過渡動畫
<attr name="esv_use_anim" format="boolean"/>
// 加載動畫 View
<attr name="esv_loadingView" format="reference" />
// 數(shù)據(jù)異常垦梆,加載失敗 View
<attr name="esv_errorDataView" format="reference" />
// 網(wǎng)絡(luò)異常 View
<attr name="esv_errorNetView" format="reference" />
// 空白頁面 View
<attr name="esv_emptyView" format="reference" />
// 設(shè)置當(dāng)前顯示的 viewState
<attr name="esv_viewState" format="enum">
<enum name="content" value="0" />
<enum name="loading" value="-1" />
<enum name="error_data" value="-2" />
<enum name="error_net" value="-3" />
<enum name="empty" value="-4" />
</attr>
</declare-styleable>
Java代碼:
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
public class EasyStateView extends FrameLayout {
// 內(nèi)容 View
public static final int VIEW_CONTENT = 0;
// 加載 View
public static final int VIEW_LOADING = -1;
// 數(shù)據(jù)異常( 數(shù)據(jù)異常指原本應(yīng)該是有數(shù)據(jù)托猩,但是服務(wù)器返回了錯誤的、不符合格式的數(shù)據(jù) ) View
public static final int VIEW_ERROR_DATA = -2;
// 網(wǎng)絡(luò)異常 View
public static final int VIEW_ERROR_NET = -3;
// 數(shù)據(jù)為空 View
public static final int VIEW_EMPTY = -4;
// View 的 Tag 標(biāo)簽值
private static final int VIEW_TAG = -5;
// 用來存放 View
private SparseArray<View> mViews;
// 是否使用過渡動畫
private boolean mUseAnim;
// 是否處于動畫中
private boolean isAniming;
// 當(dāng)前顯示的 ViewTag
private int mCurrentState;
private Context mContext;
private StateViewListener mListener;
// content View 是否被添加到隊列
private boolean isAddContent;
public interface StateViewListener {
void onStateChanged(int state);
}
public void setStateChangedListener(StateViewListener listener) {
this.mListener = listener;
}
public EasyStateView(Context context) {
this(context, null);
}
public EasyStateView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EasyStateView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
mContext = context;
mViews = new SparseArray<>();
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.EasyStateView);
mCurrentState = typedArray.getInt(R.styleable.EasyStateView_esv_viewState, VIEW_CONTENT);
int emptyResId = typedArray.getResourceId(R.styleable.EasyStateView_esv_emptyView, VIEW_TAG);
if (emptyResId != VIEW_TAG) {
View view = LayoutInflater.from(getContext()).inflate(emptyResId, this, false);
addViewToHash(view, VIEW_EMPTY);
addViewInLayout(view, -1, view.getLayoutParams());
}
int errorDataResId = typedArray.getResourceId(R.styleable.EasyStateView_esv_errorDataView, VIEW_TAG);
if (errorDataResId != VIEW_TAG) {
View view = LayoutInflater.from(getContext()).inflate(errorDataResId, this, false);
addViewToHash(view, VIEW_ERROR_DATA);
addViewInLayout(view, -1, view.getLayoutParams());
}
int errorNetResId = typedArray.getResourceId(R.styleable.EasyStateView_esv_errorNetView, VIEW_TAG);
if (errorNetResId != VIEW_TAG) {
View view = LayoutInflater.from(getContext()).inflate(errorNetResId, this, false);
addViewToHash(view, VIEW_ERROR_NET);
addViewInLayout(view, -1, view.getLayoutParams());
}
int loadingResId = typedArray.getResourceId(R.styleable.EasyStateView_esv_loadingView, VIEW_TAG);
if (loadingResId != VIEW_TAG) {
View view = LayoutInflater.from(getContext()).inflate(loadingResId, this, false);
addViewToHash(view, VIEW_LOADING);
addViewInLayout(view, -1, view.getLayoutParams());
}
mUseAnim = typedArray.getBoolean(R.styleable.EasyStateView_esv_use_anim, true);
typedArray.recycle();
}
@Override
public void addView(View child) {
addContentV(child);
super.addView(child);
}
private boolean isContentView(View child) {
if (!isAddContent && null != child
&& null == child.getTag()) {
return true;
}
return false;
}
private void addContentV(View child) {
if (isContentView(child)) {
addViewToHash(child, VIEW_CONTENT);
isAddContent = true;
}
}
private void addViewToHash(View child, int viewTag) {
child.setTag(viewTag);
if (viewTag != mCurrentState) {
child.setVisibility(GONE);
}
mViews.put(viewTag, child);
}
@Override
public void addView(View child, int index) {
addContentV(child);
super.addView(child, index);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
addContentV(child);
super.addView(child, index, params);
}
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
addContentV(child);
super.addView(child, params);
}
@Override
public void addView(View child, int width, int height) {
addContentV(child);
super.addView(child, width, height);
}
@Override
protected boolean addViewInLayout(View child, int index, ViewGroup.LayoutParams params) {
addContentV(child);
return super.addViewInLayout(child, index, params);
}
@Override
protected boolean addViewInLayout(View child, int index, ViewGroup.LayoutParams params, boolean preventRequestLayout) {
addContentV(child);
return super.addViewInLayout(child, index, params, preventRequestLayout);
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable parcelable = super.onSaveInstanceState();
int useAnim = 0;
if (mUseAnim) {
useAnim = 1;
}
return new SaveState(parcelable, mCurrentState, useAnim);
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SaveState saveState = (SaveState) state;
if (saveState.useAnim == 1) {
mUseAnim = true;
} else {
mUseAnim = false;
}
// 因為應(yīng)用方向改變觸發(fā)重繪后他宛,重新初始化讀取的 ViewState 是不準(zhǔn)確的欠气,所以要隱藏掉
if (saveState.viewState != mCurrentState) {
getStateView(mCurrentState).setVisibility(GONE);
showViewState(saveState.viewState);
}
super.onRestoreInstanceState(saveState.getSuperState());
}
private static class SaveState extends BaseSavedState {
private int viewState;
/**
* 布爾值存儲居然沒有api预柒,只能存儲布爾數(shù)組,故改成 int 記錄
* 1 使用動畫
* 2 不使用動畫
*/
private int useAnim;
private SaveState(Parcel source) {
super(source);
viewState = source.readInt();
}
private SaveState(Parcelable superState, int viewState, int useAnim) {
super(superState);
this.viewState = viewState;
this.useAnim = useAnim;
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(viewState);
out.writeInt(useAnim);
}
public static final Parcelable.Creator<SaveState> CREATE = new Parcelable.Creator<SaveState>() {
@Override
public SaveState createFromParcel(Parcel source) {
return new SaveState(source);
}
@Override
public SaveState[] newArray(int size) {
return new SaveState[size];
}
};
}
/**
* 切換默認(rèn)狀態(tài)的 View
*
* @param state
*/
public void showViewState(int state) {
if (!checkState(state)) {
showViewAnim(state, VIEW_TAG);
}
}
/**
* 切換 view 時用 loading view 過渡
*
* @param state
*/
public void afterLoadingState(int state) {
if (!checkState(state)) {
if (mCurrentState == VIEW_LOADING) {
showViewAnim(state, VIEW_TAG);
} else {
showViewAnim(VIEW_LOADING, state);
}
}
}
/**
* 檢查狀態(tài)是否合法
* true 表示不合法,不往下執(zhí)行
* false 表示該狀態(tài)和當(dāng)前狀態(tài)不同淋袖,并合法數(shù)值狀態(tài)
*
* @param state
* @return
*/
private boolean checkState(int state) {
if (state <= VIEW_TAG) {
throw new RuntimeException("ViewState 不在目標(biāo)范圍");
}
if (state == mCurrentState) {
return true;
} else if (isAniming) {
return true;
}
return false;
}
public void setUseAnim(boolean useAnim) {
this.mUseAnim = useAnim;
}
private void showViewAnim(int showState, int afterState) {
if (!isAniming) {
isAniming = true;
}
View showView = getStateView(showState);
if (null == showView) {
isAniming = false;
return;
}
View currentView = getStateView(mCurrentState);
if (mUseAnim) {
showAlpha(showState, afterState, showView, currentView);
} else {
currentView.setVisibility(GONE);
if (showView.getAlpha() == 0) {
showView.setAlpha(1f);
}
showView.setVisibility(VISIBLE);
mCurrentState = showState;
if (null != mListener) {
mListener.onStateChanged(showState);
}
isAniming = false;
}
}
/**
* 參數(shù)依次為:顯示的狀態(tài)适贸、顯示之后的狀態(tài)碼拜姿、要顯示的 View、當(dāng)前的 View
*
* @param showState
* @param afterState
* @param showView
* @param currentView
*/
private void showAlpha(final int showState, final int afterState, final View showView,
final View currentView) {
ObjectAnimator currentAnim = ObjectAnimator.ofFloat(currentView, "alpha", 1, 0);
currentAnim.setDuration(250L);
final ObjectAnimator showAnim = ObjectAnimator.ofFloat(showView, "alpha", 0, 1);
showAnim.setDuration(250L);
showAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (null != mListener) {
mListener.onStateChanged(showState);
}
if (afterState != VIEW_TAG) {
showViewAnim(afterState, VIEW_TAG);
} else {
isAniming = false;
}
}
});
currentAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
currentView.setVisibility(GONE);
showView.setVisibility(VISIBLE);
showAnim.start();
mCurrentState = showState;
}
});
currentAnim.start();
}
public int getCurrentState() {
return mCurrentState;
}
public View getStateView(int state) {
if (state <= VIEW_TAG) {
throw new RuntimeException("ViewState 不在目標(biāo)范圍");
}
return mViews.get(state);
}
public void addUserView(int state, int layId) {
setUserDefView(state, null, layId);
}
public void addUserView(int state, View view) {
setUserDefView(state, view, -1);
}
private void setUserDefView(int state, View view, int layId) {
if (state <= 0) {
throw new RuntimeException("自定義的 ViewState TAG 必須大于 0");
}
if (null == view && layId != -1) {
view = LayoutInflater.from(mContext).inflate(layId, this, false);
}
addViewToHash(view, state);
addViewInLayout(view, -1, view.getLayoutParams());
}
}
簡單說明一下,繼承 FrameLayout 是因為幀布局是效率最高的布局壁却,添加 View 到布局中用的是addViewInLayout展东,這里解釋一下為什么不用addView,因為addView會觸發(fā) requestLayout盐肃,addViewInLayout會先添加進(jìn)去砸王,然后再統(tǒng)一觸發(fā)布局,這個控件的用法非常簡單谦铃,控件里面已經(jīng)內(nèi)置了很多常用的場景類型,你可以通過 addUserView()這個方法來添加你的 View瘪菌,目前只有一個過渡動畫嘹朗,后續(xù)考慮迭代。
項目的 Github 地址 https://github.com/MarkRaoAndroid/EasyStateView