在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>