我已经看到了两种在应用程序中实例化新 Fragment 的一般做法:
Fragment newFragment = new MyFragment();
和
Fragment newFragment = MyFragment.newInstance();
第二个选项使用静态方法 newInstance()
,通常包含以下方法。
public static Fragment newInstance()
{
MyFragment myFragment = new MyFragment();
return myFragment;
}
起初,我认为主要的好处是我可以重载 newInstance() 方法以在创建 Fragment 的新实例时提供灵活性 - 但我也可以通过为 Fragment 创建重载构造函数来做到这一点。
我错过了什么?
一种方法比另一种方法有什么好处?或者这只是一个好习惯?
如果 Android 决定稍后重新创建您的 Fragment,它将调用您的 Fragment 的无参数构造函数。所以重载构造函数不是一个解决方案。
话虽如此,将内容传递给您的 Fragment 以便它们在 Android 重新创建 Fragment 后可用的方法是将包传递给 setArguments
方法。
因此,例如,如果我们想将整数传递给片段,我们将使用类似的东西:
public static MyFragment newInstance(int someInt) {
MyFragment myFragment = new MyFragment();
Bundle args = new Bundle();
args.putInt("someInt", someInt);
myFragment.setArguments(args);
return myFragment;
}
稍后在 Fragment onCreate()
中,您可以使用以下方法访问该整数:
getArguments().getInt("someInt", 0);
即使 Android 以某种方式重新创建了 Fragment,此 Bundle 也将可用。
另请注意:setArguments
只能在 Fragment 附加到 Activity 之前调用。
android 开发者参考中也记录了这种方法:https://developer.android.com/reference/android/app/Fragment.html
我看到使用 newInstance()
的唯一好处是:
您将有一个地方,可以将片段使用的所有参数捆绑在一起,并且您不必在每次实例化片段时都编写下面的代码。捆绑参数 = 新捆绑(); args.putInt("someInt", someInt); args.putString("someString", someString); // 放置任何其他参数 myFragment.setArguments(args);这是告诉其他类它希望忠实工作的参数的好方法(尽管如果片段实例中没有捆绑任何参数,您应该能够处理情况)。
因此,我认为使用静态 newInstance()
实例化片段是一种很好的做法。
还有另一种方式:
Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
instantiate()
Creates a new instance of a Fragment with the given class name. This is the same as calling its empty constructor.
instantiate(...)
methods 在 API 28 中已弃用。另外,我不认为这是创建片段的好方法。
虽然@yydl 给出了为什么 newInstance
方法更好的令人信服的理由:
如果 Android 决定稍后重新创建您的 Fragment,它将调用您的 Fragment 的无参数构造函数。所以重载构造函数不是一个解决方案。
仍然很有可能使用构造函数。要了解这是为什么,首先我们需要了解 Android 使用上述解决方法的原因。
在使用片段之前,需要一个实例。 Android 调用 YourFragment()
(no arguments 构造函数)来构造片段的实例。在这里,您编写的任何重载构造函数都将被忽略,因为 Android 无法知道使用哪一个。
在 Activity 的生命周期中,片段被如上所述创建并被 Android 多次销毁。这意味着如果你把数据放在fragment对象本身,一旦fragment被销毁,数据就会丢失。
作为解决方法,android 要求您使用 Bundle
(调用 setArguments()
)存储数据,然后可以从 YourFragment
访问。参数 bundle
受 Android 保护,因此保证是persistent。
设置此捆绑包的一种方法是使用静态 newInstance
方法:
public static YourFragment newInstance (int data) {
YourFragment yf = new YourFragment()
/* See this code gets executed immediately on your object construction */
Bundle args = new Bundle();
args.putInt("data", data);
yf.setArguments(args);
return yf;
}
但是,构造函数:
public YourFragment(int data) {
Bundle args = new Bundle();
args.putInt("data", data);
setArguments(args);
}
可以做与 newInstance
方法完全相同的事情。
当然,这会失败,这也是 Android 希望您使用 newInstance
方法的原因之一:
public YourFragment(int data) {
this.data = data; // Don't do this
}
作为进一步的解释,这里是 Android 的 Fragment 类:
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
请注意,Android 要求仅在构造时设置参数,并保证将保留这些参数。
编辑:正如@JHH 在评论中指出的那样,如果您提供需要一些参数的自定义构造函数,那么 Java 不会为您的片段提供 no arg默认构造函数。因此,这需要您定义一个 no arg 构造函数,这是您可以使用 newInstance
工厂方法避免的代码。
编辑:Android 不再允许对片段使用重载的构造函数。您必须使用 newInstance
方法。
一些科特林代码:
companion object {
fun newInstance(first: String, second: String) : SampleFragment {
return SampleFragment().apply {
arguments = Bundle().apply {
putString("firstString", first)
putString("secondString", second)
}
}
}
}
你可以用这个来争论:
val first: String by lazy { arguments?.getString("firstString") ?: "default"}
val second: String by lazy { arguments?.getString("secondString") ?: "default"}
@JvmStatic
注释是最佳做法吗? @JvmStatic fun newInstance(bundle: Bundle) = SomeFragment().apply { arguments = bundle }
我不同意 yydi answer 说:
如果 Android 决定稍后重新创建您的 Fragment,它将调用您的 Fragment 的无参数构造函数。所以重载构造函数不是一个解决方案。
我认为这是一个解决方案,一个很好的解决方案,这正是它由Java核心语言开发的原因。
确实,Android 系统可以摧毁并重新创建您的 Fragment
。所以你可以这样做:
public MyFragment() {
// An empty constructor for Android System to use, otherwise exception may occur.
}
public MyFragment(int someInt) {
Bundle args = new Bundle();
args.putInt("someInt", someInt);
setArguments(args);
}
它将允许您从后面的 getArguments()
中提取 someInt
,即使 Fragment
已被系统重新创建。这是比 static
构造函数更优雅的解决方案。
我认为 static
构造函数是无用的,不应使用。如果将来您想扩展此 Fragment
并向构造函数添加更多功能,它们也会限制您。使用 static
构造函数,您不能这样做。
更新:
Android 添加了检查,可将所有非默认构造函数标记为错误。出于上述原因,我建议禁用它。
我最近在这里。但我刚刚知道的一些事情可能会对你有所帮助。
如果您使用的是 Java,则没有什么可改变的。但是对于 Kotlin 开发人员来说,我认为下面的一些片段可以让你成为一个可以运行的地下室:
父片段:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
return T::class.java.newInstance().apply {
arguments = Bundle().also { it.putString("key_text_arg", text) }
}
}
正常通话
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
您可以通过以下方式扩展子片段类中的父初始化操作:
fun newInstance(): ChildSampleFragment {
val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
// Do anything with the current initialized args bundle here
// with child.arguments = ....
return child
}
快乐编码。
在 android 中使用参数实例化片段的最佳实践是在片段中使用静态工厂方法。
public static MyFragment newInstance(String name, int age) {
Bundle bundle = new Bundle();
bundle.putString("name", name);
bundle.putInt("age", age);
MyFragment fragment = new MyFragment();
fragment.setArguments(bundle);
return fragment;
}
您应该避免使用片段实例设置您的字段。因为每当android系统重新创建你的片段时,如果它觉得系统需要更多的内存,它就会使用不带参数的构造函数重新创建你的片段。
您可以在此处找到有关 best practice to instantiate fragments with arguments 的更多信息。
由于关于最佳实践的问题,我要补充一点,在使用某些 REST Web 服务时使用混合方法创建片段通常是个好主意
我们不能传递复杂的对象,例如一些用户模型,以显示用户片段
但是我们可以做的是检查 onCreate
该用户!=null,如果不是 - 然后将他从数据层带来,否则 - 使用现有的。
通过这种方式,我们获得了在 Android 进行片段重新创建的情况下通过 userId 重新创建的能力和用户操作的敏捷性,以及通过持有对象本身或仅它的 id 来创建片段的能力
像这样的东西:
public class UserFragment extends Fragment {
public final static String USER_ID="user_id";
private User user;
private long userId;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userId = getArguments().getLong(USER_ID);
if(user==null){
//
// Recreating here user from user id(i.e requesting from your data model,
// which could be services, direct request to rest, or data layer sitting
// on application model
//
user = bringUser();
}
}
public static UserFragment newInstance(User user, long user_id){
UserFragment userFragment = new UserFragment();
Bundle args = new Bundle();
args.putLong(USER_ID,user_id);
if(user!=null){
userFragment.user=user;
}
userFragment.setArguments(args);
return userFragment;
}
public static UserFragment newInstance(long user_id){
return newInstance(null,user_id);
}
public static UserFragment newInstance(User user){
return newInstance(user,user.id);
}
}
User user = /*...*/
将用户放入包中:{3 } 并从参数中获取用户:User user = getArguments().getParcelable("some_user");
对象必须实现 Parcelable 接口。 link
使用此代码 100% 解决您的问题
在 firstFragment 中输入此代码
public static yourNameParentFragment newInstance() {
Bundle args = new Bundle();
args.putBoolean("yourKey",yourValue);
YourFragment fragment = new YourFragment();
fragment.setArguments(args);
return fragment;
}
此示例发送布尔数据
并在 SecendFragment
yourNameParentFragment name =yourNameParentFragment.newInstance();
Bundle bundle;
bundle=sellDiamondFragments2.getArguments();
boolean a= bundle.getBoolean("yourKey");
第一个片段中的值必须是静态的
快乐代码
实例化片段的最佳方法是使用默认的 Fragment.instantiate 方法或创建工厂方法来实例化片段注意:始终在片段中创建一个空的构造函数,而恢复片段内存将引发运行时异常。
你可以像这样使用 smth:
val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, YourFragment::class.java.name)
因为这个 answer 现在已弃用
理想情况下,我们不应该在片段构造函数中传递任何东西,片段构造函数应该是空的或默认的。现在第二个问题是,如果我们要传递接口变量或参数怎么办——我们应该使用 Bundle 来传递数据。对于Interface,我们可以将Parceble放入bundle中并使该接口实现parceble如果可能的话,我们可以在活动和片段中实现该接口,我们可以在OnAttach中初始化监听器,其中我们有上下文[(上下文)监听器]。
这样在配置更改(例如字体更改)期间,Activity 重新创建侦听器不会转到 uninitialize
,我们可以避免空指针异常。
setArguments()
没用。它只会带来混乱。
public class MyFragment extends Fragment {
public String mTitle;
public String mInitialTitle;
public static MyFragment newInstance(String param1) {
MyFragment f = new MyFragment();
f.mInitialTitle = param1;
f.mTitle = param1;
return f;
}
@Override
public void onSaveInstanceState(Bundle state) {
state.putString("mInitialTitle", mInitialTitle);
state.putString("mTitle", mTitle);
super.onSaveInstanceState(state);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
if (state != null) {
mInitialTitle = state.getString("mInitialTitle");
mTitle = state.getString("mTitle");
}
...
}
}
onViewCreated
范围的字段。我想这很方便,有很多方法可以做同样的事情。这也是一种检查用户更新的简便方法(比较 getArguments
的捆绑包和 onSaveInstanceState
的捆绑包)
getArguments
的东西。 onViewCreated
范围呢?我们可以在那里恢复状态包。但我更喜欢让 onCreateView
轻而快,并在 onActivityCreated
内进行所有繁重的初始化,因为 Fragment.getActivity()
有时喜欢返回 null
并且因为新版本的 API 23 中的 onAttach()
发生了变化。
set
和 get
Arguments
进入 saveInstanceState
。您基本上在做与“幕后”相同的事情
saveInstanceState
是“在幕后”。使用 Arguments
是重复的功能,让您仔细检查:首先是 Arguments
值,然后是 saveInstanceState
值。因为您必须以任何方式使用 saveInstanceState
。 Arguments
怎么样...它们不是必需的。
我相信我有一个更简单的解决方案。
public class MyFragment extends Fragment{
private String mTitle;
private List<MyObject> mObjects;
public static MyFragment newInstance(String title, List<MyObject> objects)
MyFragment myFrag = new MyFragment();
myFrag.mTitle = title;
myFrag.mObjects = objects;
return myFrag;
}