骨架屏(Skeleton Screen)在Android中的应用

栏目: IOS · Android · 发布时间: 5年前

内容简介:在如今获取用户成本越来越高的情况下,好的用户体验能够更好的留住用户。为了提升产品的用户体验,各种技术层出不穷,其中,尤以菊花图以及由它衍生出的各种加载动画最为突出。对于菊花图,想必是又爱又恨。而如今有了比菊花图设计体验更棒的方法,即常看到的那什么是骨架屏尼?它的语义如下:

在如今获取用户成本越来越高的情况下,好的用户体验能够更好的留住用户。为了提升产品的用户体验,各种技术层出不穷,其中,尤以菊花图以及由它衍生出的各种加载动画最为突出。

对于菊花图,想必是又爱又恨。而如今有了比菊花图设计体验更棒的方法,即常看到的 Skeleton Screen Loading ,中文叫做 骨架屏

那什么是骨架屏尼?它的语义如下:

即表示在页面完全渲染完成之前,用户会看到一个占位的样式,用以描绘了当前页面的大致框架,加载完成后,最终骨架屏中各个占位部分将被真实的数据替换。

其效果图如下:

骨架屏(Skeleton Screen)在Android中的应用
骨架屏(Skeleton Screen)在Android中的应用
本着不重复造轮子的思想,从 GitHub

上找了一些骨架屏的实现。当然也可以自己来实现。其最核心就是占位和属性动画的实现。

  • 通过 View 或者 Adapter 的替换来实现骨架屏是最普遍的方案,该方案需要单独为骨架屏页面进行布局,如果页面过多或者比较复杂,写起来就还是蛮繁琐的。具体实现有 ShimmerRecyclerViewSkeletonspruce-android 等开源库。
  • 自定义一个 View 来对布局中的每个 View 进行一层包裹,当加载数据时则根据 View 来绘制骨架,否则显示正常UI。由于该方案需要将每个 View 包裹一层,所以会增加额外的布局层次。具体实现有 Skeleton Android 等开源库。

上面就是目前在Android上实现骨架屏的两种方案,下面以 SkeletonSkeleton Android 为例进行讲解。

Skeleton

要想使用 Skeleton ,需要先导入以下两个库。

dependencies {
      implementation 'com.ethanhua:skeleton:1.1.2'
      //主要是动画的实现
      implementation 'io.supercharge:shimmerlayout:2.1.0'
}
复制代码

skeleton 不仅支持在 RecyclerView 上实现骨架屏,也支持在 View 上实现骨架屏。 先来看看在 RecyclerView 上的实现。

recyclerView = findViewById(R.id.recycler);
    recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    //实际Adapter
    NewsAdapter adapter = new NewsAdapter();
    final SkeletonScreen skeletonScreen = Skeleton.bind(recyclerView)
            .adapter(adapter)//设置实际adapter
            .shimmer(true)//是否开启动画
            .angle(30)//shimmer的倾斜角度
//          .color(R.color.colorAccent)//shimmer的颜色
            .frozen(true)//true则表示显示骨架屏时,RecyclerView不可滑动,否则可以滑动
            .duration(1200)//动画时间,以毫秒为单位
            .count(10)//显示骨架屏时item的个数
            .load(R.layout.item_skeleton_news)//骨架屏UI
            .show(); //default count is 10
    recyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {
            skeletonScreen.hide();
        }
    }, 10000);//延迟时间
复制代码

使用还是比较简单的,主要是对动画属性的设置。当调用 show 方法时就会显示骨架屏,调用 hide 就会隐藏骨架屏,显示正常UI。下面就来看看这两个方法的实现。

public class RecyclerViewSkeletonScreen implements SkeletonScreen {

    //实际Adapter
    private final RecyclerView.Adapter mActualAdapter;
    //骨架UI所需Adapter
    private final SkeletonAdapter mSkeletonAdapter;
    ...
    @Override
    public void show() {
        //将骨架UI的Adapter设置给RecyclerView
        mRecyclerView.setAdapter(mSkeletonAdapter);
        if (!mRecyclerView.isComputingLayout() && mRecyclerViewFrozen) {
            mRecyclerView.setLayoutFrozen(true);
        }
    }

