有没有一种方法类似于开始片段的结果?


91

我目前在叠加中有一个片段。这是用于登录服务。在电话应用程序中,我要在叠加层中显示的每个步骤都是他们自己的屏幕和活动。登录过程分为3个部分,每个部分都有自己的活动,这些活动由startActivityForResult()调用。

现在,我想使用片段和叠加来做同样的事情。叠加层将显示与每个活动相对应的片段。问题在于这些片段托管在Honeycomb API的活动中。我可以使第一个片段正常工作,但随后需要startActivityForResult(),这是不可能的。是否有类似startFragmentForResult()的地方,在这里我可以开始一个新的片段,完成后将结果返回到先前的片段?

Answers:


57

所有片段都生活在活动中。为结果启动片段没有多大意义,因为包含它的Activity始终可以访问它,反之亦然。如果片段需要传递结果,则可以访问其活动并设置其结果并完成它。在单个活动中交换片段的情况下,两个片段仍然可以访问该活动,并且所有消息传递都可以简单地通过活动。

只要记住,您始终在Fragment及其Activity之间进行通信。开始和结束是活动之间进行通信的机制-活动可以将任何必要的信息委派给它们的片段。


11
此外,从另一个片段加载一个片段时,您可以设置目标片段,并根据需要调用父片段的onActivityResult方法。
PJL

3
您的答案不完整,片段正在使用生命周期,例如活动。因此,我们不能仅仅通过方法传递参数,而应该使用包来传递值。即使片段在某个位置设置了值,我们也需要知道他何时结束。前一个片段在启动/恢复时是否应该获取值?这是一个主意。但是没有适当的方法来存储值,该片段可能被多个其他片段/活动调用。
Loenix '16

59

如果您愿意,可以使用一些片段之间的通信方法,

setTargetFragment(Fragment fragment, int requestCode)
getTargetFragment()
getTargetRequestCode()

您可以使用这些回调。

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}

我尚未确认,但也许可以。因此,您应该注意因滥用循环引用而导致的内存泄漏setTargetFragment()
nagoya0 2012年

3
迄今为止最好的解决方案!当具有一个“活动”和一堆片段时,如果试图找到要通知的正确片段将是一场噩梦,则效果很好。谢谢
Damien Praca

1
@ userSeven7s这不适用于替换案例,但应用于隐藏案例
Muhammad Babar 2014年

1
如果我们对目标片段使用WeakReference,则@ nagoya0会做得更好
quangson91

11

我们可以简单地在片段之间共享相同的ViewModel

共享视图模型

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

class SharedViewModel : ViewModel() {

    val stringData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

}

第一片段

import android.arch.lifecycle.Observer
import android.os.Bundle
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FirstFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        sharedViewModel.stringData.observe(this, Observer { dateString ->
            // get the changed String
        })

    }

}

SecondFragment

import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGrou

class SecondFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        changeString()
    }

    private fun changeString() {
        sharedViewModel.stringData.value = "Test"
    }

}

2
嗨,Levon,我认为以上代码不会共享相同的视图模型实例。您正在为ViewModelProviders.of(this).get(SharedViewModel :: class.java)传递this(片段)。这将为片段创建两个单独的实例。您需要传递活动ViewModelProviders.of(activity).get(SharedViewModel :: class.java)
Shailendra Patil,

@ShailendraPatil不错,我现在将修复它。
Levon Petrosyan

4

我的2美分。

我通过使用隐藏和显示/添加(现有/新)将旧片段与新片段交换来在片段之间切换。因此,此答案适用于像我一样使用片段的开发人员。

然后,我使用该onHiddenChanged方法知道旧片段已从新片段切换回。请参见下面的代码。

在离开新片段之前,我在一个全局参数中设置一个结果,以供旧片段查询。这是一个非常幼稚的解决方案。

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) return;
    Result result = Result.getAndReset();
    if (result == Result.Refresh) {
        refresh();
    }
}

public enum Result {
    Refresh;

    private static Result RESULT;

    public static void set(Result result) {
        if (RESULT == Refresh) {
            // Refresh already requested - no point in setting anything else;
            return;
        }
        RESULT = result;
    }

    public static Result getAndReset() {
        Result result = RESULT;
        RESULT = null;
        return result;
    }
}

