从 onTouch 通过 id 获取片段

Fetching a fragment by id from onTouch

基于模板 IntelliJ Android 应用程序,我正在尝试制作一个 activity 在按下 TextView 时从一个片段滑到另一个片段。

然而,我的activity中的onTouch找不到原始片段。当我触摸相关的 TextView 时,应用程序崩溃,并且日志消息指示 NullPointerException(见下文)。

从 onTouch 事件中检索现有片段的正确方法是什么?

代码如下:

public class MainActivity extends FragmentActivity implements View.OnTouchListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment())
                    .commit();
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (view.getId() == R.id.textView2) {
            FragmentManager fm = getSupportFragmentManager();
            Fragment originalFragment = fm.findFragmentById(R.layout.fragment_main);

            System.out.println(("===============\noriginal Fragment is ") +
                (originalFragment == null ? "" : "not ") +
                "null\n===============");

            fm.beginTransaction()  // <-- line 84
                    .setCustomAnimations(R.animator.slide_in_right,
                            R.animator.slide_out_right,
                            R.animator.slide_in_right,
                            R.animator.slide_out_right)
                    .replace(originalFragment.getId(), Fragment
                                    .instantiate(this, AddQuestionFragment.class.getName()),
                            TAG)
                    .addToBackStack(null).commit();
            }

        }
        return false;
    }

    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            TextView v = (TextView) rootView.findViewById(R.id.textView2);
            TextView v1 = (TextView) rootView.findViewById(R.id.textView3);

            View.OnTouchListener activityAsListener = (View.OnTouchListener) getActivity();
            v.setOnTouchListener(activityAsListener);
            v1.setOnTouchListener(activityAsListener);

            return rootView;
        }
    }

    public static class AddQuestionFragment extends Fragment {
        public AddQuestionFragment() {}

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_add_question, container, false);

            return rootView;
        }
    }
}

res/layout/main.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    tools:ignore="MergeRootFrame" />

res/layout/fragment_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity$PlaceholderFragment">

    <TextView
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" android:id="@+id/textView"/>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" android:layout_below="@+id/textView"
        android:layout_alignParentLeft="true" android:layout_alignParentStart="true">
    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="@string/add_question_title"
            android:id="@+id/textView2"/>
    </LinearLayout>

</RelativeLayout>

和日志消息:

01-08 17:39:42.904    6555-6555/com.scubbo.lifetracker.app I/art﹕ Late-enabling -Xcheck:jni
01-08 17:40:08.125    6555-6555/com.scubbo.lifetracker.app I/System.out﹕ ===============
01-08 17:40:08.125    6555-6555/com.scubbo.lifetracker.app I/System.out﹕ original Fragment is null
01-08 17:40:08.125    6555-6555/com.scubbo.lifetracker.app I/System.out﹕ ===============
01-08 17:40:08.126    6555-6555/com.scubbo.lifetracker.app E/InputEventReceiver﹕ Exception dispatching input event.
01-08 17:40:08.126    6555-6555/com.scubbo.lifetracker.app E/MessageQueue-JNI﹕ Exception in MessageQueue callback: handleReceiveCallback
01-08 17:40:08.136    6555-6555/com.scubbo.lifetracker.app E/MessageQueue-JNI﹕ java.lang.NullPointerException: Attempt to invoke virtual method 'int android.support.v4.app.Fragment.getId()' on a null object reference
            at com.scubbo.lifetracker.app.MainActivity.onTouch(MainActivity.java:84)
            at android.view.View.dispatchTouchEvent(View.java:8382)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2314)
            at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1692)
            at android.app.Activity.dispatchTouchEvent(Activity.java:2739)
            at android.support.v7.app.ActionBarActivityDelegateICS$WindowCallbackWrapper.dispatchTouchEvent(ActionBarActivityDelegateICS.java:268)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2275)
            at android.view.View.dispatchPointerEvent(View.java:8578)
            at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4021)
            at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:3887)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3449)
            at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3502)
            at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3468)
            at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3578)
            at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3476)
            at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3635)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3449)
            at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3502)
            at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3468)
            at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3476)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3449)
            at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5701)
            at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5675)
            at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5646)
            at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:5791)
            at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
            at android.os.MessageQueue.nativePollOnce(Native Method)
            at android.os.MessageQueue.next(MessageQueue.java:143)
            at android.os.Looper.loop(Looper.java:122)
            at android.app.ActivityThread.main(ActivityThread.java:5221)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
01-08 17:40:08.137    6555-6555/com.scubbo.lifetracker.app D/AndroidRuntime﹕ Shutting down VM
    --------- beginning of crash
