Android 多状态页面View

前言

作为一名Android开发,经常会遇到页面无数据、网络异常,这时候我们都会在xml中设置很多ViewGroup来包裹不同的状态,我刚开始做开发的时候也喜欢这么做,这样写的坏处是一是xml被写的很乱,有时候如果view过多,我们自己也会感觉很乱,另外一点就是一个项目中有很多这种页面,有时候网络异常等页面一个项目都是相同的,所以只要做一份即可,第三点就是一个页面放多种ViewGroup,势必要最外层要包裹一层,还有就是如果只是设置View.VISIBLE而不是View.GONE,ViewGroup一样会绘制,这两种情况都会造成过度绘制,作为一名开发,我们有能力的情况下一定要去做优化处理。


无数据.png

网络异常.png

自定义属性介绍

<!--这是多类型页面-->
    <declare-styleable name="MultiStateView">
        <attr name="msv_loadingView" format="reference" />
        <attr name="msv_successView" format="reference" />
        <attr name="msv_unknownView" format="reference" />
        <attr name="msv_netErrorView" format="reference" />
        <attr name="msv_emptyView" format="reference" />
        <attr name="msv_emptyViewImage" format="reference" />
        <attr name="msv_emptyViewText" format="reference|string" />
    </declare-styleable>

看到命名应该都知道其中的含义了,有一点需要说明,因为一个项目中关于view的属性有且只能有一个名字相同的,所以我加了前缀,如果出现冲突,修改自定义属性的名称即可。

  • 冲突包括:
    1、与系统view属性冲突
    2、与AAR中的自定义属性冲突
    3、与其他第三方自定义属性冲突

对于项目中的不同状态有不同的对待,一般情况下,一个项目中关于网络异常、加载动画、未知错误都是一样的状态,对于空页面就可能会有不同的状态,所以我们对于不同的网络异常做不同的处理。

共同的状态

项目中共同的状态,我个人分为:网络异常、加载动画、未知错误。大致封装过程都是一样的,我这里只放出loading页面,如下:

显示加载状态
 /**
     * 显示加载中的状态
     */
    public void showLoading() {
        if (null == mLoadingView) {
            mLoadingView = mInflater.inflate(mLoadingViewId, null);
        }
        if (mLoadingView != null) {
            removeAllViews();
            addView(mLoadingView, 0, params);
        } else {
            throw new NullPointerException("you have to set loading view before that");
        }
    }
  • 说明:
    removeAllViews(); 调用此方法是为了让当前页面显示之后一种状态
重新设置自定义加载view
/**
     * 设置自定义的加载页面
     *
     * @param layoutResID
     */
    public void setLoadingView(@LayoutRes int layoutResID) {
        View inflate = mInflater.inflate(layoutResID, null);
        setLoadingView(inflate);
    }

    public void setLoadingView(View view) {
        if (view == null) {
            throw new NullPointerException("you set loading view is null");
        }
        mLoadingView = view;
    }
获取加载中的view
 /**
     * 获取加载页面
     */
    public View getLoadingView() {
        if (null == mLoadingView) {
            mLoadingView = mInflater.inflate(mLoadingViewId, null);
        }
        return mLoadingView;
    }
  • 说明:
    关于网络异常的情况下,一般都会重试,如下是重试回调接口
/**
     * 重新加载页面的回调接口
     */
    public interface OnReLodListener {
        void onReLoad();
    }

空数据页面处理

对于页面数据为空状态,因为项目中可能页面数据类型不同,所有有时候会出现不同的形式的空页面,提供了文本和图片两种属性,如果还是不够使用,可以设置layout的形式。

显示空页面
/**
     * 显示无数据状态
     */
    public void showEmpty() {
        if (null == mEmptyView) {
            mEmptyView = mInflater.inflate(mEmptyViewId, null);
        }

        if (mEmptyView != null&&currentState!=STATE_EMPTY) {
            removeAllViews();
            handleDiffEmpty();
            addView(mEmptyView, 0, params);
            currentState = STATE_EMPTY;
        } else {
            throw new NullPointerException("you have to set empty view before that");
        }
    }

    /**
     * 对于页面数据为空处理
     */
    private void handleDiffEmpty() {
        if (mEmptyView instanceof ViewGroup) {
            ViewGroup mEmptyViews = (ViewGroup) mEmptyView;
            ImageView emptyImage = (ImageView) mEmptyViews.getChildAt(0);
            TextView emptyDesc = (TextView) mEmptyViews.getChildAt(1);
            if (emptyImage != null && mEmptyDrawable != null) {
                emptyImage.setImageDrawable(mEmptyDrawable);
            }
            if (emptyDesc != null && TextUtils.isEmpty(mEmptyDesc)) {
                emptyDesc.setText(mEmptyDesc);
            }
        }
    }

