未附加到上下文的片段选项卡
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
在后台加载一些关闭的页面,为下一页滚动做好准备;加载此页面包括一些片段生命周期回调,如 onCreateView
、onStart
,但不包括 onResume
.
当您通过将 ViewPager
滚动到下一个 tab/page 离开 OperatorTab
时;如果 ViewPager
决定 OperatorTab
是用户稍后可能 return 返回的 cached/close 页面的一部分,则不会创建 onDestroyView()
;因此,监听器仍然存在,并且在寻呼机轻扫几下后,OperatorTab
片段可以从上下文中分离出来,使监听器存在内存泄漏...
因此,在 ViewPager
片段(或任何提前加载其片段的视图)中的最佳方法是在您离开页面后停止所有侦听器,即在 onPause
或 onStop
回调而不是等到它们被销毁。
此错误的一个可能原因是您在 Activity 中调用 getResources() 时未使用上下文。因此,您需要使用 context.getResources() 而不是上下文可能是其中之一的 here 。在你的例子中,我认为 getContext() 会起作用。
所以,我认为您应该搜索所有调用 getResources 的时间,看看您是否在使用上下文。
这个错误的简单解释是,您试图在实例化片段之前访问 getResources() 中所需的上下文。
我目前正在 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
在后台加载一些关闭的页面,为下一页滚动做好准备;加载此页面包括一些片段生命周期回调,如 onCreateView
、onStart
,但不包括 onResume
.
当您通过将 ViewPager
滚动到下一个 tab/page 离开 OperatorTab
时;如果 ViewPager
决定 OperatorTab
是用户稍后可能 return 返回的 cached/close 页面的一部分,则不会创建 onDestroyView()
;因此,监听器仍然存在,并且在寻呼机轻扫几下后,OperatorTab
片段可以从上下文中分离出来,使监听器存在内存泄漏...
因此,在 ViewPager
片段(或任何提前加载其片段的视图)中的最佳方法是在您离开页面后停止所有侦听器,即在 onPause
或 onStop
回调而不是等到它们被销毁。
此错误的一个可能原因是您在 Activity 中调用 getResources() 时未使用上下文。因此,您需要使用 context.getResources() 而不是上下文可能是其中之一的 here 。在你的例子中,我认为 getContext() 会起作用。
所以,我认为您应该搜索所有调用 getResources 的时间,看看您是否在使用上下文。
这个错误的简单解释是,您试图在实例化片段之前访问 getResources() 中所需的上下文。