java - 第二次调用 FragmentTransaction.replace() 时出现 Android Fragment 转换错误

标签 java android android-fragments classcastexception fragmenttransaction

我的 fragment_container 中有 3 种类型的 fragment ,在我的 classic_menu.xml 中用于我的 MainActivity.java。我从 fragment A 开始,然后通过使用 FragmentTransaction.replace(R.id.fragment_container, B) 的方法按下按钮转到 fragment B。当我希望使用相同的方法从 B 转到 fragment C 时,问题就来了。我在使用您在下面看到的内容时遇到了转换错误。 编辑 我通过使用 findFragmentByTag() 而不是 findFragmentById() 获得空指针。

这里是有问题的 fragment :

fragment A:

    import android.os.Bundle;
    import android.support.annotation.Nullable;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;

    import com.example.R;

    public class MainMenuFragment extends Fragment{

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle onSavedInstanceState){
        View view = inflater.inflate(R.layout.main_menu_fragment, container, false);
        return view;
        }
    }

fragment B:

    import android.os.Bundle;
    import android.support.annotation.Nullable;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;

    import com.example.R;

    public class ClassicMenuFragment extends Fragment{

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle onSavedInstanceState){
        View view = inflater.inflate(R.layout.classic_menu_fragment, container, false);
        return view;
        }

    }

fragment C:

    import android.os.Bundle;
    import android.support.annotation.Nullable;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;

    import com.example.MainActivity;
    import com.example.R;
    import com.example.widgets.TextViewPlus;

    public class OnePlayerFragment extends Fragment{

    private static TextViewPlus topScore;
    private static TextViewPlus bottomScore;

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle onSavedInstanceState){
        View view = inflater.inflate(R.layout.one_player_fragment, container, false);

        topScore = (TextViewPlus) view.findViewById(R.id.topPlayerScore1P);
        bottomScore = (TextViewPlus) view.findViewById(R.id.bottomPlayerScore1P);

        return view;
    }

    /**
     * Changes the text of certain textViewPlus objects based on the given score
     * @param view int value that determines which view to update
     * @param score value to set the text to
     */
    public void setScore(int view, int score){
        if(view == MainActivity.TOP_PLAYER_1P) topScore.setText("" + score);
        else if(view == MainActivity.BOTTOM_PLAYER) bottomScore.setText("" + score);
    }
}

正在使用的按钮:

// in main_menu_fragment.xml
<com.example.widgets.ButtonPlus
    android:id="@+id/classicB"
    style="@style/button"
    android:onClick="StartClassicMenu"/>

// in classic_menu_fragment.xml
<com.example.widgets.ButtonPlus
    android:id="@+id/onePlayerB"
    style="@style/button"
    android:onClick="StartGame"/>

MainActivity.java:

// cut a lot of stuff for brevity
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

import com.example.fragments.ClassicMenuFragment;
import com.example.fragments.MainMenuFragment;
import com.example.fragments.OnePlayerFragment;

public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.classic_menu);
    // cut more stuff



    theView = (GameView)findViewById(R.id.theView);         // get reference to the GameView
    // begin the game in Animation mode and pass this MainActivity to the GameView so it can be passed along
    theView.initiateGameThread(GameState.ANIMATION_MODE, this);
    theThread = theView.getThread();                        // get reference to the GameThread
    theGame = theThread.getGameState();                     // get reference to the GameState

    if (findViewById(R.id.fragment_container) != null) {
        if (savedInstanceState != null) return;
        MainMenuFragment mMenu = new MainMenuFragment();
        // Add the fragment to the 'fragment_container' FrameLayout
        getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, mMenu).commit();
    }
}

public void StartClassicMenu(View v){
    changeToFragment(new ClassicMenuFragment(), "ClassicMenu");
    inFragment = true;
}

