美文网首页TECH_ANDROIDAndroidAndroid开发
优雅地处理加载中(loading),重试(retry)和无数据(

优雅地处理加载中(loading),重试(retry)和无数据(

作者: KingJA | 来源:发表于2017-09-12 10:32 被阅读3866次

LoadSir是一个高效易用,低碳环保,扩展性良好的加载反馈页管理框架,在加载网络或其他数据时候,根据需求切换状态页面,可添加自定义状态页面,如加载中,加载失败,无数据,网络超时,占位图,登录失效等常用页面。可配合网络加载框架,结合返回状态码,错误码,数据进行状态页自动切换,封装使用效果更佳。
LoadSir现在版本已经升级至1.3.6,相关内容请参考Github最新说明Github传送门

本文前面是使用流程,基于1.2.2完成,后面是原理解析,如果大家有兴趣,可耐心看完。

效果预览

in Activity in View in Fragment
Placeholder Muitl-Fragment ViewPage+Fragment

使用场景

下面为大家常见的加载反馈页面:

loading error timeout
empty custom placeholder

面对这么多状态页面,你是不是还在用include的方式,setVisibility(View.VISIBLE/GONE),这种方式即不方便控制,也造成了视图层级冗余(你要把所有状态布局include进一个视图)。如果有一种工具,能把这些事都做了就好了。恰好, LoadSir 把这些事做了,接下来我们就来了解一下它。

LoadSir的功能及特点

  • 支持Activity,Fragment,Fragment(v4),View状态回调
  • 适配多个Fragment切换,及Fragment+ViewPager切换,不会状态叠加或者状态错乱
  • 利用泛型转换输入信号和输出状态,可根据网络返回体的状态码或者数据返回自动适配状态页,实现全局自动状态切换
  • 只加载唯一一个状态视图,不会预加载全部视图
  • 可保留标题栏(Toolbar,titile view等)
  • 可设置重新加载点击事件(OnReloadListener)
  • 可自定义状态页(继承Callback类)
  • 可在子线程直接切换状态
  • 可设置初始状态页(常用进度页作为初始状态)
  • 不需要设置枚举或者常量状态值,直接用状态页类类型(xxx.class)作为状态码
  • 可扩展状态页面,在配置中添加自定义状态页
  • 可对单个状态页单独设置点击事件,根据返回boolean值覆盖或者结合OnReloadListener使用,如网络错误可跳转设置页
  • 可全局单例配置,也可以单独配置
  • 无预设页面,低耦合,开发者随心配置

开始使用LoadSir

LoadSir的使用只需要简单的三步,三步上篮的三步。

添加依赖
compile 'com.kingja.loadsir:loadsir:1.3.6'

第一步: 配置

全局配置方式

全局配置方式,使用的是单例模式,即获取的配置都是一样的。可在Application中配置,添加状态页,设置初始化状态页,建议使用这种配置方式。

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LoadSir.beginBuilder()
                .addCallback(new ErrorCallback())//'添加各种状态页
                .addCallback(new EmptyCallback())
                .addCallback(new LoadingCallback())
                .addCallback(new TimeoutCallback())
                .addCallback(new CustomCallback())
                .setDefaultCallback(LoadingCallback.class)//设置默认状态页
                .commit();
    }
}
单独配置方式

如果你即想保留全局配置,又想在某个特殊页面加点不同的配置,可采用该方式。

LoadSir loadSir = new LoadSir.Builder()
                .addCallback(new LoadingCallback())
                .addCallback(new EmptyCallback())
                .addCallback(new ErrorCallback())
                .build();
        loadService = loadSir.register(this, new Callback.OnReloadListener() {
            @Override
            public void onReload(View v) {
                // 重新加载逻辑
            }
        });

第二步: 注册

在Activity中使用
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_content);
    // Your can change the callback on sub thread directly.
    LoadService loadService = LoadSir.getDefault().register(this, new Callback.OnReloadListener() {
        @Override
        public void onReload(View v) {
            // 重新加载逻辑
        }
    });
}}
在View 中使用
ImageView imageView = (ImageView) findViewById(R.id.iv_img);
LoadSir loadSir = new LoadSir.Builder()
        .addCallback(new TimeoutCallback())
        .setDefaultCallback(LoadingCallback.class)
        .build();