使用

在xml中:

<com.example.multistateview.MultiStateView
        android:id="@+id/multiStateView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        app:msv_emptyViewText="我是空页面描述"
        app:msv_successView="@layout/successlayout">

    </com.example.multistateview.MultiStateView>

*说明:
如果要获取某一个状态下的View,需要先获取当前状态的View,事例如下:

View successView = multiStateView.getSuccessView();
TextView tv_empty_desc = successView.findViewById(R.id.tv_empty_desc);

其中的原理和代码都非常简单,我把全部代码贴到下面,方便使用。

/**
 * @author gexinyu
 */
public class MultiStateView extends FrameLayout {

    private static final int STATE_LOADING = 2;
    private static final int STATE_SUCCESS = 1;
    private static final int STATE_EMPTY = 0;
    private static final int STATE_NET_ERROR = -1;
    private static final int STATE_UNKNOWN = -2;
    private static final int STATE_NEW_STATE = -3;

    private Context mContext;
    //四种状态默认的viewid
    private int mLoadingViewId;
    private int mSuccessViewId;
    private int mEmptyViewId;
    private int mUnKnownViewId;
    private int mNetErrorViewId;
    private Drawable mEmptyDrawable;
    private String mEmptyDesc;

    //四种展示的view
    private View mLoadingView;
    private View mSuccessView;
    private View mNetErrorView;
    private View mEmptyView;
    private View mUnKnownView;
    private View newStateView;//这是新的状态页面

    private LayoutInflater mInflater;
    private ViewGroup.LayoutParams params;
    private OnReLodListener mOnReLodListener;//重新加载的监听

    private int currentState;

    public MultiStateView(@NonNull Context context) {
        this(context, null);
    }

