listenzz / AndroidNavigation

A library managing navigation, nested Fragment, StatusBar, Toolbar for Android
MIT License
717 stars 95 forks source link

关于NavigationFragmen容器中子Fragment布局使用ViewBinding加载后出现转换异常 #52

Closed Fomovet closed 3 years ago

Fomovet commented 3 years ago

测试版本:最新

问题描述:当NavigationFragmen容器中子Fragment布局使用ViewBinding加载后出现转换异常,如果把XML布局文件中的根布 局LinearLayoutCompat换成FragmentLayout那么ViewBinding的写法可以使用。

复现代码:

MainActivity

public class MainActivity extends AwesomeActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null) {
            NavigationFragment navigationFragment = new NavigationFragment();
            navigationFragmentOne.setRootFragment(new TestFragment());
            setActivityRootFragment(navigationFragment);
        }
    }

}

TestFragment

public class TestFragment extends AwesomeFragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        //如果把XML布局文件中的根布局LinearLayoutCompat换成FragmentLayout那么ViewBinding的写法可以使用
        //报错写法一:FragmentTestBinding.inflate(inflater,container,false).getRoot()
        //报错写法二:FragmentTestBinding.inflate(getLayoutInflater()).getRoot()
        //不报错写法:inflater.inflate(R.layout.fragment_test, container, false)
        return inflater.inflate(R.layout.framgent_test, container, false);
    }
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/mText"
        android:text="点击跳转"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</androidx.appcompat.widget.LinearLayoutCompat>

异常信息

Caused by: java.lang.ClassCastException: android.widget.FrameLayout cannot be cast to androidx.appcompat.widget.LinearLayoutCompat
        at com.example.myapplication.databinding.FramgentTestOneBinding.bind(FramgentTestOneBinding.java:63)
        at com.example.myapplication.databinding.FramgentTestOneBinding.inflate(FramgentTestOneBinding.java:48)
        at com.example.myapplication.databinding.FramgentTestOneBinding.inflate(FramgentTestOneBinding.java:38)
        at com.example.myapplication.TestFragment.onCreateView(TestFragment.java:29)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2963)
        at androidx.fragment.app.DialogFragment.performCreateView(DialogFragment.java:489)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:518)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2106)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
        at androidx.fragment.app.FragmentManager.executePendingTransactions(FragmentManager.java:600)
        at com.navigation.androidx.FragmentHelper.executePendingTransactionsSafe(FragmentHelper.java:39)
        at com.navigation.androidx.FragmentHelper.addFragmentToBackStack(FragmentHelper.java:64)
        at com.navigation.androidx.NavigationFragment.setRootFragmentSync(NavigationFragment.java:56)
        at com.navigation.androidx.NavigationFragment.onActivityCreated(NavigationFragment.java:49)
        at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2996)
        at androidx.fragment.app.FragmentStateManager.activityCreated(FragmentStateManager.java:580)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:285)
        at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1647)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3128)
        at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:3072)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:251)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:502)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1340)
        at android.app.Activity.performStart(Activity.java:7364)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3281)
listenzz commented 3 years ago

请参考

https://github.com/listenzz/AndroidNavigation/blob/master/demo/src/main/java/com/navigation/dialog/DataBindingDialogFragment.java

这是由于 https://github.com/listenzz/AndroidNavigation/blob/master/navigation/src/main/java/com/navigation/androidx/AwesomeFragment.java#L118

的原因

Fomovet commented 3 years ago

我做了很多测试,如果使用inflater.inflate()的方式返回View,经过父类AwesomeFragment中的onGetLayoutInflater()方法处理,可以把View添加到Fragment中,但如果使用FragmentTestBinding.inflate(inflater,container,false).getRoot()的方式返回View就无法处理,这里我十分不解,因为我查看FragmentTestBinding的源码实际上也是通过inflater.inflate()方法加载遍历,最后返回布局根节点作为View。

上面提供的参考连接它也是通过inflater.inflate()加载布局,如果使用ViewBinding应该如何加载呢?

listenzz commented 3 years ago

@Fomovet 你好,经过我测试,可以这么解决

FragmentTestBinding testBinding;

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    ViewGroup root = (ViewGroup)inflater.inflate(R.layout.framgent_test, container, false);
    testBinding = FragmentTestBinding.bind(root.getChildAt(0));
    return root;
}
listenzz commented 3 years ago

这是因为我在 xml 布局外面包裹了一个 FrameLayout

https://github.com/listenzz/AndroidNavigation/blob/master/navigation/src/main/java/com/navigation/androidx/NavigationLayoutInflater.java#L31

Fomovet commented 3 years ago

因为我需要使用反射封装BaseFragment所以无法使用inflater.inflate(R.layout.framgent_test, container, false);方式返回View,这是我为什么执着于使用ViewBinding返回View的原因.

BaseFragment

public abstract class BaseFragment<T extends ViewBinding> extends AwesomeFragment {
    protected T mBinding;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,  ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        mBinding = ViewBindingUtil.create(getClass(),inflater.from(getActivity()),container,false);
        return mBinding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull @NotNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initEventAndData();
    }

    protected abstract void initEventAndData();

}

TestFragment继承BaseFragment

public class TestFragment extends BaseFragment<FramgentTestOneBinding> {

    @Override
    protected void initEventAndData() {
        mBinding.mText.setText("");
    }
}

目前我的解决办法是返回View的时候再添加一个FrameLayout:

public abstract class BaseFragment<T extends ViewBinding> extends AwesomeFragment {
    protected T mBinding;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,  ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        mBinding = ViewBindingUtil.create(getClass(),inflater.from(getActivity()),container,false);
        FrameLayout frameLayout = new FrameLayout(getActivity());
        frameLayout.addView(mBinding.getRoot());
        return frameLayout;
    }

    @Override
    public void onViewCreated(@NonNull @NotNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initEventAndData();
    }

    protected abstract void initEventAndData();

}
listenzz commented 3 years ago

@Fomovet 不错的办法