如何打造一个简单方便的LoadingLayout

写在前面

android项目中经常需要从网络服务器端获取数据并显示到页面上,由于网络速度不稳定,客户端发起请求而服务端还未返回数据时,页面需要有加载中状态;如果请求失败,页面又需要显示为网络连接失败状态;如果这次请求的数据为空,页面还需要显示为暂无数据;只有服务端返回有效的数据时,页面才会正常显示。

这个需求在平时的开发过程中非常常见,因此我写了一个简单的多状态布局,包含这四种状态,方便在以后的项目中使用。这个loadingLayout的代码我全都上传到github上了,本来想发布到jCenter上,好给大家轻松通过gradle构建,后来又想了下,这个功能很简单,添加gradle依赖太重了,大家可以通过这篇文章自己实现,并配合自己的项目进行修改和扩展。

源码及demo地址:https://github.com/mavsforlife/LoadingLayoutDemo

大家可以去看看给我提意见啊,更欢迎star哈哈哈~~~

好的,啰嗦了一大堆,下面我们来正式开整,快速打造一个简单的loadingLayout。

如何实现

大家应该很容易想到FrameLayout,将loading error empty content 这四种状态下的view放入一个FrameLayout中,提供方法根据状态来显示某一层view,隐藏其他层。

首先我们新建一个LoadingLayout类继承自FrameLayout,并定义mEmptyView mErrorView mLoadingView 三个View对象,定义两个onclickListener用于处理重新加载的逻辑(稍后会说到)。

public class LoadingLayout extends FrameLayout {

    private View mEmptyView, mErrorView, mLoadingView;
    private OnClickListener onErrorClickListener;
    private OnClickListener onEmptyClickListener;
    private LayoutInflater mLayoutInflater;
}   

初始化

我们在它的构造方法中完成一些初始化的工作

public LoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LoadingLayout, 0, 0);

        try {
            int emptyView = a.getResourceId(R.styleable.LoadingLayout_emptyView, R.layout.empty_view);
            int errorView = a.getResourceId(R.styleable.LoadingLayout_errorView, R.layout.error_view);
            int loadingView = a.getResourceId(R.styleable.LoadingLayout_loadingView, R.layout.loading_view);

            mLayoutInflater = LayoutInflater.from(getContext());
            mEmptyView = mLayoutInflater.inflate(emptyView, this, true);
            mErrorView = mLayoutInflater.inflate(errorView, this, true);
            mLoadingView = mLayoutInflater.inflate(loadingView, this, true);
        }finally {
            a.recycle();
        }

    }

上面这段代码非常的简单,初始化了这个loadingView以后,在这个viewGroup中依次添加了emptyView errorView loadingView 这三个子view。由于LoadingLayout是继承自FrameLayout的,因此这三个子view是叠成3层显示的。

自定义属性

大家看到了我定义了emptyView,errorView,loadingView 三个属性,并且设置了默认值,所以我们要先在android app的styles文件中先定义好这三个属性,并且创建empty_view, error_view, loading_view三个默认的xml布局文件。


    
    
    

  • 默认的empty_view文件


    

    

  • 默认的error_view文件

    

    

    
  • 默认的loading_view文件


    

        

        

    

重写onFinishInflate方法

当View及其子View从xml文件中加载完成以后,会调用onFinishInflate方法,我们先将所有子view都隐藏。

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        for (int i = 0; i < getChildCount() - 1; i++) {
            getChildAt(i).setVisibility(GONE);
        }
    }

如何显示不同状态的view

接下来就是重点了,我们根据不同的业务场景显示不同的view,其实非常简单,我们将loadingLayout的某一层布局显示出来,隐藏其他子布局就好了。由于我们是按照emptyView errorView loadingView contentView 这样的顺序添加的,因此可以通过view.getChildAt()方法,显示或隐藏指定布局。

  • 显示emptyView(emptyView为getChildAt(0))
    public void showEmpty() {
        for (int i = 0; i < this.getChildCount(); i++) {
            View child = this.getChildAt(i);
            if (i == 0) {
                child.setVisibility(VISIBLE);
            } else {
                child.setVisibility(GONE);
            }
        }
    }
  • 显示errorView(errorView为getChildAt(1))
    public void showError() {
        for (int i = 0; i < this.getChildCount(); i++) {
            View child = this.getChildAt(i);
            if (i == 1) {
                child.setVisibility(VISIBLE);
            } else {
                child.setVisibility(GONE);
            }
        }
    }
  • 显示loadingView(loadingView为getChildAt(2))
    public void showLoading() {
        for (int i = 0; i < this.getChildCount(); i++) {
            View child = this.getChildAt(i);
            if (i == 2) {
                child.setVisibility(VISIBLE);
            } else {
                child.setVisibility(GONE);
            }
        }
    }
  • 显示contentView(contentView为getChildAt(3))
    public void showContent() {
        for (int i = 0; i < this.getChildCount(); i++) {
            View child = this.getChildAt(i);
            if (i == 3) {
                child.setVisibility(VISIBLE);
            } else {
                child.setVisibility(GONE);
            }
        }
    }