01-08 17:40:08.138    6555-6555/com.scubbo.lifetracker.app E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.scubbo.lifetracker.app, PID: 6555
    java.lang.NullPointerException: Attempt to invoke virtual method 'int android.support.v4.app.Fragment.getId()' on a null object reference
            at com.scubbo.lifetracker.app.MainActivity.onTouch(MainActivity.java:84)
            at android.view.View.dispatchTouchEvent(View.java:8382)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2430)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2119)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2314)
            at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1692)
            at android.app.Activity.dispatchTouchEvent(Activity.java:2739)
            at android.support.v7.app.ActionBarActivityDelegateICS$WindowCallbackWrapper.dispatchTouchEvent(ActionBarActivityDelegateICS.java:268)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2275)
            at android.view.View.dispatchPointerEvent(View.java:8578)
            at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4021)
            at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:3887)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3449)
            at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3502)
            at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3468)
            at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3578)
            at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3476)
            at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3635)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3449)
            at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3502)
            at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3468)
            at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3476)
            at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3449)
            at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5701)
            at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5675)
            at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5646)
            at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:5791)
            at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
            at android.os.MessageQueue.nativePollOnce(Native Method)
            at android.os.MessageQueue.next(MessageQueue.java:143)
            at android.os.Looper.loop(Looper.java:122)
            at android.app.ActivityThread.main(ActivityThread.java:5221)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
01-08 17:40:10.095    6555-6555/com.scubbo.lifetracker.app I/Process﹕ Sending signal. PID: 6555 SIG: 9

编辑:我想我已经找到了部分问题。通过将代码更改为:

[...]
PlaceholderFragment fg = new PlaceholderFragment();
System.out.println("id of fg is " + ((Integer)fg.getId()).toString());
getSupportFragmentManager().beginTransaction()
    .add(R.id.container, fg)
    .commit();
[...]
FragmentManager fm = getSupportFragmentManager();
System.out.println("Searching for fragment with id " + ((Integer) R.layout.fragment_main).toString());
Fragment originalFragment = fm.findFragmentById(R.layout.fragment_main);
[...]

我得到了:

01-08 18:11:40.873  20001-20001/com.scubbo.lifetracker.app I/System.out﹕ id of fg is 0
01-08 18:12:04.395  20001-20001/com.scubbo.lifetracker.app I/System.out﹕ Searching for fragment with id 2130903066

我惊讶地发现原始片段的 ID 为 0(这是为什么?),但我想这可以解释为什么找不到它 - 0 != 2130903066。不过,我仍然不知道如何最好地获取原始片段。


This answer 建议我应该通过 R.id.<foo> 引用片段,而不是 R.layout.<bar> - 但它还期望片段定义以 <fragment 开头,而模板由 IntelliJ 提供,它们开始 in media res(例如 <RelativeLayout)。是时候进行更多修补了...

Attempt to invoke virtual method 'int android.support.v4.app.Fragment.getId()' on a null object reference

因为originalFragment为空。

如果您想通过调用 findFragmentById.

获取 Fragment 的 ID,请使用 add(int containerViewId, Fragment fragment, String tag) 版本的 add 方法

破解了!由于上述 ID 的意外行为,我不得不改为按标签搜索。以下作品:

[...]
if (savedInstanceState == null) {
    PlaceholderFragment fg = new PlaceholderFragment();
    getSupportFragmentManager().beginTransaction()
            .add(R.id.container, fg, MAIN_FRAGMENT_TAG)
            .commit();
}
[...]
Fragment originalFragment = fm.findFragmentByTag(MAIN_FRAGMENT_TAG);
Fragment f = fm.findFragmentByTag(TAG);
fm.beginTransaction()
    .setCustomAnimations(R.animator.slide_in_right,
        R.animator.slide_out_left,
        R.animator.slide_in_left,
        R.animator.slide_out_right)
    .replace(originalFragment.getId(),
             Fragment.instantiate(this, AddQuestionFragment.class.getName()),
             TAG)
    .addToBackStack(null).commit();

您也可以使用普通的 onClickListener 并在其中开始您的新片段替换(类似于您已经在 onCreate 中显示 PlaceholderFragment 的方式)

public class MainActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                                       .add(R.id.container, new PlaceholderFragment())
                                       .commit();
        }
    }

    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            TextView v = (TextView) rootView.findViewById(R.id.textView2);
            TextView v1 = (TextView) rootView.findViewById(R.id.textView3);

            View.OnClickListener onClickListener = new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getActivity().getSupportFragmentManager()
                        .beginTransaction()  // <-- line 84
                        .setCustomAnimations(R.animator.slide_in_right,
                                             R.animator.slide_out_right,
                                             R.animator.slide_in_right,
                                             R.animator.slide_out_right)
                        .replace(R.id.container, new AddQuestionFragment(), "TAG_AddQuestionFragment")
                        .addToBackStack(null).commit();
                }
            };

            v.setOnClickListener(onClickListener);
            v1.setOnClickListener(onClickListener);

            return rootView;
        }
    }

    public static class AddQuestionFragment extends Fragment {
        public AddQuestionFragment() {}

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_add_question, container, false);

            return rootView;
        }
    }
}