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();
    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 本笔记整理自: https://www.gitbook.com/book/tom510230/android_...
    01_小小鱼_01阅读 989评论 0 4
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 时间易逝,催人老去。转瞬间,一切早已被定格远去,所有的遗憾都只能默默留存,也许生活中“不想起”已成为常态,可午夜梦...
    时光色阅读 347评论 0 0
  • 一 初春的无界村,山青水秀,阡陌塘池,柏杨垂柳,一幅世外桃源的田园风光。 萧宁走在村外的田埂上,眼睛环视着四周,目...
    书成我读阅读 445评论 0 10