什么是getAndReset()方法?
EpicPandaForce

onResume()第二个片段被解散时,第一个片段也不会被调用吗?
PJ_Finnegan '19

4

最近,Google刚刚添加了一项新功能FragmentManager,使其FragmentManager能够充当片段结果的中央存储。我们可以轻松地在Fragment之间来回传递数据。

起始片段。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setResultListener("requestKey") { key, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result...
    }
}

我们想要返回结果的片段。

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setResult("requestKey", bundleOf("bundleKey" to result))
}

该摘录摘自Google的官方文档。 https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

在编写此答案的数据时,此功能仍处于alpha状态。您可以使用此依赖项进行尝试。

androidx.fragment:fragment:1.3.0-alpha05

2

在您的片段中,您可以调用getActivity()。这将使您能够访问创建该片段的活动。从那里,您可以调用您的customize方法来设置值或传递值。


1

有一个Android库-FlowR,可让您启动片段以获得结果。

为结果开始片段。

Flowr.open(RequestFragment.class)
    .displayFragmentForResults(getFragmentId(), REQUEST_CODE);

处理会导致调用片段。

@Override
protected void onFragmentResults(int requestCode, int resultCode, Bundle data) {
    super.onFragmentResults(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            demoTextView.setText("Result OK");
        } else {
            demoTextView.setText("Result CANCELED");
        }
    }
}

在片段中设置结果。

Flowr.closeWithResults(getResultsResponse(resultCode, resultData));

1

使用接口(和Kotlin)的解决方案。核心思想是定义一个回调接口,在您的活动中实现它,然后从片段中调用它。

首先,创建一个接口ActionHandler

interface ActionHandler {
    fun handleAction(actionCode: String, result: Int)
}

接下来,从您的孩子(在本例中为您的片段)中调用此方法:

companion object {
    const val FRAGMENT_A_CLOSED = "com.example.fragment_a_closed"
}

fun closeFragment() {
    try {
        (activity as ActionHandler).handleAction(FRAGMENT_A_CLOSED, 1234)
    } catch (e: ClassCastException) {
        Timber.e("Calling activity can't get callback!")
    }
    dismiss()
}

最后,在您的父级中实现此功能以接收回调(在本例中为您的Activity):

class MainActivity: ActionHandler { 
    override fun handleAction(actionCode: String, result: Int) {
        when {
            actionCode == FragmentA.FRAGMENT_A_CLOSED -> {
                doSomething(result)
            }
            actionCode == FragmentB.FRAGMENT_B_CLOSED -> {
                doSomethingElse(result)
            }
            actionCode == FragmentC.FRAGMENT_C_CLOSED -> {
                doAnotherThing(result)
            }
        }
    }

0

传回数据的最简单方法是setArgument()。例如,您有fragment1调用fragment2,而fragment1调用fragment3,fragment1-> framgnet2-> fargment3

在fragment1中

public void navigateToFragment2() {
    if (fragmentManager == null) return;

    Fragment2 fragment = Fragment2.newInstance();
    String tag = "Fragment 2 here";
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .add(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commitAllowingStateLoss();
}

在fragment2中,我们照常称呼fragment3

private void navigateToFragment3() {
    if (fragmentManager == null) return;
    Fragment3 fragment = new Fragment3();
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .replace(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commit();
}

当我们在fragment3中完成任务时,现在我们这样调用:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
if (fragmentManager == null) return;
fragmentManager.popBackStack();
Bundle bundle = new Bundle();
bundle.putString("bundle_filter", "data");
fragmentManager.findFragmentByTag("Fragment 2 here").setArguments(bundle);

现在在fragment2中,我们可以轻松地调用参数

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}

请谨慎使用它,在某些情况下它可以工作,但是如果您的片段具有原始参数(在此示例中为“ fragment 2”),则它将覆盖用于创建该片段的原始参数,如果片段被销毁并重新创建。
Juan

0

根据您的体系结构,您可以做的另一件事是在片段之间使用共享的ViewModel。因此,在我的情况下,FragmentA是表单,而FragmentB是项目选择视图,用户可以在其中搜索和选择项目,并将其存储在ViewModel中。然后,当我回到FragmentA时,信息已经存储了!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.