FinClip为企业提供小程序生态圈技术产品,开发者可在FinClip小程序开发帮助中心找到相关FinClip小程序指引

# 设置加载页 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)
    }
  }
}

© FinClip with ❤ , Since 2017