设置重试点击事件

在实际项目中,如果页面为空,可能业务上需要我们提供一个按钮点击跳转到首页? 购买页面? 其他指定页面?;如果因为网络原因加载失败,页面上一般会有一个重新加载按钮。这就是我在文章的开头说到的两个onclickListener的作用.

我们首先要提供两个set方法来设置onclickListener

    public LoadingLayout setOnEmptyClickListener(OnClickListener onEmptyClickListener) {
        this.onEmptyClickListener = onEmptyClickListener;
        return this;
    }

    public LoadingLayout setOnErrorClickListener(OnClickListener onErrorClickListener) {
        this.onErrorClickListener = onErrorClickListener;
        return this;
    }

然后再在onFinishInflate方法中,给按钮的点击事件实现这两个接口。


    findViewById(R.id.btn_empty).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (null != onEmptyClickListener) {
                onEmptyClickListener.onClick(v);
            }
        }
    });

    findViewById(R.id.btn_error).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (null != onErrorClickListener) {
                onErrorClickListener.onClick(v);
            }
        }
    });

一些额外提供的方法

前面的工作做完,基本已经实现了需求,只是有时候我们不方便在xml中定义emptyView,又不想使用自定义的emptyView,所以我又写了一些扩展方法。

在java类中直接设置emptyView/errorView/loadingView。

    public LoadingLayout setEmptyView(@LayoutRes int layout) {
        removeView(getChildAt(0));
        mEmptyView = mLayoutInflater.inflate(layout, null, true);
        addView(mEmptyView, 0);
        onFinishInflate();
        return this;
    }

    public LoadingLayout setErrorView(@LayoutRes int layout) {
        removeView(getChildAt(1));
        mErrorView = mLayoutInflater.inflate(layout, null, true);
        addView(mErrorView, 1);
        onFinishInflate();
        return this;
    }

    public LoadingLayout setLoadingView(@LayoutRes int layout) {
        removeView(getChildAt(2));
        mLoadingView = mLayoutInflater.inflate(layout, null, true);
        addView(mLoadingView, 2);
        return this;
    }

修改自定义emptyView/errorView的文字

    public LoadingLayout setEmptyText(String text) {
        ((TextView) findViewById(R.id.btn_empty)).setText(text);
        return this;
    }

    public LoadingLayout setErrorText(String text) {
        ((TextView) findViewById(R.id.tv_error)).setText(text);
        return this;
    }

自定义emptyView及errorView的注意事项。

我在ids.xml文件中定义了三个id。

    
    
    

在自定义errorView中,一定要创建一个button并将id设置为btn_error,创建一个textView并将id设置为tv_error;同时在自定义emptyView时,要创建一个textView并将id设置为btn_epmty,否则会引发nullPointerException,切记切记!

最终效果及使用方法

下图就是在activity中最终的显示效果啦,忽略丑丑的布局,仓促写的。。。

如何打造一个简单方便的LoadingLayout_第1张图片
simple_use.gif

使用方法:首先在activity或fragment的布局文件中插入loadingLayout,loadingLayout中包裹的就是contentView。(只允许包裹一个子 view,因此如果有多个view,需要用ScrollView等ViewGroup再包一层)

    

       

   

然后在java代码中,通过findViewById方法初始化view,并实现点击重试接口。使用showContent方法显示contView。

    loadingLayout = (LoadingLayout) findViewById(R.id.loading_layout);
    
    loadingLayout
                .setOnEmptyClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        loadingLayout.showLoading();
                    }
                })
                .setOnErrorClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        loadingLayout.showLoading();
                    }
                })
                .showContent();

最后

感谢大家,撒花~~

以及,再次求star啊啊啊啊啊

你可能感兴趣的:(如何打造一个简单方便的LoadingLayout)