loadService = loadSir.register(imageView, new Callback.OnReloadListener() {
    @Override
    public void onReload(View v) {
        loadService.showCallback(LoadingCallback.class);
        // 重新加载逻辑
    }
});
在Fragment 中使用

由于Fragment添加到Activitiy方式多样,比较特别,所以在Fragment中注册方式不同于上面两种,大家先看模板代码:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
        savedInstanceState) {
    //第一步:获取布局View
    rootView = View.inflate(getActivity(), R.layout.fragment_a_content, null);
    //第二步:注册布局View
    LoadService loadService = LoadSir.getDefault().register(rootView, new Callback.OnReloadListener() {
        @Override
        public void onReload(View v) {
            // 重新加载逻辑
        }
    });
    //第三步:返回LoadSir生成的LoadLayout
    return loadService.getLoadLayout();
}

第三步: 回调

直接回调
protected void loadNet() {
        // 进行网络访问...
        // 进行回调
        loadService.showSuccess();//成功回调
        loadService.showCallback(EmptyCallback.class);//其他回调
    }
转换器回调 (推荐使用)

如果你不想再每次回调都要手动进行的话,可以选择注册的时候加入转换器,可根据返回的数据,适配对应的回调。

LoadService loadService = LoadSir.getDefault().register(this, new Callback.OnReloadListener() {
    @Override
    public void onReload(View v) {
            // 重新加载逻辑
    }}, new Convertor<HttpResult>() {
    @Override
    public Class<? extends Callback> map(HttpResult httpResult) {
        Class<? extends Callback> resultCode = SuccessCallback.class;
        switch (httpResult.getResultCode()) {
            case SUCCESS_CODE://成功回调
                if (httpResult.getData().size() == 0) {
                    resultCode = EmptyCallback.class;
                }else{
                    resultCode = SuccessCallback.class;
                }
                break;
            case ERROR_CODE:
                resultCode = ErrorCallback.class;
                break;
        }
        return resultCode;
    }
});

回调的时候直接传入转换器指定的数据类型。

loadService.showWithConvertor(httpResult);

自定义回调页

LoadSir为了完全解耦,没有预设任何状态页,开发者根据需求自定义自己的回调页面,比如加载中,没数据,错误,超时等常用页面,
设置布局及自定义点击逻辑

public class CustomCallback extends Callback {
    @Override
    protected int onCreateView() {
        return R.layout.layout_custom;
    }

    @Override
    protected boolean onRetry(final Context context, View view) {
        //布局点击事件
        Toast.makeText(context.getApplicationContext(), "Hello mother fuck! :p", Toast.LENGTH_SHORT).show();
        //子控件事件
        (view.findViewById(R.id.iv_gift)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context.getApplicationContext(), "It's your gift! :p", Toast.LENGTH_SHORT).show();
            }
        });
        return true;//返回true则覆盖了register时传入的重试点击事件,返回false则两个都执行
    }

    //是否在显示Callback视图的时候显示原始图(SuccessView),返回true显示,false隐藏
    @Override
    public boolean getSuccessVisible() {
        return super.getSuccessVisible();
    }

    //将Callback添加到当前视图时的回调,View为当前Callback的布局View
    @Override
    public void onAttach(Context context, View view) {
        super.onAttach(context, view);
    }

    //将Callback从当前视图删除时的回调,View为当前Callback的布局View
    @Override
    public void onDetach() {
        super.onDetach(context, view);
    }
}

动态修改Callback

loadService = LoadSir.getDefault().register(...);
loadService.setCallBack(EmptyCallback.class, new Transport() {
   @Override
   public void order(Context context, View view) {
       TextView mTvEmpty = (TextView) view.findViewById(R.id.tv_empty);
       mTvEmpty.setText("fine, no data. You must fill it!");
   }
});

代码混淆

-dontwarn com.kingja.loadsir.**
-keep class com.kingja.loadsir.** {*;}

占位图布局效果

placeholder效果状态页类似ShimmerRecyclerView的效果. LoadSir只用了一个自定义状态页PlaceHolderCallback就完成类似的效果,是不是很棒 :p

看到这,想必各位使用LoadSir应该没问题了,如果还想再进一步了解它的内部结构,可以继续往下看。

原理解析

流程图

