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

本文已受權「玉剛說」微信公衆號獨家發佈php

 在現在獲取用戶成本愈來愈高的狀況下,好的用戶體驗可以更好的留住用戶。爲了提高產品的用戶體驗,各類技術層出不窮,其中,尤以菊花圖以及由它衍生出的各類加載動畫最爲突出。java

 對於菊花圖,想必是又愛又恨。而現在有了比菊花圖設計體驗更棒的方法,即常看到的Skeleton Screen Loading,中文叫作骨架屏android

 那什麼是骨架屏尼?它的語義以下:git

即表示在頁面徹底渲染完成以前,用戶會看到一個佔位的樣式,用以描繪了當前頁面的大體框架,加載完成後,最終骨架屏中各個佔位部分將被真實的數據替換。github

 其效果圖以下: 微信

在這裏插入圖片描述
在這裏插入圖片描述
 本着不重複造輪子的思想,從 GitHub上找了一些骨架屏的實現。固然也能夠本身來實現。其最核心就是佔位和屬性動畫的實現。

  • 經過View或者Adapter的替換來實現骨架屏是最廣泛的方案,該方案須要單獨爲骨架屏頁面進行佈局,若是頁面過多或者比較複雜,寫起來就仍是蠻繁瑣的。具體實現有ShimmerRecyclerViewSkeletonspruce-android等開源庫。
  • 自定義一個View來對佈局中的每一個View進行一層包裹,當加載數據時則根據View來繪製骨架,不然顯示正常UI。因爲該方案須要將每一個View包裹一層,因此會增長額外的佈局層次。具體實現有Skeleton Android等開源庫。

 上面就是目前在Android上實現骨架屏的兩種方案,下面以SkeletonSkeleton Android爲例進行講解。app

Skeleton

 要想使用Skeleton,須要先導入如下兩個庫。框架

dependencies {
      implementation 'com.ethanhua:skeleton:1.1.2'
      //主要是動畫的實現
      implementation 'io.supercharge:shimmerlayout:2.1.0'
}
複製代碼

skeleton不只支持在RecyclerView上實現骨架屏,也支持在View上實現骨架屏。  先來看看在RecyclerView上的實現。maven

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。下面就來看看這兩個方法的實現。ide

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;
        }
    }
    ...
}
複製代碼

 實現效果以下。

在這裏插入圖片描述
 從上面能夠看出,在 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 Android的使用,它的原理基本上就是經過 SkeletonGroupSkeletonView這兩個控件來進行骨架的繪製。 SkeletonGroupSkeletonView都是繼承自 RelativeLayout的自定義控件, SkeletonView起一個標識的做用,在 SkeletonGroup中會將 SkeletonView繪製成相應的長方形、圓形等骨架。

總結

 前面介紹了骨架屏在Android上的應用。它們的區別主要是需不須要本身來實現骨架屏佈局。可是從使用上來講Skeleton要比Skeleton Android方便不少,擴展性也更好一點。固然咱們也能夠根據這兩種方案的思想來本身實現骨架屏。

客戶端骨架屏詳解

相關文章
相關標籤/搜索