intitle:网站建设,如何把自己做的网站放到微信上,如何寻找建设网站的公司,网站开发业务方向架构文档对骨架屏的理解
什么是骨架屏
所谓骨架屏#xff0c;就是在页面进行耗时加载时#xff0c;先展示的等待 UI, 以告知用户程序目前正在运行#xff0c;稍等即可。 等待的UI大部分是 loading 转圈的弹窗#xff0c;有的是自己风格的小动画。其实大同小异。而骨架屏无非也是一…对骨架屏的理解
什么是骨架屏
所谓骨架屏就是在页面进行耗时加载时先展示的等待 UI, 以告知用户程序目前正在运行稍等即可。 等待的UI大部分是 loading 转圈的弹窗有的是自己风格的小动画。其实大同小异。而骨架屏无非也是一个等待的UI。基本是由各种灰色块组成夹杂着一些代表特殊样式的其他浅颜色的色块。骨架屏的不用之处就在于这些灰色块的排列组合和真正展示出来的页面样式基本一致。因此骨架屏的展示除了告知用户程序正在加载外还能让用户大概知道稍后将要展示的内容是什么给了用户一些期待从心理上让用户更愿意等待一会。
明明等2秒钟就会等够了返回离开现在的一点期待刺激着可以等3秒钟了。解决不了页面加载耗时就解决用户的等待意愿啊哈哈。
就像减肥一样坚持的困难性就在于你哼哧哼哧一顿不知道到底减了多少效果是什么。缺少刺激的机制。 就像我先把这两个对比图丢这你有了一个心理预期就更有意愿继续读下面这些枯燥的纯文字了(*^▽^*) 对骨架屏功能的探究
目前的各种骨架屏框架有的需要各种配置有的需要在正常的代码逻辑之外再编写展示这一堆灰色块的逻辑比如上图左侧的正常页面为了展示其骨架屏需要对照着左侧页面的结构手写一个各种灰色块的 xml 文件然后在加载等待前后进行两个布局的切换。对于列表来说还需要编写空的adapter来展示。此上种种少不了各种编码既增加繁琐的工作量又和正常的业务逻辑交织在一起很不友好啊。程序员是拒绝的。
另外通过调研发现
1、大部分的骨架屏是不支持交互的包括带列表的页面只有美团的骨架屏可以正常交互操作。其实不支持交互也是情理之中毕竟说到底就是个等待UI停留时间就1-2秒再长那真的说明这个页面的加载该优化了。美团这个只能说是牛掰了。
2、对于列表的填充展示骨架屏的列表样式和最终的item样式还是有较大区别的有的可以说是差别很多。归根结底还是因为有工作量映射个大概就差不多了。此外实际的业务逻辑会有各view的展示隐藏所以无法一一对应。 理想的骨架屏框架
那骨架屏的使用可以有多简单呢
1、希望没有一丝一毫的额外代码量。
2、对正常业务逻辑毫无侵入性引入后想用就用不想用就不用插拔式操作。
最终设计的骨架屏框架满足了以上0工作量0侵入的需求但是同时也为此舍弃了一些细节。 骨架屏原理简介
其主要原理是
解析正常页面的各 view 元素的布局位置然后在已有页面的上面增加一层蒙层skeletonview,然后通过draw方法将解析出的各个view 的 rect位置在skeletonview上画出来。
对于普通的view
比如 MainActivity 加载的xml文件是 activity_main.xml ?xml version1.0 encodingutf-8?
LinearLayoutxmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoxmlns:toolshttp://schemas.android.com/toolsandroid:idid/root_llandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:orientationverticalTextViewandroid:idid/content_tvandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:textSize20dpandroid:layout_margin15dpandroid:text骨架屏的实现原理/
/LinearLayout
在页面 layout 的时候就可以计算出 content_tv 的 x、y坐标以及长h、宽w。即view 的rect
然后根布局 root_ll 的上面新增一个铺满的空白view在此view draw的时候调用
canvas.drawRoundRect(rectF, radius, radius, paint)
将rect画出来至此就实现了骨架屏的效果 view 筛选
在真正的实现中对灰色块的样式进行了处理一是加了圆角而是缩减了它的宽和高也就是展示出来的灰色块要比view看起来短细一些。
主要原始是有些view是没有margin紧挨着的而内部有自己的padding所以展示出来的灰色块连成了一片另外一个原因就是将页面上的一些小view过滤掉通过设置阈值view 的 rect缩减后的大小小于这个阈值时就直接丢弃了。 对于列表viewRecyclerView、ListView:
舍弃了item样式的准确性。采用模糊处理的方式来填充列表灰块。
即事先编写好几套item样式的灰色块组合遇到列表选择类似的item样式画出来即可。
主要原因
1、复杂度的限制
如果去展示真正的item那么必须要加载这个item 的xml布局一旦要这么做那目前的骨架屏框架结构就被推翻了无法通过一个蒙层view来显示页面所有元素灰块的绘制。另外就必须要走列表加载Adapter的流程了增加了工作量做不到0侵入0代码的目的了。。
2、样式的限制
对于一个app来说因为业务的统一所以app中的列表样式基本上可以归纳为几种不会有太多的发散。加载一个真正的item和使用一个预设好的灰块组合差别不是很大。
所以在展示骨架屏时遇到列表直接配置一个最相似的 item样式即可。
比如 就是预设好的一组 rect 然后在 RecyclerView 的位置 Rect 内不停的重复draw 几个这样的灰色即可。 对于骨架屏的展示基本就是以上这两个方面。
下面是实际使用中的效果 使用方式
页面级别
如何使用呢
其实还是需要一点工作量这里的0代码夸张了一点
比如要对下面的布局使用骨架屏
?xml version1.0 encodingutf-8?
LinearLayoutxmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoxmlns:toolshttp://schemas.android.com/toolsandroid:idid/root_llandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:orientationverticalTextViewandroid:idid/content_tvandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:textSize20dpandroid:layout_margin15dpandroid:text骨架屏的实现原理/
/LinearLayout
只要在布局文件中增加一层父view即可
?xml version1.0 encodingutf-8?
com.haodf.skeleton.SkeletonLayoutxmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoxmlns:toolshttp://schemas.android.com/toolsandroid:idid/skeleton_slandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:orientationverticalTextViewandroid:idid/content_tvandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:layout_margin15dpandroid:background#dcdcdcandroid:textSize20dp //LinearLayout
/com.haodf.skeleton.SkeletonLayout
然后在代码里使用
//requester请求网络数据
skeleton_sl.loading()
requester.success { data -skeleton_sl.normal() initUI(data)}.failed{ error -skeleton_sl.normal()toast(error) }.reqeuset()
其实 skeleton_sl.loading() 出现的地方原本就是你发起网络请求时展示loading弹窗的地方只要替换一下即可。
说白了代码上的修改量就是把你现在的 LoadingDialog.show() 和LoadingDIalog.dismiss() 替换为 skeleton_sl.loading() 和 skeleton_sl.normal()
简单吧。 局部使用
到这里你会发现骨架屏是通过对目标view进行包裹实现的。这也说明这个骨架屏不仅可以让整个页面实现骨架屏效果还可以让任何一个view实现这个效果无非就是包裹住那一层view即可。
比如 原理简介
首先需要 SekeltonLayout 标签包裹住目标view 标签 target
SkeletonLayoutLinearLayoutid:target............/LinearLayout
/SkeletonLayout
在 SkeletonLayout 内在view 绘制流程的 OnLayout 阶段解析 以 target 为 root 的 view 树遍历找出所有的 View和特殊的 ViewGroup,比如 RecyclerView 等
然后通过
view.getGlobalVisibleRect(rect)
获取到这些view 在屏幕上的位置。
于是所有要绘制灰块的view变成了一个 rect 列表 rectList。
接下来 为 SkeletonLayout 添加一个子 view skeletonView ,作为绘制灰块的蒙层。
addView(skeletonView, 1)
skeletonView?.layoutParams LayoutParams(this.measuredWidth,this.measuredHeight
)
skeletonView 就是 SkeletonLayout 标签的宽高既挡住了下面的正常UI又作为一个画布在其上画各个rect.
然后在 skeletonView 的 onDraw方法中遍历 rectList针对每一个 rect 进行绘制即可。
canvas.drawRoundRect(rectF, radius, radius, paint)
rectF: RectF 就是rect,只不过转换一下支持圆角绘制罢了。
骨架屏的 loading 和 normal 状态就是 skeletonview 的 展示隐藏切换。 一些优化效果
灰块颜色
没有特殊处理所有的灰块都是灰色的。而实际页面可能会有 一些其他颜色的圆角背景之类还有一些特殊颜色的文字等等有时候将这些 view 的灰块按照原来的颜色和形状展示出来会更好一些。即让页面不那么呆板又能给用户一些颜色上的激励刺激用户的想象这里是橘色圆角肯定一会展示一个可以点的按钮吧嗯~ 等等看
所以在解析view 的rect时也对每个view 的颜色、样式、背景进行了解析。
对于背景通过view.background来解析
is GradientDrawable, is StateListDrawable - {val d view.background.constantState?.newDrawable()d?.colorFilter PorterDuffColorFilter(0x88ffffff.toInt(), PorterDuff.Mode.SRC_ATOP)d?.bounds rectgrayLands.add(GrayLand(this).apply {landType GrayLand.LAND_TYPE_DRAWABLEdrawable d})return false
}
在背景的处理过程中也需要注意不能原样绘制那样颜色太鲜艳了和整个骨架屏灰块色系不搭所以对解析出的drawable 又进行了颜色的淡化处理。
最终这些有背景的 view 的绘制则通过
drawable.draw(canvas)
来绘制。
对于文字颜色
无特殊处理均处理成灰色。有些特殊颜色的 textView ,希望在灰块展示时就显示其特殊性希望能以它本来的文字颜色作为灰块展示。
这个问题是通过 tag 标签来实现的。某个 textView 想以文字颜色来展示灰块只需要在xml文件中声明自己的tag标签值即可。
TextViewandroid:idid/titleandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:textSize19dpandroid:textColor#48aeffandroid:text这是一行蓝色的文字android:layout_centerHorizontaltrueandroid:layout_marginTop10dpandroid:tagsk_text_color/
对比一下效果
正常UI 没有特殊设置样式 设置了蓝色和绿色两个 textview 的 android:tagsk_text_color 动画效果
动画主要有等待时的loading动画和状态切换时的动画
loading动画
是通过 draw方法和 ValueAnimator来实现的。
draw来绘制每一时刻的样子ValueAnimator 来不断的改变动画的属性比如从左到右的位置变化。
默认提供了两个loading动画。一个是从左到右一闪一闪而过的光条。这个其实就是自定义了一个颜色渐变的 drawable 文件首先将它进行一点角度的旋转然后通过 drawable.draw()方法绘制而 ValueAnimator负责改变它从左到右的位置。
另一个是透明不断循环变化的动画这个是通过 ValueAnimator来不断改变 paint 的透明度进而影响整个骨架屏灰色块的透明度。
另外将动画的逻辑抽取出来提供了一个自定义loading 动画的接口 LoadingAnimator。
实现它即可快速自定义loading动画。 切换动画
即页面从loading到normal时的这个简单了 就是一个 骨架屏 skeletonView 透明度的渐变。 自定义配置
skeletonLayout.config {listviewItemType ItemRect.ITEM_TYPE_3loadingAnim ILoadingAnimtor.TYPE_BAI_JV_GUO_XIskeletonEnable truecustomLoadingAnim ILoadingAnimtor的子类
}
listviewItemType 设置列表展示的 item样式可以根据自己的项目提前定义好几种item 样式在不同页面设置不同的样式即可
样式1 样式2 样式3 skeletonEnable 是否开启 骨架屏效果
对于已经在 xml文件中添加 SkeletonLayout 标签的页面如果不想用骨架屏效果了不需要再去修改xml文件直接一行配置就可以开关骨架屏效果。
loadingAnim选择默认提供的两种loading动画效果
customLoadingAnim 使用自定义的loading 动画效果 这个骨架屏有什么遗憾的地方呢
1、列表样式的妥协放弃了准确性。
改为事先写好几种item 灰块样式使用时选择类似的展示。
带来的好处就是无需关系任何页面的具体UI无任何额外代码量。
补救措施
其实这个问题也有一个补救的措施在实际使用的时候可以扩展自定义的item样式。针对某个页面的列表根据它的 item的样式再写一组同样组合的灰块展示时通过listviewItemType 配置一下即可达到 item样式的准确性。 2、tools属性不友好
骨架屏的textview的灰块展示完全依赖于 xml 页面的默认样式我们在 xml中写一个
TextView android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitycenter_horizontal android:textSize20dp android:text骨架屏的不同之处 /
就会展示它的灰块。
但是如果把 android:text骨架屏的不同之处 替换为 tools:text骨架屏的不同之处 , 因为 tools 属性只是方便我们预览的在页面加载的时候这个 textView 的text还是“”就不会展示出来因此 view 的绘制流程就会计算出这个textview 的 rect 是 0,0,0,0因此它对应的骨架屏上就没有灰块。
这个问题的一个小解决方案是设置 layout_widthmatch_parent,
这样会计算出textview 的高度为字高宽度为父view宽度就能正常展示灰块。当然并不是所有的 textview 都可以设置 match_parent 的。
这是一个很矛盾的问题一方面使用 tools 很安全既能预览实际展示时的页面UI效果又不会因疏漏导致页面正式展示时展示了一些开发人员写的占位文案。
如果单纯为了骨架屏的展示把 tools:text 换成android:text 是不合理的得不偿失。而使用tools:text就会使预览看起来很丰满的一个页面在展示骨架屏时只有寥寥无几的几个灰块。
补救措施
在SkeletonLayout 的 onLayout之前遍历到的这些 textview,如果其 text值是空的则为他们赋一个默认的文案值这样在OnLayout 的时候就会计算出他们的有效 Rect。然后再把他们的text值恢复为空。
实测这个方案是可行的但是是不安全的数据加载过程中骨架屏是在 loading和normal之前切换的而这又同时伴随着各页面根据自己的实际业务逻辑在数据返回后对UI进行正式的赋值渲染。
因为无法确保为 textview赋默认值的操作和实际的UI赋值操作是否冲突万一先进行了实际赋值又进行了骨架屏的赋值所以把这一个方案暂时关掉了。 关于我实现的这个骨架屏框架基本介绍完了框架已投入项目使用了一段时间效果还不错。主要还是提出一种思路如何简单实效骨架屏效果。仅供大家参考。
如果想要框架完整代码使用的可以点我。