在Android開發中經常會遇到如下的開發情況:
- 1、底部有一排單選按鈕
- 2、根據按鈕點擊可以切換上面對應界面
- 3、在展示上面的界面過程中,經常會遇到四種情況(1、正在加載 2、數據爲空 3、加載錯誤 4、加載數據成功)不同的情況會展示不同的界面情況
其實這裏主要涉及到了狀態佈局的設計及使用,下面給出我的通用解決方式。
自定義狀態佈局 StateLayout.java:
/**
*
* 狀態: 1、正在加載、 2、數據爲空 3、加載錯誤 4、加載數據成功
* 每個狀態對應一個佈局 loadingView emptyView errorView 由 getContentLayoutRes() 這個方法返回View
* <p/>
* 1、同時只顯示一種狀態的View
* 2、當前顯示那種狀態佈局應該由外部的來決定(當前的請求的數據決定:沒有網絡、json解析錯誤)
* 3、能夠根據狀態和狀態佈局對應
*/
public class StateLayout extends FrameLayout {
public static final int STATE_LOADING = 0;
public static final int STATE_EMPTY = 1;
public static final int STATE_ERROR = 2;
public static final int STATE_SUCCESS = 3;
@IntDef({STATE_LOADING, STATE_EMPTY, STATE_ERROR, STATE_SUCCESS})
@Retention(RetentionPolicy.SOURCE)
public @interface States {}
public StateLayout(Context context) {
this(context, null);
}
public StateLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public StateLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedStateArray = context.obtainStyledAttributes(attrs, R.styleable.state_styleable);
int indexCount = typedStateArray.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int state = typedStateArray.getInteger(i, -1);
Log.d("StateLayout", "state:" + state);
addStateLayout(state);
}
typedStateArray.recycle();
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.state_layout_styleable);
int successReesourceId = typedArray.getResourceId(0, -1);
if (successReesourceId != -1) {
addStateLayoutRes(STATE_SUCCESS, successReesourceId);
}
typedArray.recycle();
}
private void addStateLayout(int state) {
switch (state) {
case STATE_LOADING:
addStateLayoutRes(STATE_LOADING, R.layout.layout_loding);
break;
case STATE_EMPTY:
addStateLayoutRes(STATE_EMPTY, R.layout.layout_empty);
break;
case STATE_ERROR:
addStateLayoutRes(STATE_ERROR, R.layout.layout_error);
break;
default:
break;
}
}
private SparseArray<View> mViews = new SparseArray<>();
public void addStateLayoutRes(@States int state,@LayoutRes int layoutRes) {
addStateView(state, View.inflate(getContext(), layoutRes, null));
}
//添加狀態佈局到FrameLayout
public void addStateView(@States int state,@NonNull View stateView) {
mViews.put(state, stateView);
//同一種作用只能對應一種佈局,如佈局不一樣只要狀態一樣後面添加就應該把前面的覆蓋
View preView = mViews.get(state);
if (preView != null) {
removeView(preView);
}
addView(stateView);
//添加狀態佈局是否顯示由當時的狀態mState來決定
stateView.setVisibility(mState == state ? VISIBLE : INVISIBLE);
}
//記錄當前的狀態
private int mState = STATE_LOADING;
//根據狀態顯示對應的佈局, STATE_LOADING---->STATE_ERROR--->STATE_SUCCESS
public void showStateView(@States int state) {
//上一次顯示狀態佈局
View preShowStateView = mViews.get(mState);
if (preShowStateView != null) {
preShowStateView.setVisibility(INVISIBLE);
}
View showStateView = mViews.get(state);
if (showStateView != null) {
showStateView.setVisibility(VISIBLE);
}
mState = state;
}
//提供根據狀態獲取對應狀態佈局
@NonNull
public View getStateView(@States int state) {
return mViews.get(state);
}
}
attrs.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="state_styleable">
<attr name="state_loading" format="integer" />
<attr name="state_empty" format="integer" />
<attr name="state_error" format="integer" />
<attr name="state_success" format="integer" />
</declare-styleable>
<integer name="state_loading_value">0</integer>
<integer name="state_empty_value">1</integer>
<integer name="state_error_value">2</integer>
<integer name="state_success_value">3</integer>
<declare-styleable name="state_layout_styleable">
<attr name="state_success_layout" format="reference" />
</declare-styleable>
</resources>
使用在佈局文件中:
<cn.mgzxc.ui.StateLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/statelayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:state_empty="@integer/state_empty_value"
app:state_error="@integer/state_error_value"
app:state_loading="@integer/state_loading_value"
app:state_success="@integer/state_success_value" />
使用
爲了提高代碼的複用率我們抽取BaseFragment
public abstract class BaseFragment extends Fragment {
protected Context mContext;
private View mViewRoot;
private StateLayout mStateLayout;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (mViewRoot == null) {//判斷是否爲空 防止多次加載
mViewRoot = inflater.inflate(R.layout.fragment_base, container, false);
View rlTitle = mViewRoot.findViewById(R.id.rl_title);
String titleText = getTitleText();
if (!TextUtils.isEmpty(titleText)) {
rlTitle.setVisibility(View.VISIBLE);
((TextView) mViewRoot.findViewById(R.id.textview_tilte)).setText(titleText);
}
mStateLayout = (StateLayout) mViewRoot.findViewById(R.id.statelayout);
//將子類的getContentLayoutRes()佈局添加到成功的狀態
mStateLayout.addStateLayoutRes(StateLayout.STATE_SUCCESS, getContentLayoutRes());
//錯誤狀態的監聽,當沒有網絡的時候出現錯誤,錯誤的狀態佈局應該有點擊事件,當再次連接了網絡,點擊錯誤頁面重新請求數據
mStateLayout.getStateView(StateLayout.STATE_ERROR).setOnClickListener(new View.OnClickListener() {
//點擊錯誤頁面的通知子類去重新請求加載數據
@Override
public void onClick(View v) {
initData();
}
});
//當前顯示狀態:正在加載,
mStateLayout.showStateView(StateLayout.STATE_LOADING);
//加載完成通知子類
View ChildContentView = mStateLayout.getStateView(StateLayout.STATE_SUCCESS);
//通知子類加載界面
initView(ChildContentView);
initData();
}
return mViewRoot;
}
//子類加載數據完成在顯示狀態爲成功的狀態
public void showSuccesStateView() {
mStateLayout.showStateView(StateLayout.STATE_SUCCESS);
}
public void showErroStateView(){
mStateLayout.showStateView(StateLayout.STATE_ERROR);
}
public void showEmptyStateView(){
mStateLayout.showStateView(StateLayout.STATE_EMPTY);
}
public void showLoadingStateView(){
mStateLayout.showStateView(StateLayout.STATE_LOADING);
}
//由子類來重寫
public String getTitleText() {
return "";
}
//子類提供xml佈局的資源
protected abstract int getContentLayoutRes();
protected abstract void initView(View mChildContentView);
protected abstract void initData();
}
再聲明子類的 HomeFragment
public class HomeFragment extends BaseFragment {
@BindView(R.id.textview)
TextView content;
@Override
protected int getContentLayoutRes() {
return R.layout.fragment_more;
}
@Override
protected void initView(View mChildContentView) {
ButterKnife.bind(this, mChildContentView);
}
@Override
protected void initData() {
//加載數據 開啓線程 網絡請求數據
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//1、加載成功
content.setText("加載成功的數據");
showSuccesStateView();
//2、數據請求 爲空 類似購物車 爲空的情況
//showEmptyStateView();
//3、數據請求失敗 比如未聯網
//showErroStateView();
//4、請求階段
//showLoadingStateView
}
}, 3000);
}
}
最後給出MainActivity:
public class MainActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {
private FragmentManager supportFragmentManager;
private ArrayList<Object> fragments;
@BindView(R.id.framelayout)
FrameLayout framelayout;
@BindView(R.id.radiogroup)
RadioGroup radiogroup;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
supportFragmentManager = getSupportFragmentManager();
fragments = new ArrayList<>();
fragments.add(new HomeFragment());
fragments.add(new InvestFragment());
fragments.add(new MeFragment());
fragments.add(new MoreFragment());
radiogroup.setOnCheckedChangeListener(this);
radiogroup.check(R.id.rb_home);
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
int index = group.indexOfChild(group.findViewById(checkedId));
supportFragmentManager.beginTransaction().replace(R.id.framelayout, (Fragment) fragments.get(index)).commit();
}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
}
主頁佈局文件 activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/framelayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<RadioGroup
android:id="@+id/radiogroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_home"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:button="@null"
android:gravity="center"
android:textColor="@color/main_bottom_tv_text_color"
android:drawableTop="@drawable/home_selector"
android:text="首頁"/>
<RadioButton
android:id="@+id/rb_invest"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:button="@null"
android:gravity="center"
android:textColor="@color/main_bottom_tv_text_color"
android:drawableTop="@drawable/invest_selector"
android:text="投資"/>
<RadioButton
android:id="@+id/rb_me"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:button="@null"
android:gravity="center"
android:textColor="@color/main_bottom_tv_text_color"
android:drawableTop="@drawable/me_selector"
android:text="個人"/>
<RadioButton
android:id="@+id/rb_more"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:button="@null"
android:gravity="center"
android:textColor="@color/main_bottom_tv_text_color"
android:drawableTop="@drawable/more_selector"
android:text="更多"/>
</RadioGroup>
</LinearLayout>