一种简单的Android骨架屏实现方案----0侵入0成本

这篇具有很好参考价值的文章主要介绍了一种简单的Android骨架屏实现方案----0侵入0成本。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

对骨架屏的理解

什么是骨架屏

所谓骨架屏,就是在页面进行耗时加载时,先展示的等待 UI, 以告知用户程序目前正在运行,稍等即可。 等待的UI大部分是 loading 转圈的弹窗,有的是自己风格的小动画。其实大同小异。而骨架屏无非也是一个等待的UI。基本是由各种灰色块组成,夹杂着一些代表特殊样式的其他浅颜色的色块。骨架屏的不用之处就在于这些灰色块的排列组合和真正展示出来的页面样式基本一致。因此骨架屏的展示除了告知用户程序正在加载外,还能让用户大概知道稍后将要展示的内容是什么,给了用户一些期待,从心理上,让用户更愿意等待一会。

明明等2秒钟就会等够了返回离开,现在的一点期待刺激着可以等3秒钟了。解决不了页面加载耗时,就解决用户的等待意愿啊,哈哈。

就像减肥一样,坚持的困难性就在于你哼哧哼哧一顿,不知道到底减了多少,效果是什么。缺少刺激的机制。

android骨架屏,android

 (就像我先把这两个对比图丢这,你有了一个心理预期,就更有意愿继续读下面这些枯燥的纯文字了,(*^▽^*))

对骨架屏功能的探究

目前的各种骨架屏框架,有的需要各种配置,有的需要在正常的代码逻辑之外,再编写展示这一堆灰色块的逻辑,比如上图左侧的正常页面,为了展示其骨架屏,需要对照着左侧页面的结构,手写一个各种灰色块的 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

android骨架屏,android

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:id="@+id/root_ll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/content_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:layout_margin="15dp"
        android:text="骨架屏的实现原理"
        />
</LinearLayout>

在页面 layout 的时候就可以计算出 content_tv 的 x、y坐标,以及长h、宽w。即view 的rect

然后根布局 root_ll 的上面新增一个铺满的空白view,在此view draw的时候,调用

canvas.drawRoundRect(rectF, radius, radius, paint)

将rect画出来,至此就实现了骨架屏的效果

android骨架屏,android

view 筛选

在真正的实现中,对灰色块的样式进行了处理,一是加了圆角,而是缩减了它的宽和高,也就是展示出来的灰色块要比view看起来短细一些。

主要原始是有些view是没有margin,紧挨着的,而内部有自己的padding,所以展示出来的灰色块连成了一片,另外一个原因就是将页面上的一些小view过滤掉,通过设置阈值,view 的 rect缩减后的大小小于这个阈值时,就直接丢弃了。

对于列表view(RecyclerView、ListView):

舍弃了item样式的准确性。采用模糊处理的方式来填充列表灰块。

即事先编写好几套item样式的灰色块组合,遇到列表,选择类似的item样式画出来即可。

主要原因:

1、复杂度的限制

如果去展示真正的item,那么必须要加载这个item 的xml布局,一旦要这么做,那目前的骨架屏框架结构就被推翻了,无法通过一个蒙层view来显示页面所有元素灰块的绘制。另外就必须要走列表加载Adapter的流程了,增加了工作量,做不到0侵入,0代码的目的了。。

2、样式的限制

对于一个app来说,因为业务的统一,所以app中的列表样式基本上可以归纳为几种,不会有太多的发散。加载一个真正的item,和使用一个预设好的灰块组合,差别不是很大。

所以在展示骨架屏时遇到列表,直接配置一个最相似的 item样式即可。

比如

android骨架屏,android

就是预设好的一组 rect

android骨架屏,android

然后在 RecyclerView 的位置 Rect 内,不停的重复draw 几个这样的灰色即可。

对于骨架屏的展示,基本就是以上这两个方面。

下面是实际使用中的效果:


android骨架屏,android

使用方式

页面级别

如何使用呢?

其实还是需要一点工作量,这里的0代码,夸张了一点

比如要对下面的布局使用骨架屏:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:id="@+id/root_ll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/content_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:layout_margin="15dp"
        android:text="骨架屏的实现原理"
        />
</LinearLayout>

只要在布局文件中增加一层父view即可:

<?xml version="1.0" encoding="utf-8"?>
<com.haodf.skeleton.SkeletonLayout
    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:id="@+id/skeleton_sl"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/content_tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="15dp"
            android:background="#dcdcdc"
            android:textSize="20dp" />
    </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即可。

比如:

android骨架屏,android

原理简介

首先需要 SekeltonLayout 标签包裹住目标view 标签 target

<SkeletonLayout>
    <LinearLayout
        id: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 = rect
    grayLands.add(GrayLand(this).apply {
        landType = GrayLand.LAND_TYPE_DRAWABLE
        drawable = d
    })
    return false
}

在背景的处理过程中,也需要注意不能原样绘制,那样颜色太鲜艳了,和整个骨架屏灰块色系不搭,所以对解析出的drawable 又进行了颜色的淡化处理。

最终,这些有背景的 view 的绘制,则通过

drawable.draw(canvas)

来绘制。

对于文字颜色:

无特殊处理,均处理成灰色。有些特殊颜色的 textView ,希望在灰块展示时就显示其特殊性,希望能以它本来的文字颜色作为灰块展示。

这个问题是通过 tag 标签来实现的。某个 textView 想以文字颜色来展示灰块,只需要在xml文件中声明自己的tag标签值即可。

<TextView
    android:id="@+id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="19dp"
    android:textColor="#48aeff"
    android:text="这是一行蓝色的文字"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="10dp"
    android:tag="sk_text_color"
    />

对比一下效果:

正常UI

android骨架屏,android

没有特殊设置样式

android骨架屏,android

设置了蓝色和绿色两个 textview 的 android:tag="sk_text_color"

android骨架屏,android

动画效果:

动画主要有等待时的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_3
    loadingAnim = ILoadingAnimtor.TYPE_BAI_JV_GUO_XI
    skeletonEnable = true
    customLoadingAnim = ILoadingAnimtor的子类
}

listviewItemType :设置列表展示的 item样式,可以根据自己的项目,提前定义好几种item 样式,在不同页面设置不同的样式即可

样式1:

android骨架屏,android

样式2:

android骨架屏,android

样式3:

android骨架屏,android

skeletonEnable :是否开启 骨架屏效果

对于已经在 xml文件中添加 SkeletonLayout 标签的页面,如果不想用骨架屏效果了,不需要再去修改xml文件,直接一行配置就可以开关骨架屏效果。

loadingAnim:选择默认提供的两种loading动画效果

customLoadingAnim :使用自定义的loading 动画效果,

这个骨架屏有什么遗憾的地方呢:

1、列表样式的妥协,放弃了准确性。

改为事先写好几种item 灰块样式,使用时选择类似的展示。

带来的好处就是无需关系任何页面的具体UI,无任何额外代码量。

补救措施

其实这个问题也有一个补救的措施,在实际使用的时候,可以扩展自定义的item样式。针对某个页面的列表,根据它的 item的样式,再写一组同样组合的灰块,展示时通过listviewItemType 配置一下,即可达到 item样式的准确性。

2、tools属性不友好

骨架屏的textview的灰块展示完全依赖于 xml 页面的默认样式,我们在 xml中写一个

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:textSize="20dp" android:text="骨架屏的不同之处" />

就会展示它的灰块。

但是如果把 android:text="骨架屏的不同之处" 替换为 tools:text="骨架屏的不同之处" , 因为 tools 属性只是方便我们预览的,在页面加载的时候,这个 textView 的text还是“”,就不会展示出来,因此 view 的绘制流程就会计算出这个textview 的 rect 是 (0,0,0,0),因此它对应的骨架屏上就没有灰块。

这个问题的一个小解决方案是设置 layout_width="match_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赋值操作是否冲突,万一先进行了实际赋值,又进行了骨架屏的赋值,所以把这一个方案暂时关掉了。

关于我实现的这个骨架屏框架基本介绍完了,=框架已投入项目使用了一段时间,效果还不错。主要还是提出一种思路,如何简单实效骨架屏效果。仅供大家参考。

如果想要框架完整代码使用的,可以点我。

源码:

skeletonscreen

持续更新

2023-10-31更新:

列表item优化:可以展示准确的列表骨架屏样式了

