使用RecyclerView簡單快捷地擼一個直播公屏出來

特雷西多士發表於2018-08-02

前言

雖然現在直播已經沒有了當前那麼火爆,但是仍然是很多App盈利收入的一個重要功能,像現在的網易新聞客戶端、抖音短視訊等都有引入直播這個功能,而公屏是直播的一個重要工具,所以我們瞭解一下公屏的實現也是有點必要的,公屏的實現可以有挺多做法的,但是就目前來講,我認為比較快捷的方式就是使用RecyclerView來實現,僅此獻上拙見。

功能

公屏最簡單的就是通過一個可滑動的列表進行展示使用者傳送出來的訊息,當然,一般都是通過伺服器給客戶端推送單條或者一組資料,然後客戶端再把新來的資料新增到原有的資料來源上,再重新整理列表。當然,我們可以給公屏支援多樣式的訊息,使公屏看起來更加豐富;其次,我們可以把資料來源增加大小的限制,防止資料池過大導致記憶體暴增;再之,可以給資料來源增加快取功能,把收集回來的資料放到快取池中,然後定時把快取池更新到公屏上,避免對公屏過於頻繁的繪製。

滿足以上功能,基本上就可以成為一個及格的公屏了,本文也是基於以上幾個基本的功能點進行分析,然後程式碼實現,其中難免會有不合理的地方,希望大家指出。

1.插入資料

支援插入單條或者多條資料,並且插入後在公屏中展示出來。

image

2.支援多樣式公屏

如上圖所示,目前有四種樣式的公屏,第一種就是系統通知(第一條綠色的直播規則),第二種就是普通的聊天訊息,第三種就是送禮訊息,第四種就是活動通知(粉紅色的公屏),當然,如果需要更多種,都是可以繼續增添的。

3.支援公屏大小限制

如下圖所示,我限制了公屏數量是100條,當我繼續傳送公屏時,資料來源的舊資料就會被擦除掉,然後新的資料才會放到資料池裡面。這樣做可以避免一些熱門房間的刷公屏導致的記憶體過高的問題。

image

4.快取池

這裡加了快取池,大概400ms才會進行資料重新整理,所以400ms內進行的插入操作都會先放到快取池裡面,然後時間到了之後才會對UI重新整理,這樣就可以避免UI的頻繁繪製導致GPU過高的問題。

實現

首先佈局檔案就是一個RecyclerView,以上實現的功能都是對RecyclerView的API進行呼叫罷了,我就不進行細講,就主要的功能點進行分析。

1.插入資料

插入資料就是在RecyclerView的Adapter對資料來源進行增加,然後notify即可。當然,RecyclerView是支援區域性更新的,為了效能問題,我們在插入資料之後不需要重新整理整個資料來源,而是隻更新新增的資料即可。

如果只是插入一條,可以呼叫

notifyItemInserted(getItemCount());
複製程式碼

如果是插入多條,可以呼叫

notifyItemRangeInserted(startPos, addSize);
複製程式碼

OK,那插入資料之後,我們就可以呼叫RecyclerView的smoothScrollToPosition(int position)方法,讓資料滾到到你需要的位置,這裡我們是滾到到最底部,即:

// 獲取底部index
int bottomIndex = mAdapter.getItemCount() - 1;
mChatView.smoothScrollToPosition(bottomIndex);
複製程式碼

2.多樣式的公屏

相信你對RecyclerView的getItemType是比較瞭解的,我們就可以根據訊息的不同型別載入不同的layout檔案,例如:

@Override
public BaseChatViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    switch (viewType) {
        case MyChatMsg.TYPE_NORMAL_TEXT:
            return new NormalChatHolder(LayoutInflater.from(AppUtils.getContext()).inflate(R.layout.layout_normal_text, parent, false));
        case MyChatMsg.TYPE_SYSTEM_NEWS:
            return new SystemNewsHolder(LayoutInflater.from(AppUtils.getContext()).inflate(R.layout.layout_system_news_text, parent, false));
        case MyChatMsg.TYPE_GIFT_MSG:
            return new GiftNewsHolder(LayoutInflater.from(AppUtils.getContext()).inflate(R.layout.layout_gift_text, parent, false));
        default:
            return new HeaderChatHolder(LayoutInflater.from(AppUtils.getContext()).inflate(R.layout.layout_header_text, parent, false));
    }
}
複製程式碼

然後,我們就可以在不同的layout裡面進行不同View的定製。由於我們上圖中的UI都是比較簡單,所以我們的layout都是一個TextView即可滿足,配合SpannableStringBuilder就可以做一些富文字的展示。

3.限制公屏大小

這個也是比較簡單,只要在我們插入資料之後,比較資料來源的大小和我們限制的大小,然後把超出的部分移除掉,然後再更新UI即可,例如:

private void removeOverItems() {
    //獲取資料來源大小
    int dataSize = getDataSize();
    //獲取定義的最大限制,如100
    int mMaxChatNum = DEFAULT_MAX_CHAT_NUM;
    if (dataSize > mMaxChatNum) {
        // 計算超出的大小
        int beyondSize = dataSize - mMaxChatNum;
        // 資料來源把舊的資料來源超出的部分移除
        mDatas.subList(0, beyondSize).clear();
        // 更新UI
        notifyItemRangeRemoved(1, beyondSize);
    }
}
複製程式碼

4.快取池

快取池其實就是自定義一個Runnable,然後在run()方法內,對Adapter的資料來源變更,然後notify一下,這個Runnable是定時重複執行,例如400ms,避免了每插入一條資料就對Adapter進行notify造成的頻繁繪製。詳細程式碼請看原始碼。

原始碼

大概功能介紹完成,如果有興趣深入瞭解,可以移步到SimpleChatView,如果你覺得還OK,可以給個Star支援一下。

相關文章