前言
在应用程序开发过程中,Native+H5混合开发一直是主流形式,作为内容信息的展示载体,H5无疑是更好的选择。一个标准样式的H5页面,包含头部标题栏和WebView内容区域,在贝壳体系下的App基本遵循这类型风格,如图所示:
在被窝家装App初期,我们的H5页面也是遵循这种标准样式,在后面的迭代中,为了更好的用户视觉体验效果,我们对现有的H5页面进行梳理,总结出以下几类使用场景:
#
标准页面
样式:默认白色标题栏+WebView组合布局形式;(图一)
使用场景:默认的H5页面展示效果,如果没有其它的参数开关,跳转后样式风格固定如图所示;
#
有颜色的TitleBar
样式:标题栏+状态栏颜色统一,达到视觉连贯效果,WebView上滑过程中,TitleBar不会变化;(如图二)
使用场景:适用于一些活动页、广告页等H5页面,头部TitleBar+状态栏连贯,视觉体验效果更优;
#
沉浸式TitleBar
样式:和原生的沉浸式页面效果一样,展示沉浸式状态栏效果,WebView上滑过程中,TitleBar会有透明度变化,例如设计师详情页(H5)、套餐详情页(H5)(如图三、图四)
使用场景:适用于需要展示沉浸式样式的H5页面,通过参数简单配置即可;
#
沉浸式TitleBar + IconTextBtn按钮
样式:在沉浸式TitleBar基础上,支持IconTextBtn组合按钮展示,例如点赞、收藏等交互操作;(如图五)
使用场景:以前的TitleBar右上角容器只支持创建单一的ImageView、TextView、Drawable、自定义View,在家装实际使用过程中需要增加IconTextBtn这种组合交互按钮,点击后image、text会跟随变化,我们对它进行了扩展;
针对以上不同的场景,我们开始了改造之路~
1 有颜色的TitleBar
以设计半包页为例,当前加载的url是:
https://nowm.home.ke.com/jinggong/bj/designer_channel?bgColor=C09978
需要实现TitleBar有颜色的页面,我们和H5约定在访问链接中携带一个参数为bgColor,当bgColor有值时,客户端进行特殊处理,步骤如下:
Step1: 将页面设置沉浸式样式,透明状态栏+白色文字;
Step2: 标题栏布局样式是android:gravity="bottom"居底对齐,将mTopView和mTitleBar的高度设置成状态栏高度+标题栏高度;
Step3: 给标题栏mTitleBar设置bgColor,设置标题文字和图标显示为白色;
变量解释:
mTopView是一个占位View,主要作用是将WebView的头部起点位置顶到TitleBar下方;
示例代码如下:
<!--在非沉浸式状态时,用于撑起标题栏高度顶出下方webview-->
<View
android:id="@+id/top_view"
android:layout_width="match_parent"
android:layout_height="49dp"
android:layout_below="@id/holderview" />
<com.ke.libcore.support.browser.ScrollWebView
android:id="@id/lib_cwb_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/top_view" />
if (!TextUtils.isEmpty(bgColor)) {
initStatusBar();
try {
if (!bgColor.startsWith("#")) {
bgColor = "#" + bgColor;
}
if (mTopView != null) {
RelativeLayout.LayoutParams topViewParams =
(RelativeLayout.LayoutParams) mTopView.getLayoutParams();
topViewParams.height = topHeight;
mTopView.setLayoutParams(topViewParams);
mTitleBar.setLayoutParams(topViewParams);
}
//不建议直接修改top_view的背景色,虽然可行省事,但是灵活性变差了。
mTitleBar.setBackgroundColor(Color.parseColor(bgColor));
mBtnBack.setImageResource(R.drawable.lib_webview_back_white);
mTvTitle.setTextColor(Color.WHITE);
} catch (Exception e) {
e.printStackTrace();
}
}
实际效果如图所示:
2 沉浸式TitleBar
以套餐详情页为例,当前加载的url是:
https://nowm.home.ke.com/jinggong/bj/product/brand/package_overall?immersive=true&packageId=101584454034
需要实现沉浸式TitleBar样式的页面,我们和H5约定在访问链接中携带一个参数为immersive,标准页面默认不传,当immersive有值且为true时,进行特殊处理,步骤如下:
Step1: 将页面设置沉浸式样式,透明状态栏+白色文字;
Step2: 标题栏布局样式是android:gravity="bottom"居底对齐,将titleBg、titleCover、mTitleBar的高度设置成状态栏高度+标题栏高度,titleBg、mTvTitle透明度设置0;
变量解释:
titleBg是一个View,用于垫在mTitleBar下方做滑动头部渐变效果;
titleCover是一个View,作用单一,在沉浸式状态下显示,显示顶部阴影效果,其他状态隐藏;
mTitleBar表示TitleBar标题栏;
mTvTitle是TitleBar中的标题TextView;
Step3: H5会监听WebView的滚动偏移量,通过JsBridge调用端能力和Native进行交互,将offsetY偏移量实时通知客户端;
if (TextUtils.equals(targetUrl, URL_GETSCROLLOFFSET)) {
try {
int offsetY = Integer.parseInt(targetParams.get("offsetY"));
H5CallBackManager.getInstance().onGetScrollOffset(offsetY);
return true;
} catch (Exception e) {
CustomErrorManager.upload(CustomErrorManager.EXCEPTION, "jinggong/" + TAG,
"H5沉浸式传递offsetY类型异常", JsonUtil.toJsonStr(e.toString()));
e.printStackTrace();
return false;
}
}
Step4: 客户端收到通知后,变更状态栏和标题栏样式;
@Override
public void onGetScrollOffset(int scrollHeight) {
if (null != getActivity() && !((JsBridgeWebViewActivity) getActivity()).isFront()) {
return;
}
float percent = (float) (scrollHeight) / MAX_HEIGHT_VALUE;
if (percent > 1) {
percent = 1;
}
if (percent > 0.6) {
updateTitleStyle(STYLE_WHITE, percent);
if (!isStatusBarTransparent) {
isStatusBarTransparent = true;
StatusBarTintManager statusBarTintManager = new StatusBarTintManager(mActivity);
statusBarTintManager.setStatusBarWhite();
}
} else {
updateTitleStyle(STYLE_TRANSPARENT, 1 - percent);
if (isStatusBarTransparent) {
isStatusBarTransparent = false;
initStatusBar();
}
}
if (null != titleBg) {
titleBg.setAlpha(percent);
}
}
静态效果展示:
动态效果展示:
3 沉浸式TitleBar + IconTextBtn按钮
以文章详情页为例,当前加载的url是:
https://nowm.home.ke.com/jinggong/bj/article_detail?immersive=true&id=A-2000000089
在沉浸式标题栏的基础上,H5可以通过JsBridge方法setRightButton2设置标题栏右上角按钮结构,进行特殊处理:
3.1:按钮数据结构定义如下;
public class BaseRightButtonBean {
public String name;
public String clickUrl;
//非沉浸式-展示的icon链接
public String imageUrl;
//沉浸式-展示的icon链接
public String immersiveImageUrl;
//非沉浸式-展示的文字颜色
public String textHexColor;
//沉浸式-展示的文字颜色
public String immersiveTextColor;
...
}
3.2:根据实际交互约定,创建对应的RightIconTextButton组合按钮控件;
@NonNull
private View createRightIconTextButton(@NonNull final BaseRightButtonBean bean) {
String scheme = getCurrentUrl();
String immersive = "false";
if (!TextUtils.isEmpty(scheme)) {
final Map<String, String> targetParams = UrlUtil.parseParams(scheme);
immersive =
targetParams != null ? TextUtils.isEmpty(targetParams.get("immersive")) ? "false"
: targetParams.get("immersive") : "false";
}
RightIconTextButton buttonView = new RightIconTextButton(mActivity);
buttonView.bindData(bean, mImageLoader, immersive);
return buttonView;
}
RightIconTextButton根据H5设置的参数进行赋值展示;
3.3:H5滚动通知Native时,根据当前View类型,改变对应状态;
3.4:当用户点击收藏/点赞按钮时,客户端不关心具体业务,根据clickUrl参数,客户端调用WebView能力通知H5,请求H5页面的方法:
【当前传给native的协议是】:beikejinggong://postnotification?callback=praiseClicked
【回调给H5的js是】:javascript:praiseClicked()
H5自行处理接口请求、刷新等逻辑,接口请求成功后再次通过JsBridge调用setRightButton2方法刷新右上角按钮样式和状态,示例BaseRightButtonBean数据结构如下:
HybridBridge: setRightButton2:
[
{
"name":"1",
"clickUrl":"beikejinggong://postnotification?callback=praiseClicked",
"imageUrl":"https://image1.ljcdn.com/utopia-file/b7629f177b481c551123bc37cc5b9cdf.png",
"textHexColor":"#222222",
"immersiveImageUrl":"https://image1.ljcdn.com/utopia-file/6c66f6eb06b1cad108c85fc8a90b0f1e.png",
"immersiveTextColor":"#ffffff"
},
{
"name":"0",
"clickUrl":"beikejinggong://postnotification?callback=collectionClicked",
"imageUrl":"https://image1.ljcdn.com/utopia-file/d4a5343b4873759f2bee6f85e99efa58.png",
"textHexColor":"#222222",
"immersiveImageUrl":"https://image1.ljcdn.com/utopia-file/2813ade842a5445802143d9db78d2584.png",
"immersiveTextColor":"#ffffff"
},
{
"name":"",
"clickUrl":"beikejinggong://h5/share/menu?shareType=0&webUrl=https%3A%2F%2Fnowm.home.ke.com%2Fjinggong%2Fbj%2Farticle_detail%3Fid%3DA-2000000673%26tab%3D%26immersive%3Dtrue&title=%E3%80%90%E6%88%B7%E5%9E%8B%E8%A7%A3%E8%AF%BB%EF%BD%9C%E4%B8%87%E5%B9%B4%E8%8A%B1%E5%9F%8E%E5%9B%9B%E6%9C%9F%E4%B8%A4%E5%AE%A4%E4%B8%80%E5%8E%85%E3%80%91&description=%E8%A2%AB%E7%AA%9D%E5%AE%B6%E8%A3%85%E4%B8%93%E4%B8%9A%E8%AE%BE%E8%AE%A1%E5%B8%88%E7%9C%BC%E9%87%8C%E7%9A%84%E6%88%B7%E5%9E%8B%E4%BC%98%E7%BC%BA%E7%82%B9%E5%92%8C%E6%94%B9%E9%80%A0%E9%87%8D%E7%82%B9&thumbUrl=https%3A%2F%2Fimage1.ljcdn.com%2Futopia-file%2Ff30fcf683fa1623151a89d2fd9323556.png",
"imageUrl":"https://img.ljcdn.com/home-web-pic/fc1854f248e2e385eca90221b5c72722",
"immersiveImageUrl":"https://img.ljcdn.com/home-web-pic/0d818aa08b77d63cfdbdc2b702a38586"
}
]
实际运行效果如图:
4 总结
经过不断更新完善,目前这套WebView容器已经下沉封装到了通用引擎层中,在被窝家装、被窝设计、被窝运输、被窝精工、HOME等APP运行良好。