未附加到上下文的片段选项卡

Fragment tab not attached to a context


我目前正在 android 工作室制作音板应用程序,其中包含大量片段。我的选项卡布局上的第一页只是一个普通的按钮视图,但我的第二个 tabView 中有另一个 fragmentView。目标是让第一页只显示一些正常的声音,第二个选项卡让多个操作员选择特定的音板。

无论如何,该应用似乎运行良好。我可以从主选项卡切换到操作员选项卡,甚至 select 操作员音板页面并移动到它们的片段。但是,一旦我尝试切换片段(通过我的选项卡视图),我的应用程序就会崩溃并且出现错误:

"Fragment operatorTab{4623fc9} (312d4e58-458c-4f47-8fa3-794fe15f0536)} not attached to a context."**
**at com.jkcarraher.rainbowsixsoundboard.operatorTab.resetAllButtons(operatorTab.java:113)
        at com.jkcarraher.rainbowsixsoundboard.operatorTab.access[=10=]0(operatorTab.java:31)
        at com.jkcarraher.rainbowsixsoundboard.operatorTab.onScrollChanged(operatorTab.java:107)

我在下面附上了我的代码,如果您有任何想法,请告诉我!

MainActivity.java

import android.graphics.Color;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.widget.Adapter;
import android.widget.TextView;

import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.initialization.InitializationStatus;
import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
import com.google.android.material.tabs.TabLayout;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private SectionsPageAdapter mSectionsPageAdapter;
    private ViewPager mViewPager;
    private TabLayout tabLayout;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initializeAds();
        makeSixYellow();

        //Create ViewPager (that houses all fragments)
        mSectionsPageAdapter = new SectionsPageAdapter(getSupportFragmentManager());
        mViewPager = (ViewPager) findViewById(R.id.view_pager);
        setUpViewPager(mViewPager);

        //Add & customize tabs
        tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setTabTextColors(Color.WHITE, Color.WHITE);
        tabLayout.setupWithViewPager(mViewPager);
        tabLayout.getTabAt(0).setText("Utility");
        tabLayout.getTabAt(1).setText("Voice Lines");
        checkPageChange();
    }

    private void setUpViewPager(ViewPager viewPager) {
        SectionsPageAdapter adapter = new SectionsPageAdapter(getSupportFragmentManager());
        adapter.addFragment(new utilityTab(), "Utility");
        adapter.addFragment(new voiceLinesView(), "Voice Lines");

        viewPager.setAdapter(adapter);
    }

    private void makeSixYellow(){
        TextView textView = findViewById(R.id.titleText);
        String text = "R6 Soundboard";
        SpannableString ss = new SpannableString(text);
        ForegroundColorSpan fcsYellow = new ForegroundColorSpan(Color.rgb(255,236,141));
        ss.setSpan(fcsYellow, 1,2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        textView.setText(ss);
    }

    private void initializeAds(){
        MobileAds.initialize(this, new OnInitializationCompleteListener() {
            @Override
            public void onInitializationComplete(InitializationStatus initializationStatus) {
            }
        });
    }

    private void checkPageChange(){
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                utilityTab.resetAllButtons();
            }

            @Override
            public void onPageSelected(int position) {
                utilityTab.resetAllButtons();
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                utilityTab.resetAllButtons();
            }
        });
        }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorHeader"
        android:theme="@style/AppTheme.AppBarOverlay">

        <TextView
            android:id="@+id/titleText"
            android:layout_marginLeft="20dp"
            android:layout_marginTop="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:fontFamily="@font/avenir"
            android:gravity="center_horizontal"
            android:text="R6 Soundboard"
            android:textColor="#FFF"
            android:textSize="30sp" />

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabTextColor="#fff"
            android:background="@color/colorHeader" />
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorBody"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

voiceLinesView.java(包含片段所以我可以 select 一个操作员,它将带我到他们的音板片段)

package com.jkcarraher.rainbowsixsoundboard;

import android.os.Bundle;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.ViewPager;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.material.tabs.TabLayout;

public class voiceLinesView extends Fragment {

    private SectionsPageAdapter voiceLinesSectionsPageAdapter;
    private ViewPager voiceLinesViewPager;

    public voiceLinesView() {
        // Required empty public constructor
    }

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

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.voiceLinesFrame, new operatorTab());
        fragmentTransaction.commit();


        return view;
    }

}

fragment_voice_lines_view.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    tools:context=".voiceLinesView">

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

</FrameLayout>

operatorTab.java

package com.jkcarraher.rainbowsixsoundboard;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;
import android.widget.ScrollView;

import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;

import soup.neumorphism.NeumorphCardView;
import soup.neumorphism.ShapeType;

