Android fragment全面深入解析三

转载请注明:
http://blog.csdn.net/sinat_30276961/article/details/48466719

上一篇Android fragment全面深入解析二讲解了fragment的一些系统自带的子类,FragmentManager和FragmentTransaction,并通过实例讲解了事务的堆栈和过渡动画。本篇将继续讲解fragment里的特性。

fragment实战

与activity之间的通信

在fragment执行完onAttach之后,该fragment就和activity已经建立了关系。我们可以通过在fragment里调用getActivity()轻松获取到宿主activity。

而在activity那边,可以通过上一篇讲到的FragmentManager.findFragmentByXXX方法获取到fragment。

activity通知fragment

这个就用一般的方式,在fragment里写个方法。然后只要activity那边获取到那个fragment对象(通过FragmentManager.findFragmentByXXX),再调用那个方法就行了。

那么,在宿主activity最初创建fragment时,怎么收到其他activity或者宿主activity需要传递的数据呢?

Android studio的用户在这方面很方便,因为在创建fragment时,会有选项让你选择:

Android fragment全面深入解析三_第1张图片

上图中,红框框起来的两个选项,前者是用来自动添加fragment创建代码,后者用来添加与activity交互的接口,方便吧。我们来看看那些代码:

public class BlankFragment extends Fragment {
    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    private OnFragmentInteractionListener mListener;

    // TODO: Rename and change types and number of parameters
    public static BlankFragment newInstance(String param1, String param2) {
        BlankFragment fragment = new BlankFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        public void onFragmentInteraction(Uri uri);
    }

我只贴了我们现在关注的代码,可以看到,它用静态的newInstance来创建fragment,并通过setArguments给fragment传递数据。然后在onCreate()接收bundle数据。

setArguments这种方案是官方推荐的,所以以后就按这种方法写吧。如果是Android studio用户,就在它创建的代码基础上修改一下就行了。

当然,如果你的fragment是直接添加在布局里的,那么要接收其他activity启动宿主activity时传递过来的数据,就用getActivity().getIntent().getStringExtra这类方式好了,注意加好判空。

fragment通知activity

那么怎么实现fragment通知activity?比方说,fragment那边有收到用户的事件,然后fragment除了自己处理一些事情,还需要通知到activity。

估计会有很多童鞋立马会想到EventBus。没错,那个是很方便啦。只要在activity那里复写一个onEventMainThread(),然后在fragment那边post一个事件就行了。不过,在这里,我们还是用最原始的方法。

这部分代码其实在上面自动生动代码里有了,不过我再讲下。

很简单,直接上代码:

public class CallFragment extends Fragment {
    ......
    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        public void onFragmentInteraction();
    }

上述代码,先是在fragment里写个接口。

然后activity自然要实现这个接口:

public class CallbackToActivity extends AppCompatActivity implements CallFragment.OnFragmentInteractionListener{
    ......
    @Override
    public void onFragmentInteraction() {
        Toast.makeText(this, "get Callback from fragment", Toast.LENGTH_SHORT).show();
    }
}

那么在哪里关联呢,你可以通过传统的回调设置方式,在fragment写个setOnFragmentInteractionListener(OnFragmentInteractionListener listener),然后在activity那里获取fragment对象,并调用一下,fragment.setOnFragmentInteractionListener(this)。

当然,还有更简单的方法,通过使用fragment能获取到activity的特性,在fragment里把activity对象直接赋给listener就行了:

public class CallFragment extends Fragment {
    private OnFragmentInteractionListener mListener;
    ......
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

fragment之间的通信

讲完了activity与fragment之间的通信,接着讲讲同一个宿主下的fragment之间的通信。

有几个方案供你选择:

1.getActivity().getSupportFragmentManager().findFragmentByXXX
通过上面代码,我可以很轻松的找到我要交流的fragment。

2.getTargetFragment()。
这个只能找到一个fragment,且需要先setTargetFragment。

3.通过宿主activity中转,这个方案不推荐,太麻烦。

fragment添加menu

在fragment里也是可以添加menu的,这种设定大大方便了我们动态管理menu。

可以看到,在fragment里关于menu的操作几乎和activity里的一样:

Android fragment全面深入解析三_第2张图片

上图有两个关键的api没有显示,onOptionsItemSelected和onContextItemSelected。

OK,既然都差不多,那么调用方式也差不多吧。对的,如果activity里已经解析了一个menu,那么fragment就可以直接添加menu item:

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.add("Menu "+index).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    }

再贴一下上一篇的动画效果,你仔细看右上角,可以看到,不同的fragment显示不同的menu。

Android fragment全面深入解析三_第3张图片

需要注意的是,如果要在fragment收到某个menu item的反馈,宿主activity里的那个menu item监听要返回return super.onOptionsItemSelected(item);

fragment onActivityResult

在fragment也是有onActivityResult这个api的。这个和activity里的onActivityResult一样,唯一的区别在于在fragment里是没有setResult的,你需要通过getActivity().setResult(REQUEST, intent)。

No UI fragment & onSaveInstanceState

在上一篇,有说到,fragment可以是没有ui的,也就是说,这个fragment不提供view给activity,只提供劳动力。

因为fragment的生命周期和activity的生命周期息息相关,所以管理起来也很简单。并且它的开销很小,相比较service来说,明显划算。但它的弱点也很明显,就是只能存活于一个activity。

我通过一个简单的例子来讲解一下没有ui的fragment。

public class NoUIFragmentTest extends AppCompatActivity {
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_no_uifragment_test);
        mButton = (Button) findViewById(R.id.restart);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                NoUIFragment fragment =
                        (NoUIFragment) getSupportFragmentManager().findFragmentByTag("fragment");
                fragment.restart();
            }
        });
        if (savedInstanceState == null) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(new NoUIFragment(), "fragment");
            ft.commit();
        }
    }

