1. 基礎(chǔ)使用
1.1 靜態(tài)使用
<fragment> </fragment> 標(biāo)簽,,name屬性:指定package.name:表示加載對(duì)應(yīng)的fragment
1.2 動(dòng)態(tài)使用

1.3 v4包下fragment的使用
v4包向下兼容,兼容android3.0以前的fragment

1.4 生命周期

總結(jié)
- 創(chuàng)建階段:從下往上,即:activity聲明周期在前
- 切換階段:被動(dòng)先行,即:fragment間切換時(shí)候,要隱藏的先行,要顯示的后行。。也可以形象的理解:坑位就一個(gè),只有先離開,其他才能進(jìn)來
- 前后臺(tái)/銷毀:從上往下,即:activity在下,fragment在上,activity相當(dāng)于一個(gè)容易,fragment在視圖層上層,先從上層銷毀。也可以理解:容器先死了,孩子誰(shuí)處理
2. fragment的通信
參考ios的思想:
正向傳值:用屬性傳值,也就是用target的暴露方法
反向傳值:用代理或block或通知
2.1 activity 向 fragment傳值
- fragment未創(chuàng)建時(shí)候
ac: fg.setArguments(bundle)
fg: getArguments()
- fragment已經(jīng)存在
方法一:
fragment暴露public方法給ac調(diào)用
方法二:被動(dòng)方案,即fragment主動(dòng)從ac拿數(shù)據(jù)
1:接口方案。fg持有接口,調(diào)用接口方法,ac實(shí)現(xiàn)接口方法,return 數(shù)據(jù)。
2:ac暴露方法
方法三:
無敵方案:EventBus
2.2 fragment 向 activity傳值
方法一:
ac暴露方法給fg調(diào)用
方法二:
接口方案:同ac向fg傳值。。反向傳值,更適合
方法三:
無敵方案:EventBus。。反向傳值,更適合
2.3 fragment間通信
方法一:暴露方法
方法二:接口
方法三:EventBus
3. 進(jìn)階
3.1 fg 頁(yè)面跳轉(zhuǎn)
fg中startActivityForResult,onActivityResult能接收回調(diào)
3.2 fg 嵌套
正確選擇是使用getFragmentManager()還是getChildFragmentManager()
對(duì)于宿主Activity,getSupportFragmentManager()獲取的FragmentActivity的FragmentManager對(duì)象;
對(duì)于Fragment,getFragmentManager()是獲取的是父Fragment(如果沒有,則是FragmentActivity)的FragmentManager對(duì)象,而getChildFragmentManager()是獲取自己的FragmentManager對(duì)象
3.3 fg 恢復(fù)及重疊問題
善用findFragmentByTag獲取到恢復(fù)的fg,然后結(jié)合hide和show控制狀態(tài)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
TargetFragment targetFragment;
HideFragment hideFragment;
if (savedInstanceState != null) { // “內(nèi)存重啟”時(shí)調(diào)用
//注意:其實(shí)ac銷毀并恢復(fù)時(shí),fg是被保存到savedInstanceState的,所以,如果不為空,就不用重新建fg,onSaveInstanceState已經(jīng)保存了fg
targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName);
hideFragment = getSupportFragmentManager().findFragmentByTag(HideFragment.class.getName);
// 解決重疊問題
getFragmentManager().beginTransaction()
.show(targetFragment)
.hide(hideFragment)
.commit();
}else{ // 正常時(shí)
targetFragment = TargetFragment.newInstance();
hideFragment = HideFragment.newInstance();
getFragmentManager().beginTransaction()
.add(R.id.container, targetFragment, targetFragment.getClass().getName())
.add(R.id,container,hideFragment,hideFragment.getClass().getName())
.hide(hideFragment)
.commit();
}
}
3.4 Fragment 回收和恢復(fù)問題
場(chǎng)景或原因:內(nèi)存吃緊被回收,或屏幕旋轉(zhuǎn)導(dǎo)致的重建
fg是有宿主的,回收和恢復(fù)跟ac也是有關(guān)聯(lián)的?;厥諘r(shí)候,onSaveInstanceState保存了fg及一些其他狀態(tài)?;謴?fù)時(shí)候,需要判斷savedInstanceState是否為空,如果不為空,說明保存的有之前的fg,不用重新創(chuàng)建,fg內(nèi),系統(tǒng)會(huì)默認(rèn)使用fragment的無參構(gòu)造方法創(chuàng)建fragment,所以要恢復(fù)fg的參數(shù),需要重新走解析mArguments參數(shù)的流程
onSaveInstanceState方法里會(huì)將我當(dāng)前Activity內(nèi)的Fragment存入HashMap中再存到Bundle對(duì)象中,然后當(dāng)我Activity被銷毀后返回回來時(shí),系統(tǒng)會(huì)從onCreate方法和onRestoreInstanceState方法中將我們之前存入的Fragment取出來進(jìn)行顯示

