Android中状态布局设计

在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>
发布了79 篇原创文章 · 获赞 62 · 访问量 8万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章