主頁 > 移動端開發 > Android嵌套滑動分析與實踐

Android嵌套滑動分析與實踐

2021-02-20 11:27:55 移動端開發

文章目錄

    • 【一】傳統事件分發
      • 1.1 傳統事件分發流程
      • 1.2 傳統事件滑動沖突
        • 1.2.1外部攔截法
        • 1.2.2內部攔截法
      • 1.3 傳統嵌套滑動沖突
        • 1.3.1 ScrollView+ ListView嵌套沖突
        • 1.3.2 ScrollView+ ViewPager嵌套問題
    • 【二】 傳統事件分發 VS NestedScrolling
    • 【三】NestedScrolling嵌套滑動
      • 2.1 NestedScrollingChild
      • 2.2 NestedScrollingParent
      • 2.3 兩者之間的關系
      • 2.4 二個NestedScrolling 嵌套滑動例子

【一】傳統事件分發

1.1 傳統事件分發流程

Activity:

public boolean dispatchTouchEvent(MotionEvent event)

public boolean onTouchEvent(MotionEvent event) 

ViewGroup:

public boolean dispatchTouchEvent(MotionEvent event)

public boolean onInterceptTouchEvent(MotionEvent ev)

public boolean onTouchEvent(MotionEvent event) 

View:

public boolean dispatchTouchEvent(MotionEvent event)

public boolean onTouchEvent(MotionEvent event) 

偽代碼表示dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三者之間的關系:

public boolean dispatchTouchEvent (MotionEvent ev){
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent(ev);
    } else {
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

用圖來表示事件傳遞的程序:

事件傳遞.png

1.2 傳統事件滑動沖突

來看ViewGroup的分發(PS:本文中的原始碼是基于Android API 24分析的~):

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

------other code------

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}
------other code------
handled = super.dispatchTouchEvent(transformedEvent);
}

當ViewGroup不攔截事件并由ViewGroup的子View處理事件時,mFirstTouchTarget會被賦值并指向子View,上面的代碼可以分成下面幾種情況:
1、如果MotionEvent.ACTION_DOWN事件被ViewGroup攔截,那么mFirstTouchTarget==null,那么后續的ACTION_MOVE、ACTION_UP事件都不會往子View中傳遞了,而會走ViewGroup的onTouchEvent方法,

2、當mFirstTouchTarget != null時,即子View處理后續ACTION_MOVE、ACTION_UP事件時,子View中可以通過設定getParent().requestDisallowInterceptTouchEvent(true),該設定會影響上述代碼中的disallowIntercept變數,從而使ViewGroup不攔截事件,將事件傳遞到子View中去

下面分析一下當遇到滑動沖突的時候一般的解決方法:

1.2.1外部攔截法

外部攔截法即在父ViewGroup的onInterceptTouchEvent中去做處理,偽代碼如下:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean isIntercept = false;
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            isIntercept = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if (ViewGroup攔截ACTION_MOVE條件) {
                isIntercept = true;
            } else {
                isIntercept = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            isIntercept = false;
            break;
    }
    return isIntercept;
}

這里注意除非父ViewGroup要處理所有事件,否則一定不能攔截ACTION_DOWN,因為一旦攔截了ACTION_DOWN事件,后續的MOVE、UP事件將都不能傳遞到子View了

1.2.2內部攔截法

內部攔截法是指父ViewGroup不攔截任何事件,所有事件都傳遞到子View中,通過getParent().requestDisallowInterceptTouchEvent(boolean)來控制后續事件讓不讓父ViewGroup去攔截,來看requestDisallowInterceptTouchEvent的原始碼:

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        // We're already in this state, assume our ancestors are too
        return;
    }

    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // Pass it up to our parent
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

主要是修改標志位mGroupFlags,這個標志位也是在父ViewGroup的dispatchTouchEvent中控制是否走onInterceptTouchEvent()攔截方法的,disallowIntercept為true時,父ViewGroup不會再走onInterceptTouchEvent()攔截事件;反之會走onInterceptTouchEvent()方法,默認是false,

內部攔截法的使用舉例:

在子View的dispatchTouchEvent中:

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if (父ViewGroup需要攔截并處理事件) {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return super.dispatchTouchEvent(event);
}

在父ViewGroup的onInterceptTouchEvent中:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return ev.getAction() != MotionEvent.ACTION_DOWN;
}

內部攔截法同樣不能攔截ACTION_DOWN事件,否則事件不能傳遞到子View中,

1.3 傳統嵌套滑動沖突

在開始介紹滑動沖突之前,先介紹一下測量規格MeasureSpecMeasureSpec參與了View的測量程序,子View的MeasureSpec的創建是由父View的MeasureSpec和子View自身的LayoutParams共同決定的,MeasureSpec的組成:

MeasureSpec是一個32位的int值,高2位是specMode,低30位是specSize,如下:

public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

SpecMode是測量模式,SpecSize是在某種測量模式下的測量大小,SpecMode有三個值:

USPECIFIED: 父View對子View沒有任何限制,

EXACTLY: 父View已經檢測出View的精確大小,View的最終大小就是SpecSize指定的值,它對應于LayoutParams中的match_parent和具體數值這兩種模式,