方案:newInstance方案創(chuàng)建fragment
解析:創(chuàng)建fg時(shí)候setArguments傳參,回收時(shí)候ac保存fg及mArguments等,恢復(fù)時(shí)候才能重新獲取mArguments
public static OneFragment newInstance(Class param){
OneFragment oneFragment = new OneFragment();
Bundle bundle = new Bundle();
bundle.putInt("someArgs", param);
oneFragment.setArguments(bundle);
return oneFragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState==null) {
oneFragment = OneFragment.newInstance();
ft.add(R.id.fl_content, oneFragment, "OneFragment");
}else {
oneFragment = (OneFragment) getSupportFragmentManager().findFragmentByTag("OneFragment");
}
//重建的時(shí)候,可以從保存的Arguments中重新獲取初始化參數(shù)
Bundle bundle = getArguments();
}
3.5 ViewPager + Fragment 方案
使用FragmentPagerAdapter+ViewPager時(shí),切換回上一個(gè)Fragment頁(yè)面時(shí)(已經(jīng)初始化完畢),不會(huì)回調(diào)任何生命周期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)會(huì)被回調(diào)。。另外,setUserVisibleHint(boolean isVisibleToUser) 方法總是會(huì)優(yōu)先于 Fragment 生命周期函數(shù)的調(diào)用,所以如果你想進(jìn)行一些懶加載,需要在這里處理。
在給ViewPager綁定FragmentPagerAdapter時(shí),
new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保證正確,如果ViewPager是Activity內(nèi)的控件,則傳遞getSupportFragmentManager(),如果是Fragment的控件中,則應(yīng)該傳遞getChildFragmentManager()。只要記住ViewPager內(nèi)的Fragments是當(dāng)前組件的子Fragment這個(gè)原則即可。
3.6 ~PagerAdapter 注意點(diǎn)
FragmentPagerAdapter與FragmentStatePagerAdapter有什么區(qū)別
主要區(qū)別就在與對(duì)于fragment是否銷毀,下面細(xì)說:
FragmentPagerAdapter:對(duì)于不再需要的fragment,選擇調(diào)用detach方法,僅銷毀視圖,并不會(huì)銷毀fragment實(shí)例。
FragmentStatePagerAdapter:會(huì)銷毀不再需要的fragment,當(dāng)當(dāng)前事務(wù)提交以后,會(huì)徹底的將fragment從當(dāng)前Activity的FragmentManager中移除,state標(biāo)明,銷毀時(shí),會(huì)將其onSaveInstanceState(Bundle outState)中的bundle信息保存下來,當(dāng)用戶切換回來,可以通過該bundle恢復(fù)生成新的fragment,也就是說,你可以在onSaveInstanceState(Bundle outState)方法中保存一些數(shù)據(jù),在onCreate中進(jìn)行恢復(fù)創(chuàng)建。
如上所說,使用FragmentStatePagerAdapter當(dāng)然更省內(nèi)存,但是銷毀新建也是需要時(shí)間的。一般情況下,如果你是制作主頁(yè)面,就3、4個(gè)Tab,那么可以選擇使用FragmentPagerAdapter,如果你是用于ViewPager展示數(shù)量特別多的條目時(shí),那么建議使用FragmentStatePagerAdapter
另外,viewPager.setOffscreenPageLimit 這個(gè)離屏緩存,默認(rèn)是1,所以,即使用的FragmentPagerAdapter,也要設(shè)置這個(gè)參數(shù)。否則,也只會(huì)保存兩個(gè)fg存活
你不需要考慮在“內(nèi)存重啟”的情況下,去恢復(fù)的Fragments的問題,因?yàn)镕ragmentPagerAdapter已經(jīng)幫我們處理啦。
3.7 Fragment 懶加載方案
學(xué)習(xí)文章:Androidx 下 Fragment 懶加載的新實(shí)現(xiàn)
原文:http://www.itdecent.cn/p/2201a107d5b5
- add-show-hide 方案



