# 设置加载页 UI
# 1. 效果展示
FinClip 小程序 SDK支持对加载页、错误页进行个性化配置。其中:
- 加载页是指:当启动小程序时,如果小程序未下载到设备,小程序容器会启动加载页提示用户等待,待小程序安装到设备上,加载页关闭并跳转至小程序。用户首次打开小程序、或小程序版本更新后,加载页停留时间会相对较长
- 错误页是指:小程序加载失败、或产生其他未知错误时,展示给用户的页面
具体UI效果可参考下图:
# 2. 覆盖范围
该设置由App实现,可配置加载页、错误页的视图,一经设置,App内的全部小程序、小游戏、H5应用均将按照本效果实现。
# 3. iOS 设置方法
# 3.1 实现自定义加载页
对于 iOS 小程序,SDK 支持开发者自定义加载页内容,您可按照以下步骤进行配置:
1.在继承自FATBaseLoadingView
的子类中修改加载页的样式。
代码示例如下:
@interface LoadingView : FATBaseLoadingView
@end
@implementation LoadingView
- (instancetype)initWithFrame:(CGRect)frame {
if ([super initWithFrame:frame]) {
self.loadingView.padding = 20;
self.loadingView.dotView.backgroundColor = [UIColor blackColor];
self.loadingView.animation.duration = 5;
self.titleLabel.textColor = [UIColor redColor];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
// 如果要改logo,必须在这里修改。
// 修改小程序logo应该是从管理后台上传新的小程序图标,因为更多面板、关于页面也会展示小程序logo,只改这里只是loding页面生效
self.loadingView.iconImageView.image = [UIImage imageNamed:@"mini_logo"];
}
@end
注意
因为小程序在加载过程中可能会更新加载页的背景色、小程序标题、小程序图标和各控件的frame,所以对这些属性的修改需要放在layoutSubviews内。 小程序的标题和图标建议通过开放平台修改。
2.将FATConfig
对象的baseLoadingViewClass
属性设置自定义加载页的类名。
FATConfig *config = [FATConfig configWithStoreConfigs:storeConfigs];
config.baseLoadingViewClass = @"LoadingView";
[[FATClient sharedClient] initWithConfig:config error:nil];
# 3.2 实现自定义加载失败页
实现方式与自定义加载页类似,具体步骤如下:
1.在继承自FATBaseLoadFailedView
的子类中修改加载失败页的样式。
@interface LoadFailedView : FATBaseLoadFailedView
@end
@implementation LoadFailedView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.errorLabel.textColor = [UIColor redColor];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat bottom = self.fat_height;
self.errorImageView.fat_bottom = bottom - 110;
self.errorLabel.fat_top = self.errorImageView.fat_bottom + 30;
self.detailLabel.fat_top = self.errorLabel.fat_bottom + 15;
}
@end
2.将FATConfig
对象的baseLoadingViewClass
属性设置自定义加载失败页的类名。
FATConfig *config = [FATConfig configWithStoreConfigs:storeConfigs];
config.baseLoadFailedViewClass = @"LoadFailedView";
[[FATClient sharedClient] initWithConfig:config error:nil];
# 4. Android 设置方法
SDK提供了抽象类IFinAppletLoadingPage
,开发者可继承该类,实现自己的加载页视图和失败页视图,并在初始化SDK时配置实现类。
# 4.1 IFinAppletLoadingPage 抽象方法说明
/**
* 小程序加载中的布局ID
*/
abstract fun getLoadingLayoutRes(): Int
/**
* 小程序加载失败的布局ID
*/
abstract fun getFailureLayoutRes(): Int
/**
* 小程序加载失败回调
*
* @param msg 错误信息
*/
@Deprecated("自2.36.3版本之后不再回调,请使用 onLoadingFailure(title: String, msg: String)")
abstract fun onLoadingFailure(msg: String)
/**
* 小程序加载失败回调
*
* @param title 错误标题
* @param msg 错误信息
*/
abstract fun onLoadingFailure(title: String, msg: String)
/**
* 更新小程序相关信息,参数为小程序基本信息,会多次回调,
* 初次回调时可能为空(如小程序初次启动,基本信息还未下载),
* 需开发者自行进行判空。
*
* @param appTitle app名字
* @param appAvatarUrl app图标链接
*/
abstract fun onUpdate(appTitle: String, appAvatarUrl: String)
该类提供两个布局实例:
failureLayout
对应 getFailureLayoutRes 方法返回的错误页布局文件layout
loadingLayout
对应 getLoadingLayoutRes 方法返回的加载页布局文件layout
# 4.2 自定义加载页完整实现示例
注意
由于SDK内部使用了反射技术将实现类进行实例化,因此加载页实现类必须包含仅有Context
作为参数的构造函数。
示例如下
CustomLoadingPage:
class CustomLoadingPage constructor(context: Context) : IFinAppletLoadingPage(context) {
override fun getFailureLayoutRes(): Int {
// 错误页布局资源文件
return R.layout.loading_failure_layout
}
override fun getLoadingLayoutRes(): Int {
// 加载页布局资源文件
return R.layout.loading_layout
}
override fun onLoadingFailure(msg: String) {
// 该方法已废弃,留空即可,请使用下面的 onLoadingFailure(title: String, msg: String)
}
override fun onLoadingFailure(title: String, msg: String) {
// 错误页错误消息正文
failureLayout.findViewById<TextView>(R.id.tvLoadingFailedTitle).text = title
failureLayout.findViewById<TextView>(R.id.tvLoadingFailedMsg).text = msg
}
override fun onUpdate(appTitle: String, appAvatarUrl: String) {
// 加载页小程序名字
loadingLayout.findViewById<TextView>(R.id.tvTitle).text = appTitle
// 加载页小程序图标
ImageLoader.get(context).load(appAvatarUrl, object : DrawableCallback {
override fun onLoadFailure() {
}
override fun onLoadSuccess(r: Drawable) {
val ivAvatar = loadingLayout.findViewById<RoundedImageView>(R.id.ivAvatar)
// 不要直接使用Handler post设置图标,应该先判断是否主线程,如果是主线程则直接设置图标,
// 避免主线程更新时因Handler post导致图标显示时机变慢。
if (Looper.myLooper() == Looper.getMainLooper()) {
ivAvatar.setImageDrawable(r)
} else {
handler.post {
ivAvatar.setImageDrawable(r)
}
}
}
})
}
}
loading_failure_layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/fin_color_bg_pure_auto"
android:orientation="vertical">
<TextView
android:id="@+id/tvAppName"
android:layout_width="wrap_content"
android:layout_height="@dimen/fin_applet_navbar_height"
android:layout_alignParentTop="@+id/rlLoadingFailed"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:gravity="center"
android:textColor="@color/fin_color_text_auto"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
tools:text="小程序名称"
tools:visibility="visible" />
<RelativeLayout
android:id="@+id/rlLoadingFailed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="125dp">
<ImageView
android:id="@+id/ivLoadingFailed"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_centerHorizontal="true"
android:src="@drawable/fin_applet_loading_error"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/tvLoadingFailedTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/ivLoadingFailed"
android:layout_centerHorizontal="true"
android:layout_marginTop="25dp"
android:gravity="center"
android:text="@string/fin_applet_loading_failed_tip_without_message"
android:textColor="@color/fin_color_text_auto"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvLoadingFailed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tvLoadingFailedTitle"
android:layout_centerHorizontal="true"
android:layout_marginTop="15dp"
android:gravity="center"
android:textColor="@color/fin_color_secondary_text_auto"
android:textSize="15sp"
tools:text="错误提示" />
</RelativeLayout>
<!-- 注意 fin_btn_fail_reload_margin_bottom 横竖屏的时候距离会不一样。 -->
<TextView
android:id="@+id/btnFailReload"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/fin_btn_fail_reload_margin_bottom"
android:background="@drawable/fin_applet_btn_reload_blue"
android:gravity="center"
android:minWidth="200dp"
android:text="@string/fin_applet_load_again"
android:textColor="@android:color/white"
android:textSize="15sp" />
</RelativeLayout>
loading_layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/fin_color_bg_pure_auto"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/rlLoading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<FrameLayout
android:id="@+id/flAvatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true">
<com.finogeeks.lib.applet.modules.appletloadinglayout.FinAppletLoadingSurfaceView
android:id="@+id/finAppletLoadingView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.finogeeks.lib.applet.externallib.makeramen.roundedimageview.RoundedImageView
android:id="@+id/ivAvatar"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center"
android:src="@color/color_ebeced"
app:fin_applet_riv_corner_radius="6dp" />
</FrameLayout>
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/flAvatar"
android:layout_centerHorizontal="true"
android:ellipsize="end"
android:gravity="center"
android:maxLines="2"
android:textAlignment="center"
android:textColor="@color/fin_color_text_auto"
android:textSize="18sp"
android:textStyle="bold" />
</RelativeLayout>
<ImageView
android:id="@+id/ivTechSupport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="45dp"
android:src="@drawable/fin_applet_tech_support"
android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout>
# 4.3 在SDK初始化时配置加载页
val uiConfig = FinAppConfig.UIConfig()
// 配置自定义加载页
uiConfig.setLoadingLayoutCls(CustomLoadingPage::class.java)
val config = FinAppConfig.Builder()
// 其它配置项省略
.setUiConfig(uiConfig)
.build()
FinAppClient.init(application, config, object : FinCallback<Any?> {
override fun onSuccess(result: Any?) {
}
override fun onError(code: Int, error: String) {
}
override fun onProgress(status: Int, error: String) {
}
})
# 5. Harmony 设置方法
# 5.1 实现自定义加载页
在 SDK 初始化前添加代理方法,具体参数可以参考CustomLayoutHandler
示例代码:
client.proxyHandlerManager.customLayoutHandler.getCustomLoadingLayout = () => customLoading
// 如果小程序启动依赖某些异步数据才渲染,比如请求等,这时候小程序可能还是处于无数据的状态,可以通过延迟调用 resolve 的方式来延缓 loading 页销毁时机
client.proxyHandlerManager.customLayoutHandler.onLoadingLayoutReady = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 200)
})
}
@Builder
function customLoading() {
CustomViewComponent()
}
// 该组件会渲染在整个 loading 页面
@Component
struct CustomViewComponent {
@Consume layoutParams: IFinAppProxy.ICustomLoadingLayoutParams
build() {
Column() {
Image(this.layoutParams.logo)
Text(this.layoutParams.name)
}
}
}