[示例]动态片段,片段间通信,片段重新添加到屏幕问题(片段保留)

时间:2018-04-05 01:13:44

标签: java android android-fragments

所以,在学习android的基础知识时,FragmentNavigation DrawersViewPager,创建动态视图等许多地方出现了这个词PreferencesActivity。所以我开始阅读它,结果证明这是Android平台的一个非常重要的部分。它通过允许应用程序以更好的方式在不同大小的屏幕上传播数据来增强用户体验。

例如,像Gmail这样的应用会在小屏幕设备上显示多个“页面”的数据(请查看下面的图片以获得更好的主意),并将在平板电脑等大型设备的单个页面中显示所有数据。


所以通常出现的问题是“片段是如何做到的?”以及“它如何管理其神奇的点击,在小屏幕设备中完全改变布局,但只是在大屏幕设备中更改其数据?”那么当我们在Activity布局方面进行思考时,这是一个非常困难的问题。

因此,经过一番搜索和阅读后,我能够成功地了解片段背后的理论,LifeCycles和静态片段(Fragment Documentation)。

我现在希望实现片段的实际应用,例如gmail,其中包含adding fragments programmically 的概念。所以我设计了一个小任务,帮助我学习动态片段和片段片段/片段活动通信的概念。

任务

  • 将有一个基本应用程序,其中1 MainActivity具有LinearLayout作为其根视图组。
  • 2片段:FragmentA(或片段1或粉红色背景片段)和FragmentB(或frag2 / BlueFragment)将在首次运行期间添加到MainActivity中。
  • 片段应该能够在剩下的时间内操纵其他片段的可见性,因此每个片段将有2个按钮open Other fragmentClose other fragment
  • 点击片段2中的open frag1时,片段1应添加到屏幕或不执行任何操作(如果已存在)。类似地,如果fragment1已经存在或什么也不做,那么在片段2中按close frag1应该能够删除片段1。

目标图片样本:

  • 显示2个片段的活动,粉红色片段和蓝色片段(忽略大小,正在测试linearlayout#weight):
    sample3

  • 相同的活动现在只显示fragment1,因为用户在close fragment 2内按了pink fragment。因此布局动态变化:
    sample

(同样可以应用于第二个片段)

成就:嗯,这样做之后,我能够更好地了解片段间通信,我想我现在知道Gmail中的那些“神奇点击”如何更改数据/布局完全不同的片段。

我认为在这里提出一系列我可能的失败尝试是不明智的,所以我只是提到我在解决这个任务时遇到的其他一些问题:

问题
1.片段无法相互通信(片段1中按下的按钮无法关闭片段2)
2.片段在旋转时多次重新添加到屏幕上 3.保存显示内容:启动App时,默认显示片段1。在会话期间,根据用户的输入,该布局可以改变为显示的2个片段或仅显示片段'2'。但是,如果用户旋转他的电话,则不保存屏幕上显示的内容,并且活动开始显示片段1。

1 个答案:

答案 0 :(得分:3)

第0步:创建片段,其他文件
在onCreateView()中创建fragment.xml,fragment.java扩展片段和膨胀布局。(对于两个片段)

public class Frag_b extends Fragment {

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_frag_b, container, false);
    }

}

步骤(1)动态碎片:
正如我们所知,当应用程序启动时,androidOS首先调用要显示的活动(具有"android.intent.action.MAIN"权限的活动)。因此,要在运行时显示片段,我们使用一个名为FragmentManager的类(了解有关其功能的更多信息here。因此,要附加片段:
1 - 在主要活动中创建片段和片段管理器的全局对象 2 - 创建全局片段标记:它们对于相同片段对象的可恢复性非常重要。它们将用作标记来访问/识别字符串。
3 - 通过片段管理器添加:所以这里是片段管理器的第一个用例:动态添加片段(在运行时)。这是代码:

public class MainActivity extends AppCompatActivity implements FragAHandler,FragBHandler {
    LinearLayout layRoot;

    FragmentManager manager=getSupportFragmentManager();

    public static  final String FRAG_A_TAG="FRAG_A";
    public static  final String FRAG_B_TAG="FRAG_B";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layRoot=findViewById(R.id.layout_root);
        manager.beginTransaction().add(R.id.layout_root,new Frag_a(),FRAG_A_TAG).commit();
        manager.beginTransaction().add(R.id.layout_root,new Frag_b(),FRAG_B_TAG).commit();

    }

}