- viewpager + fragment 方案




通過上述兩種方案fg的生命周期,可以形成下面的懶加載方案
abstract class LazyFragment extends Fragment {
//是否執(zhí)行了懶加載
private boolean isLoaded = false;
/**
* 當(dāng)前Fragment是否對(duì)用戶可見
*/
private boolean isVisibleToUser = false;
/**
* 當(dāng)使用ViewPager+Fragment形式會(huì)調(diào)用該方法時(shí),setUserVisibleHint會(huì)優(yōu)先Fragment生命周期函數(shù)調(diào)用,
* 所以這個(gè)時(shí)候就,會(huì)導(dǎo)致在setUserVisibleHint方法執(zhí)行時(shí)就執(zhí)行了懶加載,
* 而不是在onResume方法實(shí)際調(diào)用的時(shí)候執(zhí)行懶加載。所以需要這個(gè)變量
*/
private boolean isCalledResume = false;
/**
* 是否調(diào)用了setUserVisibleHint方法。處理show+add+hide模式下,默認(rèn)可見 Fragment 不調(diào)用
* onHiddenChanged 方法,進(jìn)而不執(zhí)行懶加載方法的問題。
*/
private boolean isCallUserVisibleHint = false;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
this.isCallUserVisibleHint = true;
judgeLazyInit();
}
@Override
public void onResume() {
super.onResume();
this.isCalledResume = true;
if (!isCallUserVisibleHint) {
//處理這個(gè),是因?yàn)閍dd-show-hide模式管理fg方案下,不走setUserVisibleHint,并且第一個(gè)默認(rèn)顯示的fg不回調(diào)onHiddenChanged
// 所以表示:不走setUserVisibleHint的就是走onHiddenChanged的add-show-hide方案
// 但是默認(rèn)第一個(gè)顯示的fg不走onHiddenChanged方案,所以才有了這個(gè)onresume中的判斷邏輯
this.isVisibleToUser = !isHidden();
}
judgeLazyInit();
}
@Override
public void onDestroyView() {
super.onDestroyView();
this.isLoaded = false;
this.isCalledResume = false;
this.isVisibleToUser = false;
this.isCallUserVisibleHint = false;
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
// add-show-hide 方案才會(huì)回調(diào)這個(gè)方法
// 把字段歸并給回調(diào)setUserVisibleHint的情況。
this.isVisibleToUser = !hidden;
judgeLazyInit();
}
private void judgeLazyInit() {
//isVisibleToUser 是表示肉眼可見,是包括了兩種情況,add-show-hide方案的情況,和vp+fg的方案
if (!this.isLoaded && this.isVisibleToUser && this.isCalledResume) {
lazyInit();
this.isLoaded = true;
}
}
abstract void lazyInit();
}
分析理解:
- viewpager + fg的方案,所有fg的setUserVisibleHint是先于其他生命周期運(yùn)行的。
- 但是只有當(dāng)fg顯示或在預(yù)緩存限制內(nèi)的fg才會(huì)走fg的生命周期。
- 只有當(dāng)isVisibleToUser為true(表示當(dāng)期窗口顯示的fg,肉眼可見的) 并且 已經(jīng)onresume的(比如第二個(gè)第三個(gè)等:isVisibleToUser為true是在onresume之后的)觸發(fā)懶加載。
- 所以綜合來看,兩個(gè)回調(diào)都有可能要觸發(fā)加載,所以,才有了lazyload的判斷條件。
- 另外,兼容add-show-hide方案,其實(shí)onHiddenChanged和setUserVisibleHint對(duì)應(yīng)是兩種方案的兩種回調(diào),上面把標(biāo)記字段統(tǒng)一為了isVisibleToUser。
- add-show-hide方案中,默認(rèn)顯示的第一個(gè)fg onHiddenChanged不回調(diào)
3.8 androidx中viewpager的setMaxLifecycle 懶加載方案
學(xué)習(xí)文章:Androidx 下 Fragment 懶加載的新實(shí)現(xiàn)
原文:http://www.itdecent.cn/p/2201a107d5b5
androidx中,F(xiàn)ragmentPagerAdapter構(gòu)造方法多個(gè)@Behavior int behavior參數(shù):
- BEHAVIOR_SET_USER_VISIBLE_HINT:跟原Support下的ViewPager一致
- BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:表示滑動(dòng)切換fragment時(shí)候,只有顯示可見的fragment才回調(diào)onresume,而不再像上一節(jié)方案中fragment的生命周期那么不友好
既然只有當(dāng)顯示的fragment才回調(diào)onresume,那么懶加載方案就簡(jiǎn)單了:
abstract class LazyFragment : Fragment() {
private var isLoaded = false
override fun onResume() {
super.onResume()
if (!isLoaded) {
lazyInit()
Log.d(TAG, "lazyInit:!!!!!!!”)
isLoaded = true
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
abstract fun lazyInit()
}
但是針對(duì)fragment嵌套的情況,上述方案并不完美,因?yàn)閒ragment嵌套情況下,并不是只有顯示的fragment才調(diào)用onresume。。

所以,針對(duì)嵌套的子fragment,增加isHidden的判斷
abstract class LazyFragment : Fragment() {
private var isLoaded = false
override fun onResume() {
super.onResume()
//增加了Fragment是否可見的判斷
if (!isLoaded && !isHidden) {
lazyInit()
Log.d(TAG, "lazyInit:!!!!!!!”)
isLoaded = true
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
abstract fun lazyInit()
}
3.9 ViewPager2 懶加載方案
上面兩種懶加載方案都涉及到Fragment生命周期的棘手問題。特別是onResume周期函數(shù),用慣了activity的我們,F(xiàn)ragment的onResume使用就很不舒服,
ViewPager2 本身就支持對(duì)實(shí)際可見的 Fragment 才調(diào)用 onResume 方法
3.10 回退棧相關(guān)
原文:https://blog.csdn.net/guxiao1201/article/details/40476267
首先需要明確的是,F(xiàn)ragmentActivity的FragmentManager是處理Fragment Transaction的而不是處理Fragment。BackStack內(nèi)部的一個(gè)Transaction可以包含一個(gè)或多個(gè)和Fragment相關(guān)的操作
FragmentTransaction默認(rèn)并不會(huì)主動(dòng)被加入到BackStack中,除非開發(fā)者調(diào)用了addToBackStack(String tag)方法
和addToBackStack相對(duì)應(yīng)的接口方法是popBackStack(),調(diào)用該方法后會(huì)將事物操作插入到FragmentManager的操作隊(duì)列,只有當(dāng)輪詢到該事物時(shí)才能執(zhí)行。所以Google還提供了可以立刻執(zhí)行的接口popBackStackImmediate()
addToBackStack源碼里是將事務(wù)加入到BackStackRecord,表示加入維護(hù)回退棧記錄而已,并不是將事務(wù)操作里的fragment加入到回退棧之類的
理解分析: FragmentManager操作單元是Transaction,可以理解為FragmentManager每次操作是從beginTransaction到commit的所有動(dòng)作,addToBackStack的時(shí)候添加上tag以標(biāo)記這次操作,比如addToBackStack(TagA), addToBackStack(TagB)。表示事務(wù)棧內(nèi)現(xiàn)在操作了兩波,TagA和TagB標(biāo)記的。然后點(diǎn)擊返回鍵,攔截到回退棧里不為空,就popBackStack,彈出去一個(gè)事務(wù)操作,剩下TagA事務(wù)操作結(jié)果。效果就等于說現(xiàn)在頁(yè)面效果是TagA操作后的結(jié)果。。例如:add(FragmentA),addToBackstack(TagA),add(FragmentB),addToBackstack(TagB)。這時(shí)候fragment先顯示的FragmentA,然后顯示的FragmentB。popBackStack(),就是把TagB的操作反向處理,TagB事務(wù)標(biāo)記的是添加FragmentB,反向操作就是移除FragmentB,剩下TagA的操作。界面顯示的是FragmentA
注意API:
popBackStack() 是從事務(wù)棧彈出頂層事務(wù)
popBackStack(String tag) 是從事務(wù)棧彈出tag及以上的所有事務(wù)
-
popBackStack(String tag,? int flag) 是從事務(wù)棧彈出tag以上的所有事務(wù),是否彈出tag事務(wù),取決于flag
- flag:POP_BACK_STACK_INCLUSIVE 就是表示tag事務(wù)一塊彈出
- 一般傳0,表示不包括
public static final int POP_BACK_STACK_INCLUSIVE = 1<<0; (flags & POP_BACK_STACK_INCLUSIVE) == 0 popBackStack(int id,? int flag) 同上原理,事務(wù)commit的時(shí)候會(huì)返回個(gè)id
以上方法是將事務(wù)彈出放到事件loop中等著處理,還有立即處理的方法,同上,~Immediate()
深入分析:
FragmentManager, BackStackRecord 源碼還沒有理清楚。。大致就是addBackStack是回退棧一條BackStackRecord記錄。。該記錄維護(hù)的有一個(gè)操作OP列表,大致包含操作命令cmd,fragment,一些進(jìn)出動(dòng)畫。。重點(diǎn)是執(zhí)行命令和fragment,有這倆,結(jié)合當(dāng)前FragmentManager維護(hù)的現(xiàn)有fragments,就能反向操作了。比如addBackStack(“replaceOper”),replace操作就是把老的移除,把新的添加,popBackStack的時(shí)候,找到FragmentManager中棧頂?shù)腸urruntFragment,在回退棧記錄里的op里拿到oldFragment,cmd。先把curruntFragment移除,再把oldFragment添加,也就是反向replace
3.11 宿主引用問題
比如fg內(nèi)網(wǎng)絡(luò)請(qǐng)求的場(chǎng)景,異步任務(wù),執(zhí)行完成后回調(diào),這時(shí)候宿主可能自己活宿主已經(jīng)銷毀了。所以,異步回調(diào)處的處理,要考慮處理自己和宿主是否可用的情況
方案:onAttach中保存ac對(duì)象。。Activity ac = (Activity)context
3.12 commit和commitAllowingStateLoss ~ commitNow和commitNowAllowingStateLoss
原文:https://blog.csdn.net/zxq614/article/details/85785297
這兩種提交事務(wù)的區(qū)別就在于是否安全的問題:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
...
看源碼:
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
……
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
……
mManager.enqueueAction(this, allowStateLoss);
……
}
……
}
public abstract class FragmentManager {
……
boolean mStateSaved;
……
public void enqueueAction(Runnable action, boolean allowStateLoss) {
// 1: 不允許頁(yè)面關(guān)閉后還執(zhí)行action的,進(jìn)行狀態(tài)檢測(cè)
if (!allowStateLoss) {
checkStateLoss();
}
}
private void checkStateLoss() {
if (mStateSaved) {
//3:頁(yè)面已關(guān),再提交,就拋異常
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
}
……
Parcelable saveAllState() {
……
if (HONEYCOMB) {
mStateSaved = true;
}
……
}
……
public void noteStateNotSaved() {
mStateSaved = false;
}
public void dispatchCreate() {
mStateSaved = false;
……
}
public void dispatchActivityCreated() {
mStateSaved = false;
……
}
public void dispatchStart() {
mStateSaved = false;
……
}
public void dispatchResume() {
mStateSaved = false;
……
}
……
public void dispatchStop() {
//2:這里標(biāo)記頁(yè)面關(guān)閉,保存狀態(tài)了
mStateSaved = true;
……
}
……
}
注意:
- 使用commit(),異步業(yè)務(wù)中,可能拋上面的異常
- 使用commitAllowingStateLoss(),雖然避免了異常問題,但是activity恢復(fù)時(shí),提交的事務(wù)是丟失的。所以,提交的事務(wù)對(duì)應(yīng)的操作是恢復(fù)不了的
- 谷歌推薦使用:commitNow以及commitNowAllowingstateLoss()
3.13 replace的Fragment如何保持狀態(tài)
調(diào)用addFragment添加的fragment的View會(huì)保存到視圖樹(ViewTree)中,其中各個(gè)控件的狀態(tài)都會(huì)被保存。但如果調(diào)用replace()來添加fragment,我們前面講到過,replace()的實(shí)現(xiàn)是將同一個(gè)container中的所有fragment視圖從ViewTree中全部清空!然后再添加指定的fragment。由于repalce操作會(huì)把以前的所有視圖全部清空,所以當(dāng)使用Transaction回退時(shí),也就只有重建每一個(gè)fragment視圖,所以就導(dǎo)致從replace操作回退回來,所有的控件都被重建,以前的用戶輸入全部沒了。
到這里,大家首先要明白一個(gè)問題,repalce()操作,會(huì)清空同一個(gè)container中的所有fragment視圖!注意用詞:請(qǐng)空的是fragment的VIEW!fragment的實(shí)例并不會(huì)被銷毀!因?yàn)閒ragment的實(shí)例是通過FragmentManager來管理的。當(dāng)fragment的VIEW被銷毀時(shí),fragment實(shí)例并不會(huì)被銷毀。他們兩個(gè)不是同時(shí)的,即在fragment中定義的變量,所上次運(yùn)行中被賦予的值是一直存在的。那fragment實(shí)例什么時(shí)候會(huì)被銷毀呢,當(dāng)然是在不會(huì)被用到的時(shí)候才會(huì)被銷毀。那什么時(shí)候不會(huì)被用到呢,即不可能再回退到這個(gè)操作的時(shí)候,就會(huì)被銷毀。
在上面的例子中,fragment1雖然被fragment2的repalce操作把它的視圖給銷毀了,但在執(zhí)行replace操作時(shí),將操作加入到了回退棧,這時(shí)候,F(xiàn)ragmentManager就知道,用戶還可能通過回退再次用到fragment1,所以就會(huì)保留fragment1的實(shí)例。相反,如果,在執(zhí)行repalce操作時(shí),沒有加入到回退棧,那FragmentManager就肯定也知道,用戶不可能再回到上次那個(gè)Fragment1界面了,所以它的fragment實(shí)例就會(huì)在清除fragment1視圖的同時(shí)也被清除了
解決方案:
方案都是基于replace操作加入到回退棧的
- 方案一
保存與恢復(fù)需要保存的狀態(tài):既然fragment實(shí)例沒有銷毀,銷毀的知識(shí)視圖,那么可以將狀態(tài)保存到屬性中啊,重新創(chuàng)建視圖的時(shí)候再恢復(fù)
- 方案二
給需要保持的狀態(tài)對(duì)應(yīng)的控件加上id。。
android:id="@+id/id_name"
- 方案三
保存FragmentView視圖
跟方案一差不多,就是將整個(gè)fragment的rootview保存到實(shí)例中
總結(jié):總之理解了replace的原理,結(jié)合回退棧的原理。。怎么保持?jǐn)?shù)據(jù)狀態(tài),要結(jié)合自己的業(yè)務(wù)需求,能實(shí)現(xiàn)的就是實(shí)現(xiàn),不能實(shí)現(xiàn)的也可以考慮其他的方案,比如不用replace,show-hide方案也可行啊。
4. app框架方案
項(xiàng)目中用的該方案,效果不錯(cuò)