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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章