    @Override
    public void hide() {
        //将正常UI的Adapter设置给RecyclerView
        mRecyclerView.setAdapter(mActualAdapter);
    }
    ...
}
复制代码

从上面可以看出,在 RecycleView 上实现骨架屏是非常简单的,但需要为骨架屏单独实现一套布局,然后通过两个 Adapter 替换即可。 虽然骨架屏很多时候都是用在列表、表格中使用,但也有在 View 上使用的需求,下面就来看看如何在 View 上实现骨架屏。

View rootView = findViewById(R.id.rootView);
   skeletonScreen = Skeleton.bind(rootView)
           .load(R.layout.activity_view_skeleton)//骨架屏UI
           .duration(1000)//动画时间,以毫秒为单位
           .shimmer(true)//是否开启动画
           .color(R.color.shimmer_color)//shimmer的颜色
           .angle(30)//shimmer的倾斜角度
           .show();
   MyHandler myHandler = new MyHandler(this);
   myHandler.sendEmptyMessageDelayed(1, 10000);
   //关闭骨架屏,显示正常UI
   skeletonScreen.hide()
复制代码

用法基本上不变,主要变化就在 showhide 这两个方法中。

public class ViewSkeletonScreen implements SkeletonScreen {
    //View替换的 工具 类
    private final ViewReplacer mViewReplacer;
    //实际View
    private final View mActualView;
    ...
    @Override
    public void show() {
        View skeletonLoadingView = generateSkeletonLoadingView();
        if (skeletonLoadingView != null) {
            //使用骨架屏UI替换实际UI
            mViewReplacer.replace(skeletonLoadingView);
        }
    }

    @Override
    public void hide() {
        if (mViewReplacer.getTargetView() instanceof ShimmerLayout) {
            ((ShimmerLayout) mViewReplacer.getTargetView()).stopShimmerAnimation();
        }
        //移除骨架屏UI,显示实际UI
        mViewReplacer.restore();
    }
    ...
}
//View替换实现类
public class ViewReplacer {
    //实际UI所在的View
    private final View mSourceView;
    //骨架屏UI所在View
    private View mTargetView;
    ...
    public void replace(View targetView) {
        ...
        if (init()) {
            mTargetView = targetView;
            //移除当前View,即实际UI所在View
            mSourceParentView.removeView(mCurrentView);
            mTargetView.setId(mSourceViewId);
            //将骨架屏UI所在View添加进来
            mSourceParentView.addView(mTargetView, mSourceViewIndexInParent, mSourceViewLayoutParams);
            mCurrentView = mTargetView;
        }
    }

    public void restore() {
        if (mSourceParentView != null) {
            //移除当前View,即骨架屏UI所在View
            mSourceParentView.removeView(mCurrentView);
            //将实际UI所在View添加进来
            mSourceParentView.addView(mSourceView, mSourceViewIndexInParent, mSourceViewLayoutParams);
            mCurrentView = mSourceView;
            mTargetView = null;
            mTargetViewResID = -1;
        }
    }
    ...
}
复制代码

实现效果如下。

骨架屏(Skeleton Screen)在Android中的应用
从上面可以看出,在 View 上实现骨架屏也是非常简单的,也需要为骨架屏单独写一套布局,然后通过两个 View 替换即可。 从使用及具体实现上可以发现 Skeleton

还是蛮简单的。但最大的缺点就是要专门为骨架屏实现一套布局,比较繁琐。

Skeleton Android

要想使用 Skeleton Android ,首先需要在项目根目录下的 build.gradle 导入存储 Skeleton Android 的仓库。

allprojects {
    repositories {
	    ...
	    maven { url 'https://jitpack.io' }
    }
}
复制代码

然后在 app 目录下的 build.gradle 文件中导入下面这个库即可。