第2步片段沟通:
here所示,片段通信是一个4层的过程:
 *接口定义函数(片段想要执行的任务)  *活动实施它们  *片段获取此接口的实例(以活动的上下文形式)  * Fragment根据需要使用此实例 {在片段片段通信的情况下,添加了2个更小的​​步骤}:
 *片段向活动发送信号  *然后活动处理信号并将其发送到其他片段。

因此,如果片段FragA希望主活动在按钮单击时显示通知(存在于片段内):

  • 创建一个具有函数void showNotif()的接口FragAcall。
  • 在MainActivity中实现它。(并定义您希望活动在接收来自片段A .i.e的点击时应执行的任何操作,显示通知)。
  • 在fragA中创建一个接口FragAcall(FragAcall callObj)的对象。
  • 在fragA中,覆盖名为onAttach(context)的方法。将此上下文广播到FragAcall'Object。
  • 在单击侦听器的按钮内,调用callObj.showNotiff()。

类似于双向片段片段通信,我使用以下步骤实现了代码:  *创建接口FragAHandler和FragBHandler:

public interface FragAHandler {
    void addFrag1();
    void closeFrag1();
    }
public interface FragBHandler {
    void addFrag2();
    void closeFrag2();
    }
  • 在主要活动中实施这些:

    public class MainActivity extends AppCompatActivity implements FragAHandler,FragBHandler
    {
    
    LinearLayout layRoot;
    FragmentManager manager=getSupportFragmentManager();
    public static final String FRAG_A_TAG = "FRAG_A";
    public static final String FRAG_B_TAG = "FRAG_B";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layRoot = findViewById(R.id.layout_root);
        manager.beginTransaction().add(R.id.layout_root, new Frag_a(), FRAG_A_TAG).commit();
    }
    
    @Override
    public void addFrag1() {
        if (manager.findFragmentByTag(FRAG_A_TAG) == null) {
            manager.beginTransaction().add(R.id.layout_root, new Frag_a(), FRAG_A_TAG).commit();
        } else {
            Toast.makeText(MainActivity.this, "frag1 Already present", Toast.LENGTH_SHORT);
        }
    }
    
    @Override
    public void closeFrag1() {
        Frag_a fragA = (Frag_a) manager.findFragmentByTag(FRAG_A_TAG);
        if (fragA != null) {
            manager.beginTransaction().remove(fragA).commit();
        } else {
            Toast.makeText(MainActivity.this, "frag1 Already not there", Toast.LENGTH_SHORT);
        }
    
    }
    
    @Override
    public void addFrag2() {
    
        if (manager.findFragmentByTag(FRAG_B_TAG) == null) {
            manager.beginTransaction().add(R.id.layout_root, new Frag_b(), FRAG_B_TAG).commit();
        } else {
            Toast.makeText(MainActivity.this, "frag2 Already present", Toast.LENGTH_SHORT);
        }
    }
    
    @Override
    public void closeFrag2() {
        Frag_b frag_b = (Frag_b) manager.findFragmentByTag(FRAG_B_TAG);
    
        if (frag_b != null) {
            manager.beginTransaction().remove(frag_b).commit();
        } else {
            Toast.makeText(MainActivity.this, "frag2 Already not there", Toast.LENGTH_SHORT);
    
        }
    
    }
    }
    

在此代码中,manager.findFragByTag()用于检查已经显示或未显示片段的天气,因为我们只想打开另一个片段的单个实例。有关详细用途,请参阅this

  • 在内部片段中,将mainActivity的类上下文附加到处理程序obj并在按钮内使用它:

    public class Frag_a extends Fragment {
    
        FragBHandler fragBHandler;
    
        public Frag_a() {
            // Required empty public constructor
        }
    
        @Override
        public void onAttach(Context context) {
            // context is the activity's context.
            super.onAttach(context);
    
            try {
                // This makes sure that the container activity has implemented
                // the callback interface. If not, it throws an exception
                fragBHandler = (FragBHandler) context;
            } catch (ClassCastException e) {
                e.printStackTrace();
                Log.e("", "onAttach: class has not implemented fragAhandler");
            }
        }
        // THIS SHOULD NEVER BE AN APPROCH.
        // public Frag_a(FragBHandler fragBHandler) {
        // this.fragBHandler = fragBHandler;
        // }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            View v = inflater.inflate(R.layout.fragment_frag_a, container, false);
            v.findViewById(R.id.bt_open_frag2).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    openFrag2();
                }
            });
            v.findViewById(R.id.bt_close_frag2).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    closeFrag2();
                }
            });
    
            return v;
        }
    
        public void closeFrag2() {
            fragBHandler.closeFrag2();
    
        }
    
        public void openFrag2() {
            fragBHandler.addFrag2();
    
        }
    
    }
    