0代码0侵入的安卓骨架屏框架----二期优化文章来源地址https://www.toymoban.com/news/detail-631328.html

到了这里,关于一种简单的Android骨架屏实现方案----0侵入0成本的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序---骨架屏实现,实现起来超级简单

    骨架屏是页面的一个空白版本,通常会在页面完全渲染之前,通过一些灰色的区块大致勾勒出轮廓,待数据加载完成后,再替换成真实的内容。通常在小程序中,我们需要手工维护骨架屏的代码,当业务变更时,同样需要对骨架屏代码进行调整。 请求数据时都会出现一定的延

    2024年02月11日
    浏览(50)
  • Android实现简单的登录界面

    该登录界面一共实现三个功能: 1.实现登录 2.实现注册 3.实现记住密码 AndroidManifest.xml 文件: bean文件中的Account类:  db文件夹中的AccountService: db文件夹中的DataBaseHelper: MainActivity:  loginActivity registeredActivity layout文件中的activity_main layout中的login文件 layout文件中的registere

    2023年04月16日
    浏览(47)
  • Android Studio实现简单ListView

    效果图 MainActivity PartBean 建议重新建一个软件包 PartAdapter 适配器 建议重新建一个适配器包 activity_main.xml 顶部标题和 ListView pat_manger_item ListView中数据的展示格式

    2024年02月13日
    浏览(43)
  • android实现简单进度条ProgressBar

    记录一下今天学习的进度条ProgressBar 1、在布局文件中添加ProgressBar 其中 **style=“@android:style/Widget.ProgressBar.Horizontal”**设置进度条样式为水平进度条,否则默认原型旋转的进度条; max 设置进度条长度,这里设置为100。 2、java代码: 声明ProgressBar;int型的mprogress表示进度条进度

    2023年04月08日
    浏览(35)
  • Android发送短信最简单代码实现

    实现效果如下:(为了尽量使用最少的代码实现功能,界面十分简单,方便理解原理) 这边我输入短信内容为\\\"252525\\\"                                                                                           图一  输入发送号码和指定内容                    

    2024年02月04日
    浏览(38)
  • Android文档预览实现方案(替换TBS)

    之前文件预览一直使用腾讯的TBS里面的文件预览服务,但是由于TBS SDK的内核文档能力已经下线了,详情可以点击查看 关于腾讯浏览服务内核SDK-内核文档能力调整公告 所以现在已经不能使用TBS来实现文档查看了,现在有两个方案来实现,  1、切换成腾讯自己的 文档浏览引擎

    2024年02月14日
    浏览(52)
  • android studio实现简单的页面跳转

    运用intent组件实现简单的跳转 主页面 跳转后的页面(返回功能) 此时就可以满足页面间的跳转和返回了,当时第一次做觉得这样就结束了,结果运行发现有报错,后来发现忽视了还要在配置文件加上新创建的页面,这里大家也要注意⚠️⚠️⚠️ 在AndroidMainfest.xml里加上新

    2024年02月07日
    浏览(39)
  • Android4.4实现简单的录像功能

    一、前言 在Android中实现录像功能,高版本Android和低版本Android的API使用方式不同。本文以Android4.4系统为基础,简单的列举一下实现过程。 二、步骤 要在 Android 4.4 中实现录像功能,可按照以下步骤进行操作: 在布局文件中添加一个 SurfaceView 控件,作为预览画面的容器。 获

    2024年02月10日
    浏览(41)
  • Android U user+root实现方案

           由于项目(MTK平台)上要实现user+root的版本,供特殊用户使用。Android T上的方案无效,经历了各种搜索查看资料,和bsp大佬一起通宵奋战,整出了方案。梳理记录下,有需要的同学可以参考。       系统判断是否有root权限的地方在system/packages/modules/adb/daemon/main.cpp里

    2024年04月09日
    浏览(38)
  • Android 无限循环RecyclerView的完美实现方案

    方案1 对Adapter进行修改 网上大部分博客的解决方案都是这种方案,对Adapter做修改。具体如下 首先,让 Adapter 的 getItemCount() 方法返回 Integer.MAX_VALUE,使得position数据达到很大很大; 其次,在 onBindViewHolder() 方法里对position参数取余运算,拿到position对应的真实数据索引,然后对

    2024年01月23日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包