This article has been authorized “Yu Gang said” wechat public account exclusive release

At a time when the cost of acquiring users is getting higher and higher, a good user experience can better retain users. In order to improve the user experience of products, various technologies emerge one after another, among which chrysanthemum diagram and various loading animations derived from it are the most prominent.

For the chrysanthemum diagram, must be both love and hate. But now there is a better way than chrysanthemum design experience, often seen in Skeleton Screen Loading, called Skeleton Screen Loading in Chinese.

So what is a skeletal screen? Its semantics are as follows:

This means that before the page is fully rendered, the user is presented with a placeholder style, which depicts the general frame of the current page. After loading, the final placeholder parts of the skeleton screen are replaced by real data.

The renderings are as follows:

GitHub

  • throughVieworAdapterThe skeleton screen is the most common solution, which requires a separate layout for the skeleton screen page, if the page is too much or more complex, it is quite tedious to write. Concrete implementations includeShimmerRecyclerView,Skeletonandspruce-androidAnd other open source libraries.
  • Customize oneViewTo each in the layoutViewWhen loading data according toViewTo draw the skeleton, otherwise display the normal UI. Because the scheme needs to be eachViewWrap one layer, so it adds an extra layer of layout. Concrete implementations includeSkeleton AndroidAnd other open source libraries.

There are two ways to achieve a Skeleton screen on Android. The following are Skeleton and Skeleton Android as an example.

Skeleton

To use Skeleton, you need to import the following two libraries first.

dependencies {
      implementation 'com. Ethanhua: skeleton: 1.1.2'
      // The main is the implementation of animation
      implementation 'the IO. Supercharge: shimmerlayout: 2.1.0'
}
Copy the code

Skeleton not only supports skeleton screen on RecyclerView, but also supports skeleton screen on View. Let’s first look at the implementation on RecyclerView.

    recyclerView = findViewById(R.id.recycler);
    recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    / / the actual Adapter
    NewsAdapter adapter = new NewsAdapter();
    final SkeletonScreen skeletonScreen = Skeleton.bind(recyclerView)
            .adapter(adapter)// Set the actual Adapter
            .shimmer(true)// Whether to enable animation
            .angle(30)// Shimmer tilt Angle
//.color(R.coor. colorAccent)// Shimmer color
            .frozen(true)// True indicates that the RecyclerView cannot slide when the skeleton screen is displayed. Otherwise, it can slide
            .duration(1200)// Animation time, in milliseconds
            .count(10)// The number of items when the skeleton screen is displayed
            .load(R.layout.item_skeleton_news)// Skeleton screen UI
            .show(); //default count is 10
    recyclerView.postDelayed(new Runnable() {
        @Override
        public void run(a) { skeletonScreen.hide(); }},10000);// Delay time
Copy the code

Use is still relatively simple, mainly on the animation property Settings. When the show method is called, the skeleton screen is displayed. When the hide method is called, the skeleton screen is hidden and the normal UI is displayed. Let’s look at the implementation of these two methods.

public class RecyclerViewSkeletonScreen implements SkeletonScreen {

    / / the actual Adapter
    private final RecyclerView.Adapter mActualAdapter;
    // Adapter required for skeleton UI
    private finalSkeletonAdapter mSkeletonAdapter; .@Override
    public void show(a) {
        // Set skeleton UI Adapter to RecyclerView
        mRecyclerView.setAdapter(mSkeletonAdapter);
        if(! mRecyclerView.isComputingLayout() && mRecyclerViewFrozen) { mRecyclerView.setLayoutFrozen(true); }}@Override
    public void hide(a) {
        // Set Adapter of normal UI to RecyclerViewmRecyclerView.setAdapter(mActualAdapter); }... }Copy the code

As you can see from the above, implementing a skeleton screen on RecycleView is very simple, but you need to implement a separate set of layout for the skeleton screen, and then replace it with two Adapters.

While most of the time skeleton screens are used for lists and tables, there is also a need to use a skeleton screen on a View. Let’s take a look at how to implement a skeleton screen on a View.

   View rootView = findViewById(R.id.rootView);
   skeletonScreen = Skeleton.bind(rootView)
           .load(R.layout.activity_view_skeleton)// Skeleton screen UI
           .duration(1000)// Animation time, in milliseconds
           .shimmer(true)// Whether to enable animation
           .color(R.color.shimmer_color)The color of the / / shimmer
           .angle(30)// Shimmer tilt Angle
           .show();
   MyHandler myHandler = new MyHandler(this);
   myHandler.sendEmptyMessageDelayed(1.10000);
   // Close the skeleton screen and display the normal UI
   skeletonScreen.hide()
Copy the code

The usage is basically the same, but the main changes are in the show and hide methods.

public class ViewSkeletonScreen implements SkeletonScreen {
    //View replaces the utility class
    private final ViewReplacer mViewReplacer;
    / / practical View
    private finalView mActualView; .@Override
    public void show(a) {
        View skeletonLoadingView = generateSkeletonLoadingView();
        if(skeletonLoadingView ! =null) {
            // Use skeleton UI instead of actual UImViewReplacer.replace(skeletonLoadingView); }}@Override
    public void hide(a) {
        if (mViewReplacer.getTargetView() instanceof ShimmerLayout) {
            ((ShimmerLayout) mViewReplacer.getTargetView()).stopShimmerAnimation();
        }
        // Remove the skeleton UI and display the actual UImViewReplacer.restore(); }... }//View replaces the implementation class
public class ViewReplacer {
    // View where the actual UI is located
    private final View mSourceView;
    // View where the skeleton UI is located
    privateView mTargetView; .public void replace(View targetView) {...if (init()) {
            mTargetView = targetView;
            // Remove the current View, i.e. the View where the actual UI is located
            mSourceParentView.removeView(mCurrentView);
            mTargetView.setId(mSourceViewId);
            // Add the View where the skeleton UI is locatedmSourceParentView.addView(mTargetView, mSourceViewIndexInParent, mSourceViewLayoutParams); mCurrentView = mTargetView; }}public void restore(a) {
        if(mSourceParentView ! =null) {
            // Remove the current View, the View where the skeleton UI is located
            mSourceParentView.removeView(mCurrentView);
            // Add the View where the actual UI is located
            mSourceParentView.addView(mSourceView, mSourceViewIndexInParent, mSourceViewLayoutParams);
            mCurrentView = mSourceView;
            mTargetView = null;
            mTargetViewResID = -1; }}... }Copy the code

The results are as follows.

View
View
Skeleton

Skeleton Android

To use Skeleton Android, you first need to import the Skeleton Android repository in build.gradle at the root of your project.

allprojects {
    repositories {
	    ...
	    maven { url 'https://jitpack.io'}}}Copy the code

Then import the following library into the build.gradle file in the app directory.

dependencies {
      compile 'com. Making. Rasoulmiri: Skeleton: v1.0.9'
}
Copy the code

One thing to note here is that referencing the library automatically refers to the two libraries, AppCompat-v7 and CardView-v7, and the versions may be older, so there may be version conflicts. The solution is as follows.

dependencies {
    implementation ('com. Making. Rasoulmiri: Skeleton: v1.0.9'){
        exclude group: 'com.android.support'}}Copy the code

First look at how to achieve Skeleton screen on RecyclerView through Skeleton Android. The biggest difference between Skeleton Android and Skeleton is that it doesn’t require a layout for the Skeleton screen, but it’s a little more complicated to use.

   recyclerView.setLayoutManager(new GridLayoutManager(this.2));
   list = new ArrayList<>();
   adapter = new PersonAdapter(this, list, recyclerView, new IsCanSetAdapterListener() {
       @Override
       public void isCanSet(a) { recyclerView.setAdapter(adapter); }});new Handler().postDelayed(new Runnable() {
       @Override
       public void run(a) {
           for (int i = 0; i < 100; i++) {
               list.add("str"+ i); } adapter.addMoreDataAndSkeletonFinish(list); }},5000);
   / / the realization of the 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(a) {
        return 50; }}Copy the code

Using Skeleton Android requires a custom Adapter that inherits from AdapterSkeleton, as well as a height measurement in the constructor. So it’s a little bit more restrictive. Let’s look at the implementation of the layout file.

<?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>
Copy the code

An additional layer of layout is obviously added. Let’s take a look at how Android implements a Skeleton screen on a View with Skeleton.

   skeletonGroup = (SkeletonGroup) findViewById(R.id.skeletonGroup);
   textTv = (TextView) findViewById(R.id.textTv);
   skeletonGroup.setSkeletonListener(new SkeletonGroup.SkeletonListener() {
       @Override
       public void onStartAnimation(a) {}@Override
       public void onFinishAnimation(a) {// Display the load data
           textTv.setText("The Android O release ultimately 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(a) { skeletonGroup.finishAnimation(); }},5000);
Copy the code

It’s a lot easier than implementing skeleton screens on RecycleView, but of course, you need to wrap controls in layout files, too.

<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>
Copy the code

The results are as follows.

Skeleton Android
SkeletonGroup
SkeletonView
SkeletonGroup
SkeletonView
RelativeLayout
SkeletonView
SkeletonGroup
SkeletonView

conclusion

The skeleton screen on Android was introduced earlier. The main difference between them is whether they need to implement their own skeleton screen layout. However, Skeleton is much more convenient to use than Skeleton Android, and scalability is a bit better. Of course, we can also implement the skeleton screen based on the ideas of these two schemes.

Details about the skeleton screen of the client