    public MultiStateView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MultiStateView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MultiStateView);

        mLoadingViewId = array.getResourceId(R.styleable.MultiStateView_msv_loadingView, R.layout.base_multi_state_loading);
        mSuccessViewId = array.getResourceId(R.styleable.MultiStateView_msv_successView, 0);
        mEmptyViewId = array.getResourceId(R.styleable.MultiStateView_msv_emptyView, R.layout.base_multi_state_empty);
        mEmptyDrawable = array.getDrawable(R.styleable.MultiStateView_msv_emptyViewImage);
        mEmptyDesc = array.getString(R.styleable.MultiStateView_msv_emptyViewText);
        mUnKnownViewId = array.getResourceId(R.styleable.MultiStateView_msv_unknownView, R.layout.base_multi_state_unknow);
        mNetErrorViewId = array.getResourceId(R.styleable.MultiStateView_msv_netErrorView, R.layout.base_multi_state_neterror);

        array.recycle();
        mInflater = LayoutInflater.from(context);
        params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        showLoading();
    }
    //++++++++++++++++++++++++++++++++加载页面++++++++++++++++++++

    /**
     * 显示加载中的状态
     */
    public void showLoading() {
        if (null == mLoadingView) {
            mLoadingView = mInflater.inflate(mLoadingViewId, null);
        }
        if (mLoadingView != null&&currentState!=STATE_LOADING) {
            removeAllViews();
            addView(mLoadingView, 0, params);
            currentState = STATE_LOADING;
        } else {
            throw new NullPointerException("you have to set loading view before that");
        }
    }

    /**
     * 设置自定义的加载页面
     *
     * @param layoutResID
     */
    public void setLoadingView(@LayoutRes int layoutResID) {
        View inflate = mInflater.inflate(layoutResID, null);
        setLoadingView(inflate);
    }

    public void setLoadingView(View view) {
        if (view == null) {
            throw new NullPointerException("you set loading view is null");
        }
        mLoadingView = view;
    }

    /**
     * 获取加载页面
     */
    public View getLoadingView() {
        if (null == mLoadingView) {
            mLoadingView = mInflater.inflate(mLoadingViewId, null);
        }
        return mLoadingView;
    }


    //++++++++++++++++++++++++++++++++成功页面++++++++++++++++++++

    /**
     * 显示成功状态
     */
    public void showSuccess() {
        if (null == mSuccessView) {
            mSuccessView = mInflater.inflate(mSuccessViewId, null);
        }
        if (mSuccessView != null&&currentState!=STATE_SUCCESS) {
            removeAllViews();
            addView(mSuccessView, 0, params);
            currentState = STATE_SUCCESS;
        } else {
            throw new NullPointerException("you have to set success view before that");
        }
    }

    /**
     * 设置自定义的成功页面
     *
     * @param layoutResID
     */
    public void setSuccessView(@LayoutRes int layoutResID) {
        setSuccessView(mInflater.inflate(layoutResID, null));
    }

    /**
     * 设置自定义的成功页面
     *
     * @param view
     */
    public void setSuccessView(View view) {
        if (view == null) {
            throw new NullPointerException("you set success view is null");
        }
        mSuccessView = view;
    }


    /**
     * 获取成功页面
     */
    public View getSuccessView() {
        if (null == mSuccessView) {
            mSuccessView = mInflater.inflate(mSuccessViewId, null);
        }
        return mSuccessView;
    }


    //++++++++++++++++++++++++++++++++未知错误页面++++++++++++++++++++

    /**
     * 显示未知错误页面
     */
    public void showUnKnown() {
        if (null == mUnKnownView) {
            mUnKnownView = mInflater.inflate(mUnKnownViewId, null);
        }

        if (mUnKnownView != null&&currentState!=STATE_UNKNOWN) {
            removeAllViews();
            addView(mUnKnownView, 0, params);
            currentState = STATE_UNKNOWN;
        } else {
            throw new NullPointerException("you have to set unknown view before that");
        }
    }

    /**
     * 设置自定义的未知错误页面
     *
     * @param layoutResID
     */
    public void setUnKnownView(@LayoutRes int layoutResID) {
        View inflate = mInflater.inflate(layoutResID, null);
        setUnKnownView(inflate);
    }

    /**
     * 设置自定义的未知错误页面
     *
     * @param view
     */
    public void setUnKnownView(View view) {
        if (view == null) {
            throw new NullPointerException("you set unknown view is null");
        }
        mUnKnownView = view;
    }

    /**
     * 获取未知错误页面
     */
    public View getUnKnownView() {
        if (null == mUnKnownView) {
            mUnKnownView = mInflater.inflate(mUnKnownViewId, null);
        }
        return mUnKnownView;
    }


    //++++++++++++++++++++++++++++++++网络错误页面++++++++++++++++++++

    /**
     * 显示加载失败(网络错误)状态 带监听器的
     */
    public void showNetError() {
        if (null == mNetErrorView) {
            mNetErrorView = mInflater.inflate(mNetErrorViewId, null);
        }

        if (mNetErrorView != null&&currentState!=STATE_NET_ERROR) {
            removeAllViews();
            addView(mNetErrorView, 0, params);
            currentState = STATE_NET_ERROR;
            mNetErrorView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    showReLoading();
                }
            });
        } else {
            throw new NullPointerException("you have to set unknown view before that");
        }
    }


    /**
     * 设置自定义的网络异常
     *
     * @param layoutResID
     */
    public void setNetErrorView(@LayoutRes int layoutResID) {
        View inflate = mInflater.inflate(layoutResID, null);
        setNetErrorView(inflate);
    }

    /**
     * 设置自定义的网络异常
     *
     * @param view
     */
    public void setNetErrorView(View view) {
        if (view == null) {
            throw new NullPointerException("you set net error view is null");
        }
        mNetErrorView = view;
    }


    /**
     * 设置获取网络错误页面
     */
    public View getNetErrorView() {
        if (null == mNetErrorView) {
            mNetErrorView = mInflater.inflate(mNetErrorViewId, null);
        }
        return mNetErrorView;
    }


    //++++++++++++++++++++++++++++++++空页面页面++++++++++++++++++++

    /**
     * 显示无数据状态
     */
    public void showEmpty() {
        if (null == mEmptyView) {
            mEmptyView = mInflater.inflate(mEmptyViewId, null);
        }

        if (mEmptyView != null&&currentState!=STATE_EMPTY) {
            removeAllViews();
            handleDiffEmpty();
            addView(mEmptyView, 0, params);
            currentState = STATE_EMPTY;
        } else {
            throw new NullPointerException("you have to set empty view before that");
        }
    }

    /**
     * 对于网络异常处理
     */
    private void handleDiffEmpty() {
        if (mEmptyView instanceof ViewGroup) {
            ViewGroup mEmptyViews = (ViewGroup) mEmptyView;
            ImageView emptyImage = (ImageView) mEmptyViews.getChildAt(0);
            TextView emptyDesc = (TextView) mEmptyViews.getChildAt(1);
            if (emptyImage != null && mEmptyDrawable != null) {
                emptyImage.setImageDrawable(mEmptyDrawable);
            }
            if (emptyDesc != null && TextUtils.isEmpty(mEmptyDesc)) {
                emptyDesc.setText(mEmptyDesc);
            }
        }
    }

    /**
     * 设置自定义的空页面
     *
     * @param layoutResID
     */
    public void setEmptyView(@LayoutRes int layoutResID) {
        View inflate = mInflater.inflate(layoutResID, null);
        setEmptyView(inflate);
    }

    /**
     * 设置自定义的空页面
     *
     * @param view
     */
    public void setEmptyView(View view) {
        if (view == null) {
            throw new NullPointerException("you set net empty view is null");
        }
        mEmptyView = view;
    }


    /**
     * 设置获取空页面
     */
    public View getEmptyView() {
        if (null == mEmptyView) {
            mEmptyView = mInflater.inflate(mEmptyViewId, null);
        }
        return mEmptyView;
    }


    /**
     * 设置自定义的新增状态页面空页面
     *
     * @param layoutResID
     */
    public void setNewStateView(@LayoutRes int layoutResID) {
        View inflate = mInflater.inflate(layoutResID, null);
        setNewStateView(inflate);
    }


    public void setNewStateView(View view) {
        if (view == null) {
            throw new NullPointerException("you set net new state view is null");
        }
        newStateView = view;
    }

    /**
     * 显示新状态view
     */
    public void showNewStateView() {
        if (newStateView != null&&currentState!=STATE_NEW_STATE) {
            removeAllViews();
            addView(newStateView, 0, params);
            currentState = STATE_NEW_STATE;
        } else {
            throw new NullPointerException("you set new state view is null");
        }
    }


    public View getNewStateView() {
        if (newStateView == null) {
            throw new IllegalArgumentException("you has not set new state view");
        }
        return newStateView;
    }


    /**
     * 再次加载数据
     */
    private void showReLoading() {
        //第一步重新loading
        if (mOnReLodListener != null) {
            showLoading();
            mOnReLodListener.onReLoad();
        } else {
            //未设置重新加载回调
            Log.e("TAG", "请设置重新加载监听");
        }
    }


    /**
     * 外部回调
     *
     * @param onReLodListener
     */
    public void setOnReLodListener(OnReLodListener onReLodListener) {
        this.mOnReLodListener = onReLodListener;
    }

    /**
     * 重新加载页面的回调接口
     */
    public interface OnReLodListener {
        void onReLoad();
    }

}

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 4,210评论 1 15
  • 本笔记整理自: https://www.gitbook.com/book/tom510230/android_...
    01_小小鱼_01阅读 428评论 0 3
  • 时间易逝,催人老去。转瞬间,一切早已被定格远去,所有的遗憾都只能默默留存,也许生活中“不想起”已成为常态,可午夜梦...
    时光色阅读 55评论 0 0
  • 一 初春的无界村,山青水秀,阡陌塘池,柏杨垂柳,一幅世外桃源的田园风光。 萧宁走在村外的田埂上,眼睛环视着四周,目...
    尘埃落定_岁月静好阅读 82评论 0 6
  • 我和叔本华一样认为书看多了,不好。一来,脑子里都是别人的思想,人就傻了(叔本华言“不要让自己的大脑变成别人...
    子健阅读 91评论 1 0