public void StartGame(View v){
    switch (v.getId()){
    case R.id.onePlayerB:
        theGame.setMode(GameState.ONE_PLAYER_MODE);
        changeToFragment(new OnePlayerFragment(), "OnePlayer");
        Log.d("MainActivity", "StartGame() for 1P mode called");
        break;
    // other cases here but cut out

    theGame.reset();
}

// called from gamestate when views need to be updated
public void setViewScore(int view, int score){
    if(theGame.getMode() == GameState.ONE_PLAYER_MODE){
        Log.d("MainActivity", "setViewScore() for 1P mode called");
        OnePlayerFragment f = (OnePlayerFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
        if(f == null) Log.d("OnePlayerFragment", "null!!!");
        f.setScore(view, score);
    }
    // other cases cut out
}

/**
 * Handles creating and managing a uniform FragmentTransaction for the entire app
 * @param newFragment the new Fragment that will fade in, replacing whichever fragment was in use 
 */
public void changeToFragment(Fragment newFragment, String tag){
    Log.d("MainActivity", "changeToFragment() Called with tag \"" + tag + "\"");
    // Create the standard fade in/out transaction
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
    // Replace the old fragment in the Relative Layout view with the new one
    transaction.replace(R.id.fragment_container, newFragment, tag);
    transaction.commit(); // Commit the transaction
}

MainActivity 各自的 xml 布局文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/back_grey">

<com.example.GameView
    android:id="@+id/theView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:layout_gravity="center" />

<RelativeLayout
    android:id="@+id/rLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </FrameLayout>

</RelativeLayout>

现在是最精彩的部分:

07-18 16:30:01.999: D/OpenGLRenderer(12335): Enabling debug mode 0
07-18 16:30:02.259: D/GameView(12335): surfaceCreated() Called
07-18 16:30:02.649: I/Timeline(12335): Timeline: Activity_idle id: android.os.BinderProxy@1f1e5333 time:475861093
07-18 16:30:13.179: D/ViewRootImpl(12335): ViewPostImeInputStage ACTION_DOWN
07-18 16:30:13.389: D/MainActivity(12335): changeToFragment() Called with tag "ClassicMenu"
07-18 16:30:14.099: D/ViewRootImpl(12335): ViewPostImeInputStage ACTION_DOWN
07-18 16:30:14.169: D/MainActivity(12335): changeToFragment() Called with tag "OnePlayer"
07-18 16:30:14.169: D/MainActivity(12335): StartGame() for 1P mode called
07-18 16:30:14.179: D/MainActivity(12335): setViewScore() for 1P mode called
07-18 16:30:14.219: D/AndroidRuntime(12335): Shutting down VM
07-18 16:30:14.249: E/AndroidRuntime(12335): FATAL EXCEPTION: main
07-18 16:30:14.249: E/AndroidRuntime(12335): Process: com.brownapps.battlepong, PID: 12335
07-18 16:30:14.249: E/AndroidRuntime(12335): java.lang.IllegalStateException: Could not execute method of the activity
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.view.View$1.onClick(View.java:4222)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.view.View.performClick(View.java:5156)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.view.View$PerformClick.run(View.java:20755)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.os.Handler.handleCallback(Handler.java:739)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.os.Handler.dispatchMessage(Handler.java:95)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.os.Looper.loop(Looper.java:145)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.app.ActivityThread.main(ActivityThread.java:5835)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at java.lang.reflect.Method.invoke(Native Method)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at java.lang.reflect.Method.invoke(Method.java:372)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
07-18 16:30:14.249: E/AndroidRuntime(12335): Caused by: java.lang.reflect.InvocationTargetException
07-18 16:30:14.249: E/AndroidRuntime(12335):    at java.lang.reflect.Method.invoke(Native Method)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at java.lang.reflect.Method.invoke(Method.java:372)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.view.View$1.onClick(View.java:4217)
07-18 16:30:14.249: E/AndroidRuntime(12335):    ... 10 more
07-18 16:30:14.249: E/AndroidRuntime(12335): Caused by: java.lang.ClassCastException: com.example.fragments.ClassicMenuFragment cannot be cast to com.example.fragments.OnePlayerFragment
07-18 16:30:14.249: E/AndroidRuntime(12335):    at com.example.MainActivity.setViewScore(MainActivity.java:325)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at com.example.GameState$5.run(GameState.java:650)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at android.app.Activity.runOnUiThread(Activity.java:5517)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at com.example.GameState.reset(GameState.java:647)
07-18 16:30:14.249: E/AndroidRuntime(12335):    at com.example.MainActivity.StartGame(MainActivity.java:146)
07-18 16:30:14.249: E/AndroidRuntime(12335):    ... 13 more

您可能需要的唯一其他信息是,在 theGamereset() 方法中,它调用了 上的 setViewScore() >MainActivity 对象通过 theView.initiateGameThread(GameState.ANIMATION_MODE, this); 使用 runOnUiThread() 传递给它。

所以,我的问题是,为什么我第一次调用 changeToFragment() 时将 MainMenuFragment 更改为 ClassicMenuFragment,但搞砸了第二次应该将 ClassicMenuFragment 更改为 OnePlayerFragment 时?

感谢您花时间考虑我的这个问题。

最佳答案

ClassCastException 是正确的 - 您不能将 ClassicMenuFragment 转换为 OnePlayerFragment。在继承方面,您不能将一个兄弟类强制转换为另一个兄弟类(这两个类都是兄弟类,具有共同的父级 Fragment)。一个类比是 OrangeApple 都是 Fruit 类的子类,但是您不能将 Orange 转换为Apple(这没有意义!)

相反,删除对 OnePlayerFragment 的强制转换并使用 instanceof 关键字,仅在确定 fragment 是哪个子实例后才强制转换:

Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f == null) Log.d("Fragment", "null!!!");
if (f instanceof OnePlayerFragment) {
    ((OnePlayerFragment) f).setScore(view, score);
}

关于java - 第二次调用 FragmentTransaction.replace() 时出现 Android Fragment 转换错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31495924/

相关文章:

java - 当 _rev 为 null 时,_rev 格式无效

java - 如何以编程方式移动 mapView (osmdroid)

java - 使用 gson 将 Java 对象转换为 JSON 字符串

android - 首次设置导航组件

java - 无法从 Fragment 访问 TextView

java - 如何创建 2 个新玩家并从 EditText 字段中获取名称

android - 如何在 MapView 下方添加 TextView?

android - 移动应用——在后端存储和版本化相对静态的json文件

java - 更快地启动对象并更快地更新 Textview

android - 仅在一个方向上使用 fragment 作为 ListView header