dependencies {
      compile 'com.github.rasoulmiri:Skeleton:v1.0.9'
}
复制代码

这里有一点需要注意,引用该库会自动引用 appcompat-v7cardview-v7 这两个库且版本可能较低,所以可能会存在版本冲突问题,解决方案如下。

dependencies {
    implementation ('com.github.rasoulmiri:Skeleton:v1.0.9'){
        exclude group: 'com.android.support'
    }
}
复制代码

先来看如何通过 Skeleton AndroidRecyclerView 上实现骨架屏。 Skeleton Android 相比 Skeleton 最大的区别就是不需要专门为骨架屏实现一套布局,但使用起来就稍微复杂一些。

recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
   list = new ArrayList<>();
   adapter = new PersonAdapter(this, list, recyclerView, new IsCanSetAdapterListener() {
       @Override
       public void isCanSet() {
           recyclerView.setAdapter(adapter);
       }
   });

   new Handler().postDelayed(new Runnable() {
       @Override
       public void run() {
           for (int i = 0; i < 100; i++) {
               list.add("str" + i);
           }
           adapter.addMoreDataAndSkeletonFinish(list);
       }
   }, 5000);
   //adapter的实现
   public class PersonAdapter extends AdapterSkeleton<String, SimpleRcvViewHolder> {

    public PersonAdapter(final Context context, final List<String> items, final RecyclerView recyclerView, final IsCanSetAdapterListener IsCanSetAdapterListener) {
        this.context = context;
        this.items = items;
        this.isCanSetAdapterListener = IsCanSetAdapterListener;
        measureHeightRecyclerViewAndItem(recyclerView, R.layout.item_person);// Set height

    }

    @Override
    public SimpleRcvViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new SimpleRcvViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_person, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull SimpleRcvViewHolder holder, int position) {
        SkeletonGroup skeletonGroup = holder.getView(R.id.skeleton_group);
        if (skeletonConfig.isSkeletonIsOn()) {
            //need show s for 2 cards
            skeletonGroup.setAutoPlay(true);
            return;
        } else {
            skeletonGroup.setShowSkeleton(false);
            skeletonGroup.finishAnimation();
        }
    }

    @Override
    public int getItemCount() {
        return 50;
    }

}
复制代码

在使用 Skeleton Android 时需要我们自定义的 Adapter 去继承 AdapterSkeleton ,也需要在构造方法里进行高度的测量。所以这样就会限制比较大。再来看布局文件的实现。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/bg_grid_item">
    <io.rmiri.skeleton.SkeletonGroup
        android:id="@+id/skeleton_group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            ...>
            <io.rmiri.skeleton.SkeletonView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <ImageView
                    ... />
            </io.rmiri.skeleton.SkeletonView>

            <io.rmiri.skeleton.SkeletonView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

                <TextView
                    ... />
            </io.rmiri.skeleton.SkeletonView>
            
            <io.rmiri.skeleton.SkeletonView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <TextView
                    ... />
            </io.rmiri.skeleton.SkeletonView>
            
            <io.rmiri.skeleton.SkeletonView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <TextView
                    ... />
            </io.rmiri.skeleton.SkeletonView>
        </LinearLayout>
    </io.rmiri.skeleton.SkeletonGroup>
</LinearLayout>
复制代码

很明显增加了额外的布局层级。下面再来看通过 Skeleton AndroidView 上实现骨架屏。

skeletonGroup = (SkeletonGroup) findViewById(R.id.skeletonGroup);
   textTv = (TextView) findViewById(R.id.textTv);
   skeletonGroup.setSkeletonListener(new SkeletonGroup.SkeletonListener() {
       @Override
       public void onStartAnimation() {

       }

       @Override
       public void onFinishAnimation() {//显示加载数据
           textTv.setText("The Android O release ultimately became Android 8.0 Oreo, as predicted by pretty much everyone the first time they thought of a sweet");
      }
   });

   new Handler().postDelayed(new Runnable() {
       @Override
       public void run() {
           skeletonGroup.finishAnimation();
       }
   }, 5000);