关键类

  • LoadSir:提供单例模式获取全局唯一实例,内部保存配置信息,根据配置创建LoadService。
  • LoadService:具体操作服务类,提供showSuccess,showCallback,showWithCoverator等方法来进行状态页回调。
  • LoadLayout:最终显示在用户面前的视图View,替换了原布局,是LoadService直接操作对象,要显示的状态页的视图会被添加到LoadLayout上。
  • Callback:状态页抽象类,抽象自定义布局和自定义点击事件两个方法留给子类实现。
  • Coverator:转换接口,可将网络返回实体转换成对应的状态页,达到自动适配状态页的目的。

我们直接观察在Activity中普通加载和使用LoadSir加载视图的区别

>>>没使用LoadSir
>>>使用LoadSir

大家可以看到,LoadSir用LoadLayout把原来的布局给替代掉了,原来的布局加在了LoadLayout上,其它自定义的状态页也同样会被加到这个LoadLayout上(显示的时候),而且LoadLayout的子View只有一个,就是当前要显示的状态页布局,并没有把当前不显示的比如加载中布局,错误布局,无数据布局加载进来,这也是LoadSir的优点之一,按需加载,并且只加载一个状态布局。

>>>替换逻辑
public static TargetContext getTargetContext(Object target) {
        ViewGroup contentParent;
        Context context;
        if (target instanceof Activity) {
            Activity activity = (Activity) target;
            context = activity;
            contentParent = (ViewGroup) activity.findViewById(android.R.id.content);
        } else if (target instanceof View) {
            View view = (View) target;
            contentParent = (ViewGroup) (view.getParent());
            context = view.getContext();
        } else {
            throw new IllegalArgumentException("The target must be within Activity, Fragment, View.");
        }
       ...
        if (contentParent != null) {
            contentParent.removeView(oldContent);
        }
        return new TargetContext(context, contentParent, oldContent, childIndex);
    }

大家可以看到,在Activity和View中的情况都比较简单,直接获取target的父控件,然后在父控件中替换掉该布局即可。在Fragment中,由于可能多个Fragment的布局View并存在一个父控件里,所以不能简单地使用父控件删除子View方式替换,也有可能父控件是ViewPager,不能通过addView()的方式添加LoadLayout。因此Fragment的注册方式是直接返回了LoadLayout到Activity上。这样也达到了一样的目的。

下面是ViewPager+Fragment场景中使用LoadSir的视图,两个Fragment用各自的LoadLayout进行视图分离,避免了状态页叠加或错位。



看到这的童鞋应该也大概知道LoadSir是怎么回事了,如果想明白LoadSir的代码实现,请继续往下看。

源码解析

我们按上面三步上篮的步骤来稍微分析下源码

>>>第一步:配置

单例模式获取LoadSir,在LoadSir构造的时候创建默认配置

public static LoadSir getDefault() {
        if (loadSir == null) {
            synchronized (LoadSir.class) {
                if (loadSir == null) {
                    loadSir = new LoadSir();
                }
            }
        }
        return loadSir;
    }

    private LoadSir() {
        this.builder = new Builder();
    }

Builder主要提供添加状态页,和设置默认状态页的方法

public static class Builder {
        private List<Callback> callbacks = new ArrayList<>();
        private Class<? extends Callback> defaultCallback;

        public Builder addCallback(Callback callback) {
            callbacks.add(callback);
            return this;
        }

        public Builder setDefaultCallback(Class<? extends Callback> defaultCallback) {
            this.defaultCallback = defaultCallback;
            return this;
        }
      ...
        public LoadSir build() {
            return new LoadSir(this);
        }

    }

LoadSir提供beginBuilder()...commit()来设置全局配置。

public class LoadSir  {
   ...
    public static Builder beginBuilder() {
        return new Builder();
    }

    public static class Builder {
      
        public void commit() {
            getDefault().setBuilder(this);
        }
      ...
    }
}

>>>第二步:注册

LoadSir注册后返回的是LoadService,一看名字大家就明白这是服务类,就是我们所说的Service层。

public LoadService register(Object target, Callback.OnReloadListener onReloadListener) {
        return register(target, onReloadListener, null);
    }

    public <T> LoadService register(Object target, Callback.OnReloadListener onReloadListener, Convertor<T>
            convertor) {
        TargetContext targetContext = LoadSirUtil.getTargetContext(target);
        return new LoadService<>(convertor, targetContext, onReloadListener, builder);
    }

在LoadService的构造方法中根据target等信息创建Success视图,并且生成LoadLayout,相当于LoadSir每次注册都会创建一个LoadLayout。

