数据更改时刷新片段内容的方法(如调用 onCreateView)
Method to refresh Fragment content when data changed ( like recall onCreateView)
我有一个 Activity
布局中有一个片段容器。
里面可以显示3个不同的Fragment
。
这些片段包含一个 Listview
,它使用我制作的自定义适配器显示数据。
所以每个列表元素都是在 onCreateView
期间创建的,在我查询数据库以获得数据之后。
但有时我的数据库中的某些数据可能会发生变化,所以我想 redraw/recreate 它 Listview
。
- 什么是刷新片段视图的最佳方式(我的意思是,资源需求越少)?
- 有没有手动调用onCreateView的方法?
用
分离并附加它
Fragment currentFragment = getFragmentManager().findFragmentByTag("YourFragmentTag");
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.detach(currentFragment);
fragmentTransaction.attach(currentFragment);
fragmentTransaction.commit();
或使用
搜索片段
Fragment currentFragment = getActivity().getSupportFragmentManager().findFragmentById(R.id.container);
Fragment
有一个非常有用的方法,可以用来刷新片段
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
//Write down your refresh code here, it will call every time user come to this fragment.
//If you are using listview with custom adapter, just call notifyDataSetChanged().
}
}
合并两个答案并删除了 if (isVisibleToUser)
,因为它使得 setUserVisibleHint
以不可预测的异步顺序被调用,片段可以刷新也可以不刷新。我发现这段代码稳定(在你的片段中):
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// Refresh tab data:
if (getFragmentManager() != null) {
getFragmentManager()
.beginTransaction()
.detach(this)
.attach(this)
.commit();
}
}
当片段中的数据发生变化时,将片段分离并重新附加到 framelayout
中总是一个好主意。在我的例子中,我有一个 listview
显示我的用户最喜欢的项目。一旦用户不喜欢某个产品,我必须将其从列表中删除并重新加载 Favorites
片段。
所以我做了类似这样的事情来将片段重新加载到框架布局中:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.frm,new FavoritesFragment()).addToBackStack(null).commit();
这里,frm
是存放片段的MainActivity
中的框架布局,FavoritesFragment()
是需要重新加载的片段。
每次用户按下 unlike
按钮时都应执行上述代码
根据FragmentTransaction#replace,这与调用删除然后添加相同。因此,您可以在启动片段或要重新加载片段时将 .replace
与片段管理器一起使用。因此,请使用 onCreate
中的相同功能以及您要重新加载它的位置...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
loadContentFragment();
}
}
private void someFunctionThatChangesValue(String value) {
mValue = value;
loadContentFragment();
}
private void loadContentFragment() {
ContentListFragment newFrag = ContentListFragment.newInstance();
// passing value from activity
Bundle args = new Bundle();
args.putString(Constants.ARG_ACCOUNT_NAME, mValue);
newFrag.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frag_container,
newFrag,
Constants.CONTENT_FRAGMENT)
.commitNow();
}
这样一来,只有一个函数可以加载内容和传递数据。这假设您的布局中有一个 ID 为 content_frag_container
的条目。我使用了框架布局。
这个方法对我有用:
MyCameraFragment f2 = new MyCameraFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, f2);
transaction.addToBackStack(null);
transaction.commit();
以下代码刷新来自适配器的片段
下载适配器代码:
public class DownloadsAdapter extends RecyclerView.Adapter<DownloadsAdapter.ViewHolder> {
private Context mCtx;
//getting the context and product list with constructor
public DownloadsAdapter(Context mCtx, List<DataModel> fileUrlLinkList) {
this.mCtx = mCtx;
this.fileUrlList = fileUrlLinkList;
}
**... and in onBindViewHolder**
FragmentManager manager = ((AppCompatActivity) mCtx).getSupportFragmentManager();
Fragment currentFragment = manager.findFragmentByTag("DOWNLOADS");
FragmentTransaction fragmentTransaction = manager.beginTransaction();
fragmentTransaction.detach(currentFragment);
fragmentTransaction.attach(currentFragment);
fragmentTransaction.commit();
...
}
如果你对上面列出的一些方法有问题(就像我在升级后遇到的那样......),我建议在片段中制作某种 public 刷新方法然后简单地调用它,它代码更少,更好更快,因为片段不需要重新初始化...
FragmentManager fm = getSupportFragmentManager();
//if you added fragment via layout xml
Fragment fragment = fm.findFragmentById(R.id.your_fragment_id);
if(fragment instanceof YourFragmentClass) // avoid crash if cast fail
{
((YourFragmentClass)fragment).showPrayer();
}
如果您通过代码添加片段并在添加片段时使用了标记字符串,请改用 findFragmentByTag:
Fragment fragment = fm.findFragmentByTag("yourTag");
if(fragment instanceof YourFragmentClass)
{
((YourFragmentClass)fragment).showPrayer();
}
在要刷新的Fragment中设置
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if(isVisibleToUser){
if (getFragmentManager() != null) {
getFragmentManager()
?.beginTransaction()
?.detach(this)
?.attach(this)
?.commit();
}
}
}
如今,Android 应用程序倾向于实现 Android Navigation 组件(这简化了片段管理和导航),在这种情况下,建议采用不同于其他建议答案的其他方法,以避免直接操作片段并与片段管理器交互。当您需要做的只是更新适配器数据时,执行“刷新”以使您的片段状态保持最新会不必要地破坏并重新创建视图。
您可以改为使用事件驱动的方法并实现类似事件总线的模式,消费者可以向其发布和订阅。您正在更新数据的组件将负责发布与更新相关的事件,而任何关心更新的组件都可以订阅这些事件。
这是 Kotlin 中的一个简单 EventBus class,它利用协程和流程等 Kotlin 语言功能来实现上述模式:
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
class EventBus {
private val _events = MutableSharedFlow<AppEvent>()
val events = _events.asSharedFlow()
suspend fun emitEvent(event: AppEvent) {
_events.emit(event)
}
}
在这种情况下,AppEvent
是一个枚举,例如:
enum class AppEvent {
TODO_LIST_UPDATED
}
如果您想将数据与事件一起传递,您可以为您的 AppEvent
而不是枚举实现 Kotlin sealed classes。
假设与您的片段无关的其他组件正在更新数据,一旦完成它就可以发出一个事件:
eventBus.emitEvent(AppEvent.TODO_LIST_UPDATED)
假设您使用的是 MVVM 架构,您的片段视图模型可以在初始化时订阅事件总线:
init {
viewModelScope.launch {
eventBus.events
.filter { it == AppEvent.TODO_LIST_UPDATED }
.collectLatest {
val todosResponse = todoRepository.getTodos()
_todos.value = todosResponse.todos
}
}
}
您的片段将观察 LiveData 并在检索到新数据后在适配器上设置数据。
如果您没有视图模型,您可以直接在您的片段中执行此操作,使用 lifecycleScope
尽管如今,建议使用视图模型并实现 MVVM 架构。
需要确保的一件重要事情是您的事件总线实例是单例实例,以便所有消费者都发布和订阅同一总线。您可以通过使用依赖注入框架并遵循为该框架创建单例的说明来实现这一点。
您可以在 blog post I wrote on this topic 上详细了解事件总线模式。
如果您按照 Material 设计教程进行操作,则只需导航至同一页面即可自动重新加载。不要忘记通过 backstack 添加“false”,因为您不必 return 到前一个片段,因为它是同一个片段。
((NavigationHost) getActivity()).navigateTo(new MyNotesFragment(), false);
import androidx.fragment.app.Fragment;
public interface NavigationHost {
void navigateTo(Fragment fragment, boolean addToBackstack);
}
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
public class MainActivity extends AppCompatActivity implements NavigationHost {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.container, new LoginFragment())
.commit();
}
}
@Override
public void navigateTo(Fragment fragment, boolean addToBackstack) {
FragmentTransaction transaction =
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment);
if (addToBackstack) {
transaction.addToBackStack(null);
}
transaction.commit();
}
}
#我的项目是使用 Android 导航用片段构建的#
MainList 片段调用FilterDialog 片段。
程序和用户在“过滤器”对话框中完成工作后,return 按钮被推 return 到 MainList 片段并按要求刷新它。如果您有 Viewmodel 并且能够共享日期,这很容易做到。
我先给你结束,然后是导致它的编码链。
*val navController = findNavController()
navController.run{ popBackStack()
navigate(mViewModel. FilterDialogReturnTarget )}`*
这会弹出后台堆栈并正确刷新 return 片段以反映对过滤器的更改。
>>> Viewmodel:
import androidx.lifecycle.ViewModel …….
class MainActivityViewModel : ViewModel() { ……
var FilterDialogReturnTarget:Int=0…….
>>navigation Graph
navigation/nglist.xml
<navigation …….
<fragment
android:id="@+id/nav_main_list" ……
>>>MainList.kt
class MainList : androidx.fragment.app.Fragment(){……
import App.name.name.viewmodel.MainActivityViewModel
private val mViewModel: MainActivityViewModel by activityViewModels()
private fun setUpListeners() {….
binding.vemlBtnFilterOn.setOnClickListener {…….
mViewModel.FilterDialogReturnTarget = R.id.nav_main_list
findNavController().navigate(R.id.action_main_list_to_FilterDialog)
}
>>>FilterDialog.kt
import androidx.fragment.app.DialogFragment
import app.name.name.MainActivityViewModel
class ElectionsFilterDialog : DialogFragment(){…..
private val mViewModel: MainActivityViewModel by activityViewModels()
private fun setUpListeners() {….
binding.vemlBtnFilterOn.setOnClickListener {…….
val navController = findNavController()
navController.run{ popBackStack()
navigate(mViewModel.FilterDialogReturnTarget )}
这对我来说就像一个魅力,因为我已经有了视图模型和导航。
我有一个 Activity
布局中有一个片段容器。
里面可以显示3个不同的Fragment
。
这些片段包含一个 Listview
,它使用我制作的自定义适配器显示数据。
所以每个列表元素都是在 onCreateView
期间创建的,在我查询数据库以获得数据之后。
但有时我的数据库中的某些数据可能会发生变化,所以我想 redraw/recreate 它 Listview
。
- 什么是刷新片段视图的最佳方式(我的意思是,资源需求越少)?
- 有没有手动调用onCreateView的方法?
用
分离并附加它Fragment currentFragment = getFragmentManager().findFragmentByTag("YourFragmentTag");
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.detach(currentFragment);
fragmentTransaction.attach(currentFragment);
fragmentTransaction.commit();
或使用
搜索片段Fragment currentFragment = getActivity().getSupportFragmentManager().findFragmentById(R.id.container);
Fragment
有一个非常有用的方法,可以用来刷新片段
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
//Write down your refresh code here, it will call every time user come to this fragment.
//If you are using listview with custom adapter, just call notifyDataSetChanged().
}
}
合并两个答案并删除了 if (isVisibleToUser)
,因为它使得 setUserVisibleHint
以不可预测的异步顺序被调用,片段可以刷新也可以不刷新。我发现这段代码稳定(在你的片段中):
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// Refresh tab data:
if (getFragmentManager() != null) {
getFragmentManager()
.beginTransaction()
.detach(this)
.attach(this)
.commit();
}
}
当片段中的数据发生变化时,将片段分离并重新附加到 framelayout
中总是一个好主意。在我的例子中,我有一个 listview
显示我的用户最喜欢的项目。一旦用户不喜欢某个产品,我必须将其从列表中删除并重新加载 Favorites
片段。
所以我做了类似这样的事情来将片段重新加载到框架布局中:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.frm,new FavoritesFragment()).addToBackStack(null).commit();
这里,frm
是存放片段的MainActivity
中的框架布局,FavoritesFragment()
是需要重新加载的片段。
每次用户按下 unlike
按钮时都应执行上述代码
根据FragmentTransaction#replace,这与调用删除然后添加相同。因此,您可以在启动片段或要重新加载片段时将 .replace
与片段管理器一起使用。因此,请使用 onCreate
中的相同功能以及您要重新加载它的位置...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
loadContentFragment();
}
}
private void someFunctionThatChangesValue(String value) {
mValue = value;
loadContentFragment();
}
private void loadContentFragment() {
ContentListFragment newFrag = ContentListFragment.newInstance();
// passing value from activity
Bundle args = new Bundle();
args.putString(Constants.ARG_ACCOUNT_NAME, mValue);
newFrag.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frag_container,
newFrag,
Constants.CONTENT_FRAGMENT)
.commitNow();
}
这样一来,只有一个函数可以加载内容和传递数据。这假设您的布局中有一个 ID 为 content_frag_container
的条目。我使用了框架布局。
这个方法对我有用:
MyCameraFragment f2 = new MyCameraFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, f2);
transaction.addToBackStack(null);
transaction.commit();
以下代码刷新来自适配器的片段
下载适配器代码:
public class DownloadsAdapter extends RecyclerView.Adapter<DownloadsAdapter.ViewHolder> {
private Context mCtx;
//getting the context and product list with constructor
public DownloadsAdapter(Context mCtx, List<DataModel> fileUrlLinkList) {
this.mCtx = mCtx;
this.fileUrlList = fileUrlLinkList;
}
**... and in onBindViewHolder**
FragmentManager manager = ((AppCompatActivity) mCtx).getSupportFragmentManager();
Fragment currentFragment = manager.findFragmentByTag("DOWNLOADS");
FragmentTransaction fragmentTransaction = manager.beginTransaction();
fragmentTransaction.detach(currentFragment);
fragmentTransaction.attach(currentFragment);
fragmentTransaction.commit();
...
}
如果你对上面列出的一些方法有问题(就像我在升级后遇到的那样......),我建议在片段中制作某种 public 刷新方法然后简单地调用它,它代码更少,更好更快,因为片段不需要重新初始化...
FragmentManager fm = getSupportFragmentManager();
//if you added fragment via layout xml
Fragment fragment = fm.findFragmentById(R.id.your_fragment_id);
if(fragment instanceof YourFragmentClass) // avoid crash if cast fail
{
((YourFragmentClass)fragment).showPrayer();
}
如果您通过代码添加片段并在添加片段时使用了标记字符串,请改用 findFragmentByTag:
Fragment fragment = fm.findFragmentByTag("yourTag");
if(fragment instanceof YourFragmentClass)
{
((YourFragmentClass)fragment).showPrayer();
}
在要刷新的Fragment中设置
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if(isVisibleToUser){
if (getFragmentManager() != null) {
getFragmentManager()
?.beginTransaction()
?.detach(this)
?.attach(this)
?.commit();
}
}
}
如今,Android 应用程序倾向于实现 Android Navigation 组件(这简化了片段管理和导航),在这种情况下,建议采用不同于其他建议答案的其他方法,以避免直接操作片段并与片段管理器交互。当您需要做的只是更新适配器数据时,执行“刷新”以使您的片段状态保持最新会不必要地破坏并重新创建视图。
您可以改为使用事件驱动的方法并实现类似事件总线的模式,消费者可以向其发布和订阅。您正在更新数据的组件将负责发布与更新相关的事件,而任何关心更新的组件都可以订阅这些事件。
这是 Kotlin 中的一个简单 EventBus class,它利用协程和流程等 Kotlin 语言功能来实现上述模式:
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
class EventBus {
private val _events = MutableSharedFlow<AppEvent>()
val events = _events.asSharedFlow()
suspend fun emitEvent(event: AppEvent) {
_events.emit(event)
}
}
在这种情况下,AppEvent
是一个枚举,例如:
enum class AppEvent {
TODO_LIST_UPDATED
}
如果您想将数据与事件一起传递,您可以为您的 AppEvent
而不是枚举实现 Kotlin sealed classes。
假设与您的片段无关的其他组件正在更新数据,一旦完成它就可以发出一个事件:
eventBus.emitEvent(AppEvent.TODO_LIST_UPDATED)
假设您使用的是 MVVM 架构,您的片段视图模型可以在初始化时订阅事件总线:
init {
viewModelScope.launch {
eventBus.events
.filter { it == AppEvent.TODO_LIST_UPDATED }
.collectLatest {
val todosResponse = todoRepository.getTodos()
_todos.value = todosResponse.todos
}
}
}
您的片段将观察 LiveData 并在检索到新数据后在适配器上设置数据。
如果您没有视图模型,您可以直接在您的片段中执行此操作,使用 lifecycleScope
尽管如今,建议使用视图模型并实现 MVVM 架构。
需要确保的一件重要事情是您的事件总线实例是单例实例,以便所有消费者都发布和订阅同一总线。您可以通过使用依赖注入框架并遵循为该框架创建单例的说明来实现这一点。
您可以在 blog post I wrote on this topic 上详细了解事件总线模式。
如果您按照 Material 设计教程进行操作,则只需导航至同一页面即可自动重新加载。不要忘记通过 backstack 添加“false”,因为您不必 return 到前一个片段,因为它是同一个片段。
((NavigationHost) getActivity()).navigateTo(new MyNotesFragment(), false);
import androidx.fragment.app.Fragment;
public interface NavigationHost {
void navigateTo(Fragment fragment, boolean addToBackstack);
}
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
public class MainActivity extends AppCompatActivity implements NavigationHost {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.container, new LoginFragment())
.commit();
}
}
@Override
public void navigateTo(Fragment fragment, boolean addToBackstack) {
FragmentTransaction transaction =
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment);
if (addToBackstack) {
transaction.addToBackStack(null);
}
transaction.commit();
}
}
#我的项目是使用 Android 导航用片段构建的#
MainList 片段调用FilterDialog 片段。 程序和用户在“过滤器”对话框中完成工作后,return 按钮被推 return 到 MainList 片段并按要求刷新它。如果您有 Viewmodel 并且能够共享日期,这很容易做到。
我先给你结束,然后是导致它的编码链。
*val navController = findNavController()
navController.run{ popBackStack()
navigate(mViewModel. FilterDialogReturnTarget )}`*
这会弹出后台堆栈并正确刷新 return 片段以反映对过滤器的更改。
>>> Viewmodel:
import androidx.lifecycle.ViewModel …….
class MainActivityViewModel : ViewModel() { ……
var FilterDialogReturnTarget:Int=0…….
>>navigation Graph
navigation/nglist.xml
<navigation …….
<fragment
android:id="@+id/nav_main_list" ……
>>>MainList.kt
class MainList : androidx.fragment.app.Fragment(){……
import App.name.name.viewmodel.MainActivityViewModel
private val mViewModel: MainActivityViewModel by activityViewModels()
private fun setUpListeners() {….
binding.vemlBtnFilterOn.setOnClickListener {…….
mViewModel.FilterDialogReturnTarget = R.id.nav_main_list
findNavController().navigate(R.id.action_main_list_to_FilterDialog)
}
>>>FilterDialog.kt
import androidx.fragment.app.DialogFragment
import app.name.name.MainActivityViewModel
class ElectionsFilterDialog : DialogFragment(){…..
private val mViewModel: MainActivityViewModel by activityViewModels()
private fun setUpListeners() {….
binding.vemlBtnFilterOn.setOnClickListener {…….
val navController = findNavController()
navController.run{ popBackStack()
navigate(mViewModel.FilterDialogReturnTarget )}
这对我来说就像一个魅力,因为我已经有了视图模型和导航。