复制代码

比在 RecycleView 上实现骨架屏简单多了,当然,布局文件里也需要将控件进行一层包裹。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:Skeleton="http://schemas.android.com/apk/res-auto"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <TextView
        ... />
    <io.rmiri.skeleton.SkeletonGroup
        android:id="@+id/skeletonGroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        Skeleton:SK_BackgroundViewsColor="#EEEEEE"
        Skeleton:SK_animationAutoStart="true"
        Skeleton:SK_animationDirection="LTR"
        Skeleton:SK_animationDuration="1000"
        Skeleton:SK_animationFinishType="none"
        Skeleton:SK_animationNormalType="alpha"
        Skeleton:SK_backgroundMainColor="@android:color/transparent"
        Skeleton:SK_highLightColor="#DEDEDE">
        <LinearLayout
            ...>
            <!--Rect-->
            <LinearLayout
                ...>
                <TextView
                    .... />
                <io.rmiri.skeleton.SkeletonView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    Skeleton:SK_shapeType="rect">
                    
                    <TextView
                        ... />
                        
                </io.rmiri.skeleton.SkeletonView>
            </LinearLayout>
            <!--Oval-->
            <LinearLayout
                ...>
                <TextView
                    ... />
                <io.rmiri.skeleton.SkeletonView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    Skeleton:SK_shapeType="oval">
                    
                    <android.support.v7.widget.AppCompatImageButton
                        ... />
                        
                </io.rmiri.skeleton.SkeletonView>
            </LinearLayout>
            <!--Text-->
            <LinearLayout
                ...>
                <TextView
                    ... />
                    
                <io.rmiri.skeleton.SkeletonView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    Skeleton:SK_shapeType="text"
                    Skeleton:SK_textLineHeight="16dp"
                    Skeleton:SK_textLineLastWidth="threeQuarters"
                    Skeleton:SK_textLineNumber="5"
                    Skeleton:SK_textLineSpaceVertical="4dp">
                    
                    <TextView
                        ... />
                        
                </io.rmiri.skeleton.SkeletonView>
            </LinearLayout>
        </LinearLayout>
    </io.rmiri.skeleton.SkeletonGroup>
</LinearLayout>
复制代码

实现效果如下。

骨架屏(Skeleton Screen)在Android中的应用
上面介绍了 Skeleton Android 的使用,它的原理基本上就是通过 SkeletonGroupSkeletonView 这两个控件来进行骨架的绘制。 SkeletonGroupSkeletonView 都是继承自 RelativeLayout 的自定义控件, SkeletonView 起一个标识的作用,在 SkeletonGroup 中会将 SkeletonView

绘制成相应的长方形、圆形等骨架。

总结

前面介绍了骨架屏在Android上的应用。它们的区别主要是需不需要自己来实现骨架屏布局。但是从使用上来说 Skeleton 要比 Skeleton Android 方便很多,扩展性也更好一点。当然我们也可以根据这两种方案的思想来自己实现骨架屏。

客户端骨架屏详解


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

区块链技术驱动金融

区块链技术驱动金融

阿尔文德·纳拉亚南、约什·贝努、爱德华·费尔顿、安德鲁·米勒、史蒂文·戈德费德 / 林华、王勇 / 中信出版社,中信出版集团 / 2016-8-25 / CNY 79.00

从数字货币及智能合约技术层面,解读了区块链技术在金融领域的运用。“如果你正在寻找一本在技术层面解释比特币是如何运作的,并且你有一定计算机科学和编程的基本知识,这本书应该很适合你。” 《区块链:技术驱动金融》回答了一系列关于比特币如何运用区块链技术运作的问题,并且着重讲述了各种技术功能,以及未来会形成的网络。比特币是如何运作的?它因何而与众不同?你的比特币安全吗?比特币用户如何匿名?区块链如何......一起来看看 《区块链技术驱动金融》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码