这是一个支持多种状态的布局库,包括空布局,错误布局和加载布局。此外,该库还提供了自定义状态的选项。
implementation 'cz.kinst.jakub:android-stateful-layout-base:2.0.7'
在xml中使用
在onCreate中设置对应的状态对应的布局
// in onCreate() statefulLayout.setStateView(STATE_NO_PERSMISSION, LayoutInflater.from(this).inflate(R.layout.state_no_permission, null)); statefulLayout.setStateView(STATE_PROGRESS, LayoutInflater.from(this).inflate(R.layout.state_progress, null));
setStateView(String state, View view)
添加新状态和相应的视图
setState(String state)
更改当前状态
getState()
返回当前视图状态(字符串ID)
setOnStateChangeListener(OnStateChangeListener listener)
设置状态更改事件的侦听器
setStateController(StateController stateController)
设置状态控制器对象。请参见下文。
如果您不想直接使用视图进行操作(MVVM/MPV场景),您可以创建StateController的实例并将其绑定到StatefulLayout(例如使用数据绑定)。StateController允许您设置不同的状态以及控制当前状态本身。请参阅下面的示例或示例项目中的DataBindingControllerActivity。
stateController = StatefulLayout.StateController.create() .withState(STATE_NO_PERSMISSION, LayoutInflater.from(this).inflate(R.layout.state_no_permission, null)) .withState(STATE_PROGRESS, LayoutInflater.from(this).inflate(R.layout.state_progress, null)) .build(); //... stateController.setState(STATE_PROGRESS);
如果您想拥有一组完全自定义的状态/视图,则应该使用这个基类。您可以在构造函数中继承和添加自定义状态(例如,请参阅SimpleStatefulLayout),也可以直接使用Stateful布局并通过setStateView()在代码中动态添加状态。原始StatefulLayout只包含一个状态-StatefulLayout.state.CONTENT,无论标记的XML内容中有什么子级。
implementation 'cz.kinst.jakub:android-stateful-layout-simple:2.0.7'
SimpleStatefulLayout扩展了Stateful布局,并为大多数应用程序添加了几个有用的状态-State.OFFLINE、State.PROGRESS、State.EMPTY。它为这些状态提供了可自定义的布局占位符,并提供了一种为状态提供完全自定义布局的方式。
您也可以通过扩展类或使用setStateView()添加状态来自定义它。
// in onCreate() statefulLayout.showProgress(); // load data statefulLayout.showContent();
app:offlineText
处于离线状态时显示的自定义文本
app:offlineRetryText
离线状态下重试按钮的文本
app:emptyText
处于离线状态时显示的自定义文本
app:offlineImageDrawable
显示在离线状态文本上方的自定义图像(如果不使用自定义布局)
app:emptyImageDrawable
显示在空状态文本上方的自定义图像(如果不使用自定义布局)
app:offlineLayout
处于离线状态时显示的自定义布局
app:emptyLayout
处于 EMPTY 状态时显示的自定义布局
app:progressLayout
处于 PROGRESS 状态时显示的自定义布局
app:state
视图的初始状态(内容
、进度
、离线
、空布局
)
app:stateTextAppearance
当不使用自定义布局时,这是在 离线和 空布局状态下设置 TextView 样式的方法。
showContent()
showProgress()
showEmpty()
showOffline()
setEmptyText(String text)
如果使用默认布局,这将设置在 EMPTY 状态下显示的文本
setOfflineText(String text)
如果使用默认布局,这将设置在离线状态下显示的文本
setOfflineRetryText(String text)
如果使用默认布局,这将设置在离线状态下显示的重试按钮的文本
setOfflineRetryOnClickListener(OnClickListener listener)
如果使用默认布局,这会将单击侦听器设置为在离线状态下显示的重试按钮
setEmptyImageDrawable(Drawable drawable)
设置不使用自定义布局时显示在空文本上方的自定义图像
setEmptyImageResource(int resourceId)
设置不使用自定义布局时显示在空文本上方的自定义图像
setOfflineImageDrawable(Drawable drawable)
设置不使用自定义布局时在离线文本上方显示的自定义图像
setOfflineImageResource(int resourceId)
设置不使用自定义布局时在离线文本上方显示的自定义图像
setTransitionsEnabled(boolean enabled)
启用/禁用状态之间的转换
Android动画之Transition和TransitionManager使用
StateView 一个轻量级的控件, 继承自 View
, 吸收了 ViewStub
的一些特性, 初始状态下是不可见的, 不占布局位置, 占用内存少。 当进行操作显示空/重试/加载视图后, 该视图才会被添加到布局中。
githup
引入
allprojects { repositories { ... maven { url 'https://jitpack.io' } } }
dependencies { implementation 'com.github.nukc:StateView:v3.0.2' }
直接在代码中使用:
注入到 Activity
mStateView = StateView.inject(Activity activity);
注入到 View
mStateView = StateView.inject(View view);
注入到 ViewGroup
mStateView = StateView.inject(ViewGroup parent);
或添加到布局(这种方式可以更灵活):
显示空视图: mStateView.showEmpty();
显示加载视图: mStateView.showLoading();
显示重试视图: mStateView.showRetry();
显示内容: mStateView.showContent();
设置重试点击事件:
mStateView.setOnRetryClickListener(new StateView.OnRetryClickListener() { @Override public void onRetryClick() { //do something, no need to call showLoading() //不需要调用showLoading()方法, StateView自会调用 } });
设置自定义视图:
全局设置办法:在自己项目的layout下新建, 名字跟StateView默认layout一样即可(也不用代码设置). 默认layout的名字:base_empty
/base_retry
/base_loading
.
单页面设置:layout名字不一样, 然后再代码设置.
setEmptyResource(@LayoutRes int emptyResource) setRetryResource(@LayoutRes int retryResource) setLoadingResource(@LayoutRes int loadingResource) // v2.1 setEmptyView(View view) setRetryView(View view) setLoadingView(View view) // v3.0.0 setView(viewType: Int, view: View) // eg: set empty view setView(mStateView.getEmptyResource(), emptyView) // set any view setView(1, view) // show view show(viewType: Int)
利用 OnInflateListener
设置文本图像或者其它操作: 在 view 成功添加到 parent 的时候回调(每个 viewType 只回调一次)
mStateView.setOnInflateListener(new StateView.OnInflateListener() { @Override public void onInflate(@StateView.ViewType int viewType, View view) { if (viewType == StateView.EMPTY) { // set text or other ViewGroup emptyView = (ViewGroup) view; TextView tvMessage = (TextView) emptyView.findViewById(R.id.tv_message); ImageView ivState = (ImageView) emptyView.findViewById(R.id.iv_state); tvMessage.setText("custom message"); ivState.setImageResource(R.drawable.retry); } else if (viewType == StateView.RETRY) { // ... } } });
设置视图切换动画:
// 默认 provider 是 null,即默认不提供动画切换 // 如果需要,设置一个就可以了 setAnimatorProvider(AnimatorProvider provider)
动画效果可以自定义,也可以直接使用 animations 这个库,与主库分离,这样不需要的就可以只依赖 library。
compile 'com.github.nukc.stateview:animations:1.0.1'
目前提供了如下几个动画效果:
渐变缩放: FadeScaleAnimatorProvider
卡片翻转: FlipAnimatorProvider
左右滑动: SlideAnimatorProvider
自定义的话,直接实现 AnimatorProvider
接口并提供 Animator
就可以了
public class FadeScaleAnimatorProvider implements AnimatorProvider { @Override public Animator showAnimation(View view) { AnimatorSet set = new AnimatorSet(); set.playTogether( ObjectAnimator.ofFloat(view, "alpha", 0f, 1f), ObjectAnimator.ofFloat(view, "scaleX", 0.1f, 1f), ObjectAnimator.ofFloat(view, "scaleY", 0.1f, 1f) ); return set; } @Override public Animator hideAnimation(View view) { AnimatorSet set = new AnimatorSet(); set.playTogether( ObjectAnimator.ofFloat(view, "alpha", 1f, 0f), ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.1f), ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.1f) ); return set; } }
对于是沉浸式全屏模式下的,可以使用此方法补上 statusBar 的 height,从而不覆盖 toolbar
/** * @return statusBarHeight */ private int getStatusBarHeight() { int height = 0; int resId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resId > 0) { height = getResources().getDimensionPixelSize(resId); } return height; } ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) mStateView.getLayoutParams(); layoutParams.topMargin += getStatusBarHeight()
githup
LoadSir
是一个高效易用,低碳环保,扩展性良好的加载反馈页管理框架,在加载网络或其他数据时候,根据需求切换状态页面, 可添加自定义状态页面,如加载中,加载失败,无数据,网络超时,如占位图,登录失效等常用页面。可配合网络加载框架,结合返回 状态码,错误码,数据进行状态页自动切换,封装使用效果更佳。
⭐支持Activity,Fragment,Fragment(v4),View状态回调
⭐适配多个Fragment切换,及Fragment+ViewPager切换,不会布局叠加或者布局错乱
⭐利用泛型转换输入信号和输出状态,可根据网络返回体的状态码或者数据返回自动适配状态页,实现全局自动状态切换
⭐无需修改布局文件
⭐只加载唯一一个状态视图,不会预加载全部视图
⭐不需要设置枚举或者常量状态值,直接用状态页类类型(xxx.class)作为状态码
⭐可对单个状态页单独设置点击事件,根据返回boolean值覆盖或者结合OnReloadListener使用,如网络错误可跳转设置页
⭐无预设页面,低耦合,开发者随心配置
⭐可保留标题栏(Toolbar,titile view等)
可设置重新加载点击事件(OnReloadListener)
可自定义状态页(继承Callback类)
可在子线程直接切换状态
可设置初始状态页(常用进度页作为初始状态)
可扩展状态页面,在配置中添加自定义状态页
可全局单例配置,也可以单独配置
LoadSir的使用,只需要简单的三步
compile 'com.kingja.loadsir:loadsir:1.3.8'
全局配置方式,使用的是单例模式,即获取的配置都是一样的。可在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) { // 重新加载逻辑 } });
@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) { // 重新加载逻辑 } }); }}
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); // 重新加载逻辑 } }); Ps: [1]要注册RelativeLayout或ConstraintLayout的子View,如果该子View被其它子View约束,建议在子View外层再包一层布局,参考 acitivy_view.xm和activity_constraintlayout.xml
由于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() { @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; } //当前Callback的点击事件,如果返回true则覆盖注册时的onReloa(),如果返回false则两者都执行,先执行onReloadEvent()。 @Override protected boolean onReloadEvent(final Context context, View view) { Toast.makeText(context.getApplicationContext(), "Hello buddy! :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; } //是否在显示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); } }
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!"); } });
ProgressCallback loadingCallback = new ProgressCallback.Builder() .setTitle("Loading", R.style.Hint_Title) .build(); HintCallback hintCallback = new HintCallback.Builder() .setTitle("Error", R.style.Hint_Title) .setSubTitle("Sorry, buddy, I will try it again.") .setHintImg(R.drawable.error) .build(); LoadSir loadSir = new LoadSir.Builder() .addCallback(loadingCallback) .addCallback(hintCallback) .setDefaultCallback(ProgressCallback.class) .build();
githup
推荐使用这个