谷歌电子市场开发流程(3)-关于加载界面的处理

本文介绍了一个App中如何处理不同状态的加载页面,包括未加载、加载中、加载失败、数据为空及加载成功等状态,并详细解释了如何通过自定义View和抽象方法实现页面的灵活切换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇说到谷歌电子市场app的大体框架,重点是怎样填充viewpager,我们使用了一个个Fragment去作为页面的载体,这篇我们将怎样在Fragment中初始化页面。

我们知道,Fragment只是一个View的载体,也就是说View是显示在Fragment中,所以,初始化一个Fragment需要实现onCreateView()方法,返回的View就是显示在Fragment中的页面,因此,我们要编辑一个个的View来作为onCreateView()方法的返回值,而此项目中包含的7个标签页Fragment都是继承BaseFragment,为什么要这么做呢?我们可以用面向对象的思想来思考,上篇提到过,各个标签页显示的View有相似之处。在对网络发出请求时,根据网络状态,页面会显示不同的状态。

1.未加载和没有数据状态。

2.加载失败

3.正在加载

4.加载成功

大致分为五种状态(未加载和没有数据显示相同界面),其中只有加载成功之后页面才会显示从网络中获取到的数据,而其他四种状态各个页面显示的都是相同的View,这就是相似之处,而共同之处可以的父类就进行初始化,子类只需直接继承即可。因此在BaseFragment中,我们可以直接自定义一个View类,去填充它。接下来问题就是,该自定义一个什么View才合适呢?

在开发其他app时,每个Activity几乎都会为它配置一个相应的Layout文件,也就是显示在Activity中的界面,更直白的说,就是填充在Activity中的View,而Layout文件就是布局文件,我们知道,android中有五大布局,LinerLayout(线性布局)、FrameLayout(帧布局)、TableLayout(表格布局)、RelativeLayout(相对布局)、AbsoluteLayout(绝对布局),其中帧布局是五大布局中最简单的一个布局,在这个布局中,整个界面被当成一块空白备用区域,这正是我们需要的,也就相当于在Fragment中放了一块空白的画布,我们只需要在上面画出我们所需要界面效果即可。因此,自定义View可以继承FrameLayout。

新建Loadingpage类,继承FrameLayout,实现三个构造方法。然后在BaseFragment初始化一个LoadingPage对象,将其作为onCreateView()的返回值。

现在画布有了,我们就要开始动手绘画了。

1.考虑到网络加载的五种状态,首先设置一些代表这些状态的标记常量

private static final int STATE_LOAD_UNDO=1;//未加载
private static final int STATE_LOAD_LOADING=2;//加载中
private static final int STATE_LOAD_ERROR=3;//加载失败
private static final int STATE_LOAD_EMPTY=4;//数据为空
private static final int STATE_LOAD_SUCCESS=5;//加载成功

2.开始初始化页面,上面说过,只有加载成功的页面没有相似,其它四种状态我们都可在此类中初始化

(1)对上述四种状态编写相应的布局文件

(2)使用View.inflate()方法,将布局文件转换为一个个的View

(3)调用addView()方法将View添加到FrameLayout中

运行之后,发现在界面中各个状态对应的页面重叠在一起,那么,接下来就需要对各种状态进行判断,来判断何种页面该显示和不该显示。

3.创建一个showRightPage()方法,在此方法中实现判断逻辑

private void showRightPage() {
    mLoadingEmptyPage.setVisibility((mCurrentState==STATE_LOAD_UNDO||mCurrentState==STATE_LOAD_EMPTY)?VISIBLE:GONE);
    mLoadingErrorPage.setVisibility(mCurrentState==STATE_LOAD_ERROR?VISIBLE:GONE);
    mLoadingPage.setVisibility((mCurrentState == STATE_LOAD_LOADING) ? VISIBLE : GONE);
    if (mLoadingSuccessPage==null&&mCurrentState==STATE_LOAD_SUCCESS){
        mLoadingSuccessPage = onCreateSuccessPage();
        if (mLoadingSuccessPage!=null){
            addView(mLoadingSuccessPage);
        }
    }
}

这个方法主要就是使用View的setVisbility()方法来实现view的显示和隐藏。

再运行之后,发现运行正常,得到我们想要的效果。

现在,我们需要解决的就是加载成功状态页面的初始化,这里逻辑比较绕,需要好好整理思路。

首先,我们知道,每一个Fragment里的加载成功页面是各不相同的,因此,父类中也就是BaseFragment中不能决定各子类的页面的显示效果,换句话说,加载成功页面不能在父类中初始化,那么,该怎样让子类决定该如何加载呢?有没有一种类,是在父类声明,而在子类中实现的呢?

没错,就是抽象类,面向对象的思想告诉我们,我们可以在抽象类中声明抽象方法,继承抽象类的子类必须要实现其父类未实现的抽象方法,这正是我们想要的。

1.将LoadingPage类改为抽象类,在其中添加onCreateSuccessPage()抽象方法,交给其子类实现,因为LoadingPage类不知道该如何初始化加载成功页面,只能交给其子类去实现。由于BaseFragment中初始化了LoadingPage类的对象,因此,也需要实现其抽象方法。

mLoadingPage = new LoadingPage(UIUtils.getContext()){
    @Override
    public View onCreateSuccessPage() {
        return null;
    }
};

2.但是,此时又出现了问题,BaseFragment也不知道该如何初始化加载成功页面,所以,我们再将BaseFragment类改为抽象类,创建onCreateSuccessPage()抽象方法,交给各自的标签Fragment去实现。

mLoadingPage = new LoadingPage(UIUtils.getContext()){
    @Override
    public View onCreateSuccessPage() {
        return BaseFragment.this.onCreateSuccessPage();
    }
};

现在,我们实现了加载成功页面的初始化,但是,新的问题又出现了,我们知道,加载成功是需要请求数据的,每个页面都需要请求数据,这又是相似之处,那么就可以在父类去初始化

1.在LoadingPage类中创建一个LoadingData()方法,因为要请求网络数据,所以必须开启子线程去加载,而具体如何加载,请求何种网络,以什么方式去请求,LoadingPage都不知道,那么就只有交给其他类去实现,所以需要再创建一个onLoad()抽象方法。在这里还要注意onLoad()的返回值。onLoad()需不需要返回值呢?

此时又要考虑到网络状态的问题,对于请求数据,可能就有三种情况,第一种是最好的,加载成功了,我们获取到了数据,页面正常运行了,第二种呢,就是加载失败,比如说服务器崩溃了,手机没联网,这些情况都可能导致加载失败,一旦加载失败,那么就要回到加载失败状态的页面,第三种,就是数据为空的情况,网络都正常,但是服务器中没有数据提供,那么请求到的数据也就是空的,那么就必须返回到数据为空的状态页面。

既然有网络状态的问题,那么,onLoad()方法就必须要有一个返回值,告诉LoadingData()方法到底有没有接收到数据,以便各个标签页显示相应的加载页面,因为除了加载成功的页面,其他两种状态的页面都在LoadingPage类中初始化,而LoadingData()是在LoadingPage类中的,所以onLoad()方法需要返回值。

那么返回值是什么呢?包含多个数据,我们首先想到的就是一个集合去封装,但是我们又知道,这三种状态需要和上面五种网络状态的标记常量关联起来,这时,就可以想到使用枚举是最好的,枚举中的每一个成员都代表一个对象,我们去实现它的一个带参的构造方法去覆盖默认的构造方法,这个参数就可以设置为int型(因为,代表上述五种网络状态的标记常量是int型的,我需要将标记常量作为参数去和每一个枚举成员关联起来)。

public enum ResultState{
    STATE_SUCCESS(STATE_LOAD_SUCCESS),STATE_EMPTY(STATE_LOAD_EMPTY),STATE_ERROR(STATE_LOAD_ERROR);
    private int state;
    ResultState(int state){
        this.state=state;
    }
    public int getState(){
        return state;
    }
}

这样一个onLoad()抽象方法就构造完成了。此时,我们在LoadingData中就可以根据onLoad()的返回值去做相应的UI更新,此处还需要注意,因为LoadingData()方法中单独开了一个子线程去实现请求网络数据,而更新UI必须在主线程中实现,这时,UIUtils工具类就体现其作用了,我们在上一篇中就说过,UIUtils中维护了一个RunningMainThread()方法,其作用就是将线程抛给主线程去实现,此处就可以直接调用这个方法,去实现UI更新。

public void LoadingData(){
    if (mCurrentState!=STATE_LOAD_SUCCESS){
        mCurrentState=STATE_LOAD_SUCCESS;
        new Thread(new Runnable() {
            @Override
            public void run() {
                final ResultState resultState=onLoad();
                UIUtils.RunningMainThread(new Runnable() {
                    @Override
                    public void run() {
                        if (resultState!=null){
                            mCurrentState=resultState.getState();
                        }
                        showRightPage();
                    }
                });
            }
        }).start();
    }
}

至此,LoadingData()就已经维护完成。但是,我们也发现这个方法还没有任何调用,那么,在何处调用这个方法呢?

当我们打开应用时,页面就开始请求数据,在滑动ViewPager里的页面时,我们也需要去请求数据,也就是说,当前ViewPager处于哪一个位置,哪一个位置的Fragment里的页面就要请求数据,所以,我们在MainActivity中,实现onPageChangeListener()监听器,监听其中的页面选择,在其中的onPageSelector()方法中去调用onLoad()

这就是整个关于加载页面的处理流程,我们可以看到,在这段逻辑中,大量的使用了封装,知识面向对象得核心思想,封装大大简化了我们的代码量,但是同时也使代码的复杂度大大上升了,必须环环相扣,才能做到滴水不漏,使应用运行成功。此处再附上两张逻辑处理图帮助自己理解。

ps:终于敲完了,已经凌晨4点。。。不说了,睡觉,明天继续!!!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值