上述代码,R.layout.activity_no_uifragment_test包含一个progressbar和button。

之所以加上if (savedInstanceState == null)的判断,是因为我要做横竖屏切换。横竖屏切换的时候,会调到onSaveInstanceState()。在那里,会把当前activity已经创建的所有fragment,事务backstack和fragment的state全部保存下来。然后在activity下次执行onCreate()时,全部重新创建fragments并还原它们的state。这部分如果有疑问,可以查看源码,你可以跟进去看下activity的onSaveInstanceState()和onCreate()。篇幅有限,就不展开讲了。

上面指的fragment的state不是指的fragment的状态,而是fragment里onSaveInstanceState的bundle数据。

这里,又体现了fragment的一个好处。就是当应用遇到意外情况(如:内存不足)由系统销毁一个Activity或者横竖屏切换等其他情况时,onSaveInstanceState() 被调用起来了。此时,已经创建的fragment和事务堆栈以及state会被保存下来,然后在下次activity启动时会自动还原。

所以说,上述代码,如果不加个if (savedInstanceState == null)判断,那么每次切屏,都会多创建一个fragment。虽然这个fragment没有ui,但这可不是个好想法,会导致内存泄露,所以一定要注意加判断。

ok,接着往下看代码:

    public static class NoUIFragment extends Fragment {
        int position = 0;
        boolean ready = false;
        boolean quiting = false;

        ProgressBar progressBar;

        final Runnable mRunnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (this) {
                        while (!ready || position >= 500) {
                            if (quiting)
                                return;
                            try {
                                wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        position++;
                        progressBar.setProgress(position);
                    }

                    synchronized (this) {
                        try {
                            wait(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };

        static NoUIFragment newInstance() {
            NoUIFragment fragment = new NoUIFragment();
            return fragment;
        }

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            new Thread(mRunnable).start();
            super.onCreate(savedInstanceState);

            if (savedInstanceState != null) {
                position = savedInstanceState.getInt("position", 0);
            }
        }

        public void restart() {
            synchronized (mRunnable) {
                position = 0;
                mRunnable.notify();
            }
        }

        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);

            progressBar = (ProgressBar) getActivity().findViewById(R.id.progress_horizontal);
            synchronized (mRunnable) {
                ready = true;
                mRunnable.notify();
            }
        }

        @Override
        public void onDetach() {
            super.onDetach();

            synchronized (mRunnable) {
                progressBar = null;
                ready = false;
                mRunnable.notify();
            }
        }

        @Override
        public void onDestroy() {
            super.onDestroy();

            synchronized (mRunnable) {
                ready = false;
                quiting = true;
                mRunnable.notify();
            }
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putInt("position", position);
        }
    }

代码有点长,不过逻辑不难,我稍微讲下。

在这个fragment里,我定义了一个Runnable的匿名内部类。这个Runnable就是用来不断增加进度条的。我在fragment的onCreate起一个thread执行它,然后它会在第一个同步块的wait()阻塞。然后,我在onActivityCreated里先获取progressbar对象,并唤醒阻塞。此时,progressbar就以每0.05秒加1格的速度增加。

这里,我在fragment里复写了onSaveInstanceState,然后就可以看到,在横竖屏切换时,progressbar不会从0开始,而是继续开始。

OK,来看下效果,横竖屏切换的话,只能你自己去测试,我这边不方便贴上来。

Android fragment全面深入解析三_第4张图片

fragment hide

我们再来看下FragmentTransaction.hide()。

这个hide,顾名思义就是隐藏。它的特性有点类似View.INVISIBLE,也就是说,这个view是存在的,只是不可见。

那这个API干嘛用,直接用View.setVisible()不就行了?试着再回忆一下前面几篇提到的fragment事务堆栈,想起了吧。再和这个关联起来,想到了吧。对的,可见不可见可以通过事务堆栈去控制了!

直接上代码看看吧:

先在activity里动态加入一个HideFragment

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

        if (savedInstanceState == null) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(R.id.container, new HideFragment(), "HideFragment");
            ft.commit();
        }
    }