import static android.view.MotionEvent.ACTION_MOVE;

public class operatorTab extends Fragment {
    private ScrollView scrollView;
    public static RelativeLayout KapkanButton;
    public static RelativeLayout GlazButton;
    public static RelativeLayout FuzeButton;
    public static RelativeLayout TachankaButton;

    voiceLinesView voiceLinesView = new voiceLinesView();


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

        //Initialize ScrollView
        scrollView = view.findViewById(R.id.operatorScrollView);

        //Initialize buttons 1-4
        KapkanButton = view.findViewById(R.id.kapkanButton);
        GlazButton = view.findViewById(R.id.glazButton);
        FuzeButton = view.findViewById(R.id.fuzeButton);
        TachankaButton = view.findViewById(R.id.tachankaButton);

        //Make buttons 1-6 pressable
        initPressableButton(KapkanButton);
        initPressableButton(GlazButton);
        initPressableButton(FuzeButton);
        initPressableButton(TachankaButton);

        scrollResetListener();
        return view;
    }

    @SuppressLint("ClickableViewAccessibility")
    private void initPressableButton(final RelativeLayout relativeLayout) {
        relativeLayout.setBackgroundColor(getResources().getColor(R.color.colorBody));
        final Rect r = new Rect();

        relativeLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent event) {
                // get the View's Rect relative to its parent
                view.getHitRect(r);
                // offset the touch coordinates with the values from r
                // to obtain meaningful coordinates
                final float x = event.getX() + r.left;
                final float y = event.getY() + r.top;

                if(event.getAction() == MotionEvent.ACTION_DOWN) {
                    relativeLayout.setBackgroundColor(getResources().getColor(R.color.colorButtonPressed));
                    return true;
                } else if(event.getAction() == MotionEvent.ACTION_UP) {
                    if (r.contains((int) x, (int) y)) {
                        relativeLayout.setBackgroundColor(getResources().getColor(R.color.colorBody));
                        //On Click Up
                        FragmentTransaction fr = getFragmentManager().beginTransaction();
                        fr.replace(R.id.voiceLinesFrame, new kapkanVoiceLines());
                        fr.commit();
                    }

                }else if(event.getAction() == ACTION_MOVE){
                    if (!r.contains((int) x, (int) y)) {
                        relativeLayout.setBackgroundColor(getResources().getColor(R.color.colorBody));
                    }
                    return true;
                }
                return false;
            }
        });
    }

    private void scrollResetListener(){
        scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                resetAllButtons();
            }
        });
    }

    public void resetAllButtons(){
        KapkanButton.setBackgroundColor(getResources().getColor(R.color.colorBody));
        GlazButton.setBackgroundColor(getResources().getColor(R.color.colorBody));
        FuzeButton.setBackgroundColor(getResources().getColor(R.color.colorBody));
        TachankaButton.setBackgroundColor(getResources().getColor(R.color.colorBody));
    }
}