VOILA,BOTH FRAGMENTS现在可以正常谈话!
**第3部分:片段复制**
这是最困难的任务之一。到目前为止,我的目标是创建一个带有2个片段通信的应用程序。但是当我旋转手机时,出现了一个重大失败:屏幕上出现的任何内容都被创建了多个倍!

我通过以下方式找到了解决方案:
  - 在onCreate()期间为frag1和2使用一个公共对象,并通过检查它是否已存在于后端堆栈中,然后仅使用该对象再次添加。
  - 检查后栈后也添加初始片段。

所以我的mainActivity代码的最终版本变为:

    public class MainActivity extends AppCompatActivity implements FragAHandler, FragBHandler {
        LinearLayout layRoot;
        FragmentManager manager = getSupportFragmentManager();


        Frag_a fragmentObjA;
        Frag_b fragmentObjB;
        public static final String FRAG_A_TAG = "FRAG_A";
        public static final String FRAG_B_TAG = "FRAG_B";


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

            initialise();

            if (manager.findFragmentByTag(FRAG_A_TAG) == null && manager.findFragmentByTag(FRAG_B_TAG) == null) {
                //both are imp because of testcase not giving exact answer:
                // >>>open app: shows frag1>'press open frag2'>>>frg2 opens >'close frag 1' >>> rotate.
                //Expected output >> "fragment 2(already present object in backstack and on screen) to remain on screen
                //output recieved : both frag 1 and frg2 showing
                manager.beginTransaction().add(R.id.layout_root, fragmentObjA, FRAG_A_TAG)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_ENTER_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag1 Already present or fragment 2 present", Toast.LENGTH_SHORT).show();
            }

        }

        private void initialise() {
            layRoot = findViewById(R.id.layout_root);

            if(manager.findFragmentByTag(FRAG_A_TAG)!=null){
                fragmentObjA= (Frag_a) manager.findFragmentByTag(FRAG_A_TAG);
                Log.e(">>", "initialise: frgement A object recieved from the frag manager is used" );

            }
            else{
                Log.e(">>", "initialise:new frag a object created" );
                fragmentObjA=new Frag_a();

            }
            if(manager.findFragmentByTag(FRAG_B_TAG)!=null){
                fragmentObjB= (Frag_b) manager.findFragmentByTag(FRAG_B_TAG);

                Log.e(">>", "initialise: frgement B object recieved from the frag manager is used" );
            }
            else{
                Log.e(">>", "initialise:new frag object created" );
                fragmentObjB=new Frag_b();
            }
        }


        //-----------handler methods-------------------
        @Override
        public void addFrag1() {
            if (manager.findFragmentByTag(FRAG_A_TAG) == null) {
                manager.beginTransaction().add(R.id.layout_root, fragmentObjA, FRAG_A_TAG)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_ENTER_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag1 Already present", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void closeFrag1() {
            Frag_a fragA = (Frag_a) manager.findFragmentByTag(FRAG_A_TAG);
            if (fragA != null) {
                manager.beginTransaction().remove(fragA)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_EXIT_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag1 Already not there", Toast.LENGTH_SHORT).show();

            }

        }

        @Override
        public void addFrag2() {

            if (manager.findFragmentByTag(FRAG_B_TAG) == null) {
                manager.beginTransaction().add(R.id.layout_root, fragmentObjB, FRAG_B_TAG)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_ENTER_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag2 Already present", Toast.LENGTH_SHORT).show();
            }


        }

        @Override
        public void closeFrag2() {
            Frag_b frag_b = (Frag_b) manager.findFragmentByTag(FRAG_B_TAG);

            if (frag_b != null) {
                manager.beginTransaction().remove(frag_b)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_EXIT_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag2 Already not there", Toast.LENGTH_SHORT).show();

            }

        }

    }