然后在menu里加上响应代码,执行隐藏和保存事务到堆栈:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        if ("Next".equals(item.getTitle())) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.hide(getSupportFragmentManager().findFragmentByTag("HideFragment"));
            ft.replace(R.id.container, new CallFragment());
            ft.addToBackStack(null);
            ft.commit();
            return true;
        }else if ("Hide".equals(item.getTitle())) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.hide(getSupportFragmentManager().findFragmentByTag("HideFragment"));
            ft.addToBackStack(null);
            ft.commit();
            return true;
        }

“Next”的那个菜单可以先忽略。可以看到,在”Hide”菜单响应里,我先隐藏那个fragment,然后把这个事务加到堆栈里。

我们看下效果:

Android fragment全面深入解析三_第5张图片

在点击了”HIDE”菜单之后,HideFragment里的一个EditText就不可见了。然后点击BACK之后,EditText又可见了。

OK,在上一篇里有说到过:

如果你在事务里执行了remove动作,然后没有调用addBackToStack,那么remove的那个fragment就会在commit时被destroy掉了,用户就回退不到原先的fragment。但是如果你调用了addBackToStack,那么那个fragment只是被stop了,等用户按BACK就会变回resumed状态。

事实上,这句话没说全。我们再来看看这个例子。

    if ("Next".equals(item.getTitle())) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.hide(getSupportFragmentManager().findFragmentByTag("HideFragment"));
        ft.replace(R.id.container, new CallFragment());
//            ft.addToBackStack(null);
        ft.commit();
        return true;
    }

现在我们来看下”Next”菜单里的代码,这里,我把ft.addToBackStack(null)给注释掉了,我们来看看HideFragment那边打印的log:

Android fragment全面深入解析三_第6张图片

可以看到,HideFragment直接被destroy并且onDetach了。

然后把ft.addToBackStack(null)取消注释,再看log。

Android fragment全面深入解析三_第7张图片

可以看到,HideFragment执行到onDestroyView,后面onDestroy和onDetach这两个没有执行。也就是说加了addToBackStack之后,执行remove动作,那个fragment不会被销毁,但是view被销毁了。出栈的时候是重新去创建view的。

有些朋友就会有疑问,上面的代码里没有remove动作啊?事实上是有的,replace动作其实是remove+add这两个动作的结合。

这样一来,上述代码执行的结果就是,在HideFragment的EditText里输入了一些内容,然后点击Next,再点BACK,会发现内容没有了。那有什么办法可以不让它内容消失呢?

那就别做remove动作呗。把replace动作改成add动作就行了。当然还有另一个方案,在HideFragment的onCreateView里获取到Edittext,然后添加一个SaveEnable属性,这样就会保存下来。

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView");
        View view = inflater.inflate(R.layout.fragment_hide, container, false);
        TextView textView = (TextView) view.findViewById(R.id.edit_text);
        textView.setSaveEnabled(true);
        return view;
    }

效果如下:

Android fragment全面深入解析三_第8张图片

ok,关于fragment的深入解析就讲到这里。

代码入口

你可能感兴趣的:(Android控件)