fragment_operator_tab.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".operatorTab">

    <ScrollView
        android:id="@+id/operatorScrollView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorBody">
            <RelativeLayout
                android:id="@+id/kapkanButton"
                android:layout_width="match_parent"
                android:layout_height="60sp"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/kapkanIcon"
                    android:layout_width="50sp"
                    android:layout_height="50sp"
                    android:layout_marginTop="5sp"
                    android:layout_marginLeft="10sp"
                    android:src="@drawable/ic_kapkan"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/kapkanIcon"
                    android:layout_marginLeft="10sp"
                    android:layout_marginTop="15sp"
                    android:layout_gravity="center"
                    android:fontFamily="@font/avenir"
                    android:text="Kapkan"
                    android:textColor="#ffffff"
                    android:textSize="25dp" />

                <ImageView
                    android:layout_width="40sp"
                    android:layout_height="40sp"
                    android:layout_alignParentEnd="true"
                    android:layout_marginTop="10sp"
                    android:layout_marginEnd="10dp"
                    android:src="@drawable/ic_arrow_right" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/glazButton"
                android:layout_below="@+id/kapkanButton"
                android:layout_width="match_parent"
                android:layout_height="60sp"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/glazIcon"
                    android:layout_width="50sp"
                    android:layout_height="50sp"
                    android:layout_marginTop="5sp"
                    android:layout_marginLeft="10sp"
                    android:src="@drawable/ic_glaz"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/glazIcon"
                    android:layout_marginLeft="10sp"
                    android:layout_marginTop="15sp"
                    android:layout_gravity="center"
                    android:fontFamily="@font/avenir"
                    android:text="Glaz"
                    android:textColor="#ffffff"
                    android:textSize="25dp" />

                <ImageView
                    android:layout_width="40sp"
                    android:layout_height="40sp"
                    android:layout_alignParentEnd="true"
                    android:layout_marginTop="10sp"
                    android:layout_marginEnd="10dp"
                    android:src="@drawable/ic_arrow_right" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/fuzeButton"
                android:layout_below="@+id/glazButton"
                android:layout_width="match_parent"
                android:layout_height="60sp"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/fuzeIcon"
                    android:layout_width="50sp"
                    android:layout_height="50sp"
                    android:layout_marginTop="5sp"
                    android:layout_marginLeft="10sp"
                    android:src="@drawable/ic_fuze"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/fuzeIcon"
                    android:layout_marginLeft="10sp"
                    android:layout_marginTop="15sp"
                    android:layout_gravity="center"
                    android:fontFamily="@font/avenir"
                    android:text="Fuze"
                    android:textColor="#ffffff"
                    android:textSize="25dp" />

                <ImageView
                    android:layout_width="40sp"
                    android:layout_height="40sp"
                    android:layout_alignParentEnd="true"
                    android:layout_marginTop="10sp"
                    android:layout_marginEnd="10dp"
                    android:src="@drawable/ic_arrow_right" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/tachankaButton"
                android:layout_below="@+id/fuzeButton"
                android:layout_width="match_parent"
                android:layout_height="60sp"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/tachankaIcon"
                    android:layout_width="50sp"
                    android:layout_height="50sp"
                    android:layout_marginTop="5sp"
                    android:layout_marginLeft="10sp"
                    android:src="@drawable/ic_tachanka"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tachankaIcon"
                    android:layout_marginLeft="10sp"
                    android:layout_marginTop="15sp"
                    android:layout_gravity="center"
                    android:fontFamily="@font/avenir"
                    android:text="Tachanka"
                    android:textColor="#ffffff"
                    android:textSize="25dp" />

                <ImageView
                    android:layout_width="40sp"
                    android:layout_height="40sp"
                    android:layout_alignParentEnd="true"
                    android:layout_marginTop="10sp"
                    android:layout_marginEnd="10dp"
                    android:src="@drawable/ic_arrow_right" />
            </RelativeLayout>

        </RelativeLayout>
    </ScrollView>

</FrameLayout>

尝试在片段被销毁时删除 ViewTreeObserver.OnScrollChangedListener 这将避免任何可以附加到被销毁的片段上下文的侦听器在您离开并返回此片段时不会被触发。

首先:为监听器创建一个全局字段

public class operatorTab extends Fragment {

    ...

    ViewTreeObserver.OnScrollChangedListener  mScrollListener = new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            resetAllButtons();
        }
    };

然后用创建的字段设置监听器

private void scrollResetListener(){
    scrollView.getViewTreeObserver().addOnScrollChangedListener(mScrollListener);
}

然后删除侦听器..我相信它通常应该在 onDestroyView() >> 但如果它不起作用也可以在 onStop()onPause 中尝试

这也将解决在 Fragment 的视图被销毁后触发回调时此侦听器的内存泄漏问题

@Override
public void onDestroyView() {
    super.onDestroyView();
    scrollView.getViewTreeObserver().removeOnScrollChangedListener(mScrollListener);
}

更新

onStop()/onPause() worked for me

它不适用于 onDestroyView() 的原因是 OperatorTab 片段是 ViewPager 页面之一的一部分;并且默认情况下 ViewPager 在后台加载一些关闭的页面,为下一页滚动做好准备;加载此页面包括一些片段生命周期回调,如 onCreateViewonStart,但不包括 onResume.

当您通过将 ViewPager 滚动到下一个 tab/page 离开 OperatorTab 时;如果 ViewPager 决定 OperatorTab 是用户稍后可能 return 返回的 cached/close 页面的一部分,则不会创建 onDestroyView();因此,监听器仍然存在,并且在寻呼机轻扫几下后,OperatorTab 片段可以从上下文中分离出来,使监听器存在内存泄漏...

因此,在 ViewPager 片段(或任何提前加载其片段的视图)中的最佳方法是在您离开页面后停止所有侦听器,即在 onPauseonStop 回调而不是等到它们被销毁。

此错误的一个可能原因是您在 Activity 中调用 getResources() 时未使用上下文。因此,您需要使用 context.getResources() 而不是上下文可能是其中之一的 here 。在你的例子中,我认为 getContext() 会起作用。

所以,我认为您应该搜索所有调用 getResources 的时间,看看您是否在使用上下文。

这个错误的简单解释是,您试图在实例化片段之前访问 getResources() 中所需的上下文。