LoadService(Convertor<T> convertor, TargetContext targetContext, Callback
            .OnReloadListener onReloadListener, LoadSir.Builder builder) {
        this.convertor = convertor;
        Context context = targetContext.getContext();
        View oldContent = targetContext.getOldContent();
        loadLayout = new LoadLayout(context, onReloadListener);
        loadLayout.addCallback(new SuccessCallback(oldContent, context,
                onReloadListener));
        if (targetContext.getParentView() != null) {
            targetContext.getParentView().addView(loadLayout, targetContext.getChildIndex(), oldContent
                    .getLayoutParams());
        }
        initCallback(builder);
    }
>>>第三步:回调

LoadService的三个回调方法最终调用的都是loadLayout.showCallback(callback);

public void showSuccess() {
        loadLayout.showCallback(SuccessCallback.class);
    }

    public void showCallback(Class<? extends Callback> callback) {
        loadLayout.showCallback(callback);
    }

    public void showWithConvertor(T t) {
        if (convertor == null) {
            throw new IllegalArgumentException("You haven't set the Convertor.");
        }
        loadLayout.showCallback(convertor.map(t));
    }

我们直接看LoadLayout的showCallback方法,先做Callback是否配置判断,然后进行线程安全操作。重点还是showCallbackView(callback);

public void showCallback(final Class<? extends Callback> callback) {
        if (!callbacks.containsKey(callback)) {
            throw new IllegalArgumentException(String.format("The Callback (%s) is nonexistent.", callback
                    .getSimpleName()));
        }
        if (LoadSirUtil.isMainThread()) {
            showCallbackView(callback);
        } else {
            postToMainThread(callback);
        }
    }

这个方法可以说是最后的执行者,就做两件事,删除LoadLayout所有子View(重置),添加指定的布局页View(回调)。

private void showCallbackView(Class<? extends Callback> status) {
        if (getChildCount() > 0) {
            removeAllViews();
        }
        for (Class key : callbacks.keySet()) {
            if (key == status) {
                addView(callbacks.get(key).getRootView());
            }
        }
    }

自此,LoadSir一个完整的配置,注册,回调的过程完成了。不知道你们明白了没,反正我是有点口渴了。

总结

建议在Application中全局配置,在BaseActivity,BaseFragment或者MVP中封装使用,能极大的减少代码量,让你的代码更加优雅,生活更加愉快。时间和个人能力有限,如果大家发现需要改进的地方,欢迎提交issue。
如果这个库对你有用的话,也请点个star:p Github传送门

相关文章

网友评论

  • MK_c4a8:你好,使用过程中我遇到一个问题,我是在baseActivity中register的,在国产手机上都没问题,在三星手机上会java.lang.OutOfMemoryError,内存溢出了,报错行数就是baseActivity中register所在行。请问是什么原因呢
  • 27902aa78e16:你好,我想在网络不好的时候点击返回键的时候取消加载loading该怎么做
    27902aa78e16:@KingJA 好的 谢谢
    KingJA:@爵士海盗 你好,我觉得大致分三步:
    1.取消请求(具体方法根据请求框架不同而不同)。
    2.显示原布局(成功布局),成功布局设为可见。
    3.原布局有触发重新加载的动作,以便网络好的时候再次手动加载。
  • 如梦时光:这个项目帮了大忙了,已打赏
    KingJA:@如梦时光 感谢你的使用,很高兴能帮助到你.
  • 7b75ad75281b:有个问题想问下作者,但是不知道怎么联系你呢
    KingJA:@Olmecm 评论,私信,github都可以
  • onion90:你好,请问ErrorCallback那些页面是要自己写的吗?
    KingJA:@onion90 是的,这样你就可以完全自定义你的状态页,而且没有耦合。
  • gaolhjy:你好,看了你demo中代码,对于大多数情况可以满足我需求.但我在进行含有recyclerView的时候,如果没有数据.空布局会把整个界面覆盖.但其实我recyclerView不仅有头部,还是标题.这种情况如何解决呢?
    如果方便的话,麻烦加一下我的QQ:984992087
  • 40baeb639f3e:依赖会报错
    Error:Execution failed for task ':ladingviewdemo:processDebugManifest'.
    > Manifest merger failed : Attribute application@label value=(@string/appName) from AndroidManifest.xml:11:9-40
    is also present at [com.kingja.loadsir:loadsir:1.2.2] AndroidManifest.xml:13:9-32 value=(LoadSir).
    Suggestion: add 'tools:replace="android:label"' to <application> element at AndroidManifest.xml:7:5-22:19 to override.

    和AndroidManifest的 android:label="@string/appName"冲突
    KingJA:@40baeb639f3e 请下载最新版本com.kingja.loadsir:loadsir:1.3.2
  • 15d7c82e493f:为什么连续的点击Loading页面 progressBar会卡主不动了?
    KingJA:@15d7c82e493f 复写Callback onReloadEvent(Context context, View view)方法,返回true,空实现来规避进度页面的点击事件,一般进度页面不需要点击事情。
  • MrYag:你好,我是萌新一个,看了公众号过来,有些问题想当面请教问您一下。这是我的QQ,920054486,
    MrYag:@KingJA 谢谢,麻烦你啦
    KingJA: @MrYag 好的,晚点我会加你讨论。
    MrYag:有些东西看不大懂,还望指教
  • 中午晚上吃通心粉:你好,思路很棒,建议在文档着重提示这句代码,loadService.showSuccess();//成功回调,我导入项目发现这句话不写自己的布局是不会出来的:disappointed_relieved:
    KingJA: @中午晚上吃通心粉 呵呵,解决了就好。
    中午晚上吃通心粉: @中午晚上吃通心粉 不好意思 是我开始没理解你文档的意思 你是拿类当状态值
  • 汪珺:结合项目使用的时候出现一个问题,会使RefreshLayout刷新效果无效,没找到原因
    KingJA:你好,最好提供完整的代码或使用场景。目前最新的是branch v1.3.0测试版:https://github.com/KingJA/LoadSir/tree/v1.3.0
  • a3f4fdbfbb5e:有木有封装好的demo 可以直接在项目里用的,不封装的话写起来好麻烦感觉
    a3f4fdbfbb5e:@KingJA 嗯我看看去 还有个问题我在activity或fragment里一调用LoadSir.getDefault().register运行起来就崩溃 提示我fragment里找不到某个控件 去掉就好了 很奇怪这是为什么
    KingJA:branch v1.3.0的Demo中有简单的封装,也可以参考https://github.com/KingJA/LoadSirBestPractice,这Demo中也可以将LoadSir封装在BaseTitleActivity中,具体看使用场景。
  • b593db4a14e9:刚才踩了一个坑,项目中的fragment onActivityCreated 中 初始化视图会报空
    如果使用BaseFragment 来封装LoadSir 的话,
    在onCreateView中的获取布局view时
    rootView = View.inflate(getActivity(), R.layout.fragment_a_content, null);
    把其变为全局变量,然后在子Fragment中通过base层中的rootView 来初始化视图
    ,刚使用该控件的时在fragment中以loadService.getLoadLayout()来findViewById()
    铁定报空指针
  • b593db4a14e9:在您的github 项目中的fragment onActivityCreated 中 初始化视图会报空
    KingJA: @吴浩武汉掌游科技有限公 要先显示SuccessCallback,这样布局才会被加载
    b593db4a14e9:@KingJA /**
    * Description:TODO
    * Create Time:2017/9/5 13:28
    * Author:KingJA
    * Email:kingjavip@gmail.com
    */
    public class FragmentA extends BaseFragment {

    @Override
    protected int onCreateFragmentView() {
    return R.layout.fragment_a_content;
    }

    @Override
    protected void loadNet() {
    // do net here...
    // call back
    PostUtil.postCallbackDelayed(mBaseLoadService, ErrorCallback.class);
    }


    @Override
    protected void onNetReload(View v) {
    Toast.makeText(getContext(),"reload in Fragment A",Toast.LENGTH_SHORT).show();
    mBaseLoadService.showCallback(LoadingCallback.class);
    //do retry logic...

    //callback
    PostUtil.postSuccessDelayed(mBaseLoadService);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // 该处就会报空指针 textView =null
    TextView textView= (TextView) getActivity().findViewById(R.id.tv_success);
    }
    }
    KingJA: @吴浩武汉掌游科技有限公 你好,能否描述更详细些,或gmail我提供详细代码?
  • Lrxc:1. 如何去掉(可设置初始状态页(常用进度页作为初始状态)) 初始化状态页呢?
    2. 最好去掉res下面values文件,否则会强制要求supportVersion 25.3.1及以上的,一些老项目不能使用。。
    KingJA: @萧萧冷者 初始页如果你不设置是不会加载的,我想你说的可能是LoadLayout,它是状态页的父控件,这样解释后你能再描述下你的需求吗。
    Lrxc:@KingJA setDefaultCallback(Custom.class)//设置默认状态页
    这个页面会强制使用,设置布局背景透明也不行。。。WebView中使用的,希望可以不强制显示 谢谢
    KingJA:1.你好,你指的去掉是什么意思?
    2.谢谢提醒。
  • 根号仨:有个很大的问题,这种方式会把toolbar挡住,体验非常不好
    根号仨:Fragment中mBaseLoadService.getLoadLayout(),肯定会把所有的组件都覆盖?toolbar也是,有没有办法解决
    根号仨: @KingJA fragment主要是
    KingJA:@根号仨 用注册View的方式把Toolbar以下的视图view传进去,这样就保留了Toolbar。因为Toolbar和TitleView的样式多种多样,加入自定义会增加耦合。我一般在BaseTitleActivity封装标题栏逻辑,然后具体设置让子类实现。
  • AwaitZhang:问一下,就是如果我placeholder页面想用作者这种的,其他用我自己的,我是该怎么写?写一个Callback继承你的Callback?不写这个的话,就直接默认用作者你那种的吗?
    AwaitZhang:@KingJA 哦哦,谢谢你
    KingJA:不是哦,LoadSir库为了解耦,没有提供任何预设的状态页的,全部都是开发者自己设置想要的布局。placeholder页面你可以复制下原布局,然后控件背景填充灰色,最好还是和原布局看上去类似,提高用户体验。具体操作就是自定义一个PlaceHolder状态页。:stuck_out_tongue_winking_eye:
  • f41b2a53e251:感觉把问题搞复杂了,而且并不轻
    KingJA:@泡网编辑 感谢你的意见,能否说说具体的理由:ghost:
  • 杰森斯坦晟:你好,能不能丢出来个封装了基本用法的demo
    KingJA:@android_大晟 你好,久等了,branch v1.3.0的Demo中有简单的封装:https://github.com/KingJA/LoadSir/tree/v1.3.0,也可以参考https://github.com/KingJA/LoadSirBestPractice,这Demo中也可以将LoadSir封装在BaseTitleActivity中,具体看使用场景。
    杰森斯坦晟:@KingJA 谢谢了,已经接入LoadSir,静待二次封装的demo
    KingJA:@android_大晟 这个可以有,请关注github,完成我会更新到上面。
  • JackMe:ButterKnife.bind(this,mBaseLoadService.getLoadLayout())
    Nightsong:@KingJA MVPArm中的fragment怎么改呢?
    b593db4a14e9:@KingJA 原来也有人和我遇到了一样的问题
    KingJA:View rootView = View.inflate(getActivity(), R.layout.fragment, null);
    ButterKnife.bind(this, rootView);
    不需要传LoadLayout
  • JackMe:fragment配合ButterKnife,View.inflate的View里面的控件的id找不到呢
  • JackMe:你好 fragment配合ButterKnife 使用 貌似会出现问题
  • 傲雪秋思:不错,但是自定义布局的点击事件为什么时灵?时不灵?
    KingJA:onRetry(final Context context, View view)里的View就是布局View,你可以在这里findViewById获取各个子控件,然后do anything.5304467(hex)
    傲雪秋思:@KingJA 解决了!对了 大神,那个我自定义的布局内部有不同的按钮,需要执行不同的点击事件,怎么区分!!!方便加下QQ吗?
    KingJA: @傲雪秋思 能描述下场景吗
  • 2b71a369532c:不错,使用方便。
    KingJA: @2b71a369532c 谢谢哦
  • adonis_lsh:认认真真的看完了,这种和日常的做法流程上基本上是一样的,很不错,但是,貌似不能刷新某一个子view.
    KingJA: @adonis_lsh 刷新子view和activity方法一样的,注册时候传子view进去。 DEMO中有。
    KingJA: @adonis_lshOnReloadListener接口回调方法里的view就是布局view,你可以在这里取

本文标题:优雅地处理加载中(loading),重试(retry)和无数据(

本文链接:https://www.haomeiwen.com/subject/gyobjxtx.html