AT_MOST:父View指定了一個可用大小SpecSize,View的大小不能大于這個值,具體是什么值要看不同View的具體實作,它對應于LayoutParams中的wrap_content,

1.3.1 ScrollView+ ListView嵌套沖突

默認ListView中的onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // Sets up mListPadding
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    -----其他代碼-----
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    if (heightMode == MeasureSpec.UNSPECIFIED) {
        heightSize = mListPadding.top + mListPadding.bottom +    childHeight + getVerticalFadingEdgeLength() * 2;
    }

    if (heightMode == MeasureSpec.AT_MOST) {
        heightSize = measureHeightOfChildren(widthMeasureSpec, 0,    NO_POSITION, heightSize, -1);
    }
 }

通過原始碼我們知道當測量模式是UNSPECIFIED時,高度只是一個item的高度(包括上下的padding);當測量模式是AT_MOST時,高度是所有item的高度,即整個listview的高度,通過測驗發現ScrollView和ListView嵌套使用時,傳給ListView的測量模式是UNSPECIFIED,所以只能顯示一個Item的高度,那怎么顯示整個ListView的高度呢?通過上面的分析我們已經知道答案了:重寫ListView的onMeasure方法!

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, heightSpec);
}

放棄通過父View及自身LayoutParams生成的MeasureSpec(specMode為UNSPECIFIED),重新生成一個specMode為AT_MOST的MeasureSpec即可,

1.3.2 ScrollView+ ViewPager嵌套問題

來看ViewPager的onMeasure原始碼:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // For simple implementation, our internal size is always 0.
    // We depend on the container to specify the layout size of
    // our view.  We can't really know what it is since we will be
    // adding and removing different arbitrary views and do not
    // want the layout to change as this happens.
    setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
            getDefaultSize(0, heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

通過分析原始碼我們知道:

測量模式specMode是UNSPECIFIED時,ViewPager的高度是0;
測量模式specMode是AT_MOSTEXACTLY時,ViewPager的高度直接取的父View傳入的值,

通過測驗發現當ScrollView和ViewPager嵌套使用時,測量模式specMode是UNSPECIFIED,所以默認高度是0.

【二】 傳統事件分發 VS NestedScrolling

  • 傳統事件分發:子View處理Touch事件時,父View可以進行攔截并在父View中處理,但是一旦父View進行攔截,后續事件都不會再往子View中傳遞了,
  • NestedScrolling:子View在滾動的時候,首先將dx、dy交給NestedScrollingParent進行消耗,剩余部分還給子View,

【三】NestedScrolling嵌套滑動

從Android5.0開始引入了NestedScrolling機制(5.0之前可以用Support V4包向前兼容),用來處理子View和父View嵌套滑動時的互動機制,子View一般是可以滑動的View并且需要實作 NestedScrollingChild 介面,父View需要實作NestedScrollingParent介面,

2.1 NestedScrollingChild

public interface NestedScrollingChild {
   
    public void setNestedScrollingEnabled(boolean enabled);

    public boolean isNestedScrollingEnabled();

    public boolean startNestedScroll(int axes);

    public void stopNestedScroll();

    public boolean hasNestedScrollingParent();

    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);

    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);

    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);

    public boolean dispatchNestedPreFling(float velocityX, float velocityY);
}

2.2 NestedScrollingParent

public interface NestedScrollingParent {
   
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);

    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);

    public void onStopNestedScroll(View target);

    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);

    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);

    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);

    public boolean onNestedPreFling(View target, float velocityX, float velocityY);

    public int getNestedScrollAxes();
}

2.3 兩者之間的關系

NestedScroll.png

一、startNestedScroll

首先子view需要開啟整個流程(內部主要是找到合適的能接受nestedScroll的parent),通知父View,我要和你配合處理TouchEvent

二、dispatchNestedPreScroll

在子View的onInterceptTouchEvent或者onTouch中(一般在 MontionEvent.ACTION_MOVE事件里),呼叫該方法通知父View滑動的距離,該方法的第三第四個引數回傳父view消費掉的 scroll長度和子View的表單偏移量,如果這個scroll沒有被消費完,則子view進行處理剩下的一些距離,由于表單進行了移動,如果你記錄了手指最后的位置,需要根據第四個引數offsetInWindow計算偏移量,才能保證下一次的touch事件的計算是正確的,
如果父view接受了它的滾動引數,進行了部分消費,則這個函式回傳true,否則為false,
這個函式一般在子view處理scroll前呼叫,

三、dispatchNestedScroll

向父view匯報滾動情況,包括子view消費的部分和子view沒有消費的部分,
如果父view接受了它的滾動引數,進行了部分消費,則這個函式回傳true,否則為false,
這個函式一般在子view處理scroll后呼叫,

四、stopNestedScroll

結束整個流程,

更詳細見:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0603/2990.html

2.4 二個NestedScrolling 嵌套滑動例子

直接見github吧:
https://github.com/crazyqiang/AndroidStudy

或者見鴻神的博客:
https://blog.csdn.net/lmj623565791/article/details/52204039

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/261406.html

標籤:其他

上一篇:與 Android 互動五:Gradle配置

下一篇:Android:O泡果奶——來場惡作劇

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more