Android Fragment学习与使用—高级篇

本文详细探讨了Android中Fragment的高级用法,包括回退栈管理、与Activity的通信、重叠问题解决、与ActionBar和MenuItem的交互、无布局Fragment、DialogFragment的使用、startActivityForResult以及FragmentPagerAdapter和FragmentStatePagerAdapter的区别。此外,还介绍了Fragment间数据传递的策略,为实际项目中高效管理Fragment提供了全面指导。

一、概述

上一篇已经说明了Fragment的生命周期,以及基础的使用方法和一些api的作用。但是想要在项目中使用好Fragment必须能够清晰明白的管理好它的状态,以下会介绍实际开发会遇到的一些场景。

二、Fragment回退栈管理

Activity是由任务栈管理的,遵循先进后出的原则,Fragment也可以实现类似的栈管理,从而实现多个Fragment先后添加后可以返回上一个Fragment,当activity容器内没有Fragment时回退则退出Activity。

具体方法:FragmentTransaction.addToBackStack(String) // 通常传入null即可

代码如下:

Fragment f = new Fragment();  
FragmentManager fm = getSupportFragmentManager();  
FragmentTransaction ftx = fm.beginTransaction();  
ftx.replace(R.id.fragment_container, f, "ONE");  
ftx.addToBackStack(null);  
ftx.commit();

注:
1.activity的第一个Fragment(根Fragment)可以不添加回退栈,这样最后一个Fragment按返回时就不会空白而是直接退出activity。
2.调用addToBackStack(null)将当前的事务添加到了回退栈,调用replace方法后Fragment实例不会被销毁,但是视图层次会被销毁,即会调用onDestoryView和onCreateView。若需保存当前fragment视图状态,则可以使用hide后add新的Fragment

三、Fragment与Activity通信

a、如果Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
b、如果Activity中未保存任何Fragment的引用,可以通过每个Fragment都有一个唯一的TAG或者ID使用getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。
c、在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。

注:如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。

推荐方式:
1.接口(Fragment返回数据给Activity)

Fragment部分代码:

public class TestFragment extends Fragment {

    private OnSaveListener listener;

    public void setListener(OnSaveListener listener) {
        this.listener = listener;
    }

    public interface OnSaveListener {
        void onSaveFinished(boolean result);

        void onSaveStart();
    }


    @OnClick(R.id.btn_save)
    public void save() {
        ....
        listener.onSaveFinished(true);
    }
}

Activity部分代码:

TestFragment f = new TestFragment();
f.setListener(new ShowCheckFragment.OnSaveListener() {

    @Override
    public void onSaveFinished(boolean result) {
        ......
    }

    @Override
    public void onSaveStart() {
        ......
    }
});

FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.fragment_container, f);
fragmentTransaction.commit();

2.Fragment Arguments(传递数据到Fragment中)

Fragment部分代码:


public class TestFragment extends Fragment  
{  

    private String mArgument;  
    public static final String ARGUMENT = "argument";  

    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);   
        Bundle bundle = getArguments();  
        if (bundle != null)  
            mArgument = bundle.getString(ARGUMENT);  

    }  

    /** 
     * 传入需要的参数,设置给arguments 
     * @param argument 
     * @return 
     */  
    public static TestFragment newInstance(String argument)  
    {  
        Bundle bundle = new Bundle();  
        bundle.putString(ARGUMENT, argument);  
        TestFragment f = new TestFragment();  
        f.setArguments(bundle);  
        return f;  
    }

Fragment添加newInstance静态方法给实例化时调用,将需要的参数传入,设置到bundle中,然后setArguments(bundle),最后在onCreate中进行获取(Activity之间的Intent调用也可以采取类似方式,详见郭霖大神的第一行代码Activity章节);

注:
setArguments方法必须在fragment创建以后,添加给Activity前完成。千万不要先调用了add,然后设置arguments。

四、Fragment重叠问题

当屏幕旋转或者内存重启(Fragment以及容器activity被系统回收后再打开时重新初始化)会导致Fragment重叠问题,是因为activity本身重启的时候会恢复Fragment,然后创建Fragment的代码又会新建一个Fragment的原因。

解决方法:在onCreate方法中判断参数Bundle savedInstanceState,为空时初始化Fragment实例,然后在Fragment中通过onSaveInstanceState的方法恢复数据

代码:

private TestFragment f;

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

        Log.e(TAG, savedInstanceState+"");  

        if(savedInstanceState == null)  
        {  
            f = new TestFragment();  
            FragmentManager fm = getSupportFragmentManager();  
            FragmentTransaction tx = fm.beginTransaction();  
            tx.add(R.id.id_content, f, "ONE");  
            tx.commit();  
        }  



    }  

五、Fragment与ActionBar和MenuItem

Fragment可以添加自己的MenuItem到Activity的ActionBar或者可选菜单中。
a、在Fragment的onCreate中调用 setHasOptionsMenu(true);
b、然后在Fragment类中实现onCreateOptionsMenu;
c、如果希望在Fragment中处理MenuItem的点击,也可以实现onOptionsItemSelected;Activity也可以直接处理该MenuItem的点击事件。

Fragment部分代码:

public class TestFragment extends Fragment  
{  

    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setHasOptionsMenu(true);  
    }  

    ......

    @Override  
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)  
    {  
        inflater.inflate(R.menu.fragment_menu, menu);  
    }  

    @Override  
    public boolean onOptionsItemSelected(MenuItem item)  
    {  
        switch (item.getItemId())  
        {  
        case R.id.id_menu_test:  
            ...... 
            break;  
        }  
        return true;  
    }  

} 

Activity代码:

    @Override  
    public boolean onCreateOptionsMenu(Menu menu)  
    {  
        super.onCreateOptionsMenu(menu);  
        getMenuInflater().inflate(R.menu.main, menu);  
        return true;  
    }  

    @Override  
    public boolean onOptionsItemSelected(MenuItem item)  
    {  
        switch (item.getItemId())  
        {  
        case R.id.action_settings:  
            ......  
            return true;  
        default:  
            //如果希望Fragment自己处理MenuItem点击事件,一定不要忘了调用super.xxx  
            return super.onOptionsItemSelected(item);  
        }  
    }  

注:如果要Fragmenr自己处理MenuItem点击事件,一定要调用super.xx

六、没有布局的Fragment—保存大量数据

主要用于处理异步请求带来的数据保存问题,尤其是异步请求未完成时屏幕旋转这种现象。步骤如下:
1、继承Fragment,声明引用指向你的有状态的对象
2、当Fragment创建时调用setRetainInstance(boolean)
3、把Fragment实例添加到Activity中
4、当Activity重新启动后,使用FragmentManager对Fragment进行恢复

Fragment部分代码:

public class TestFragment extends Fragment  
{  

    // data object we want to retain  
    // 保存一个异步的任务  
    private MyAsyncTask data;  

    // this method is only called once for this fragment  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        // retain this fragment  
        setRetainInstance(true);  
    }  

    public void setData(MyAsyncTask data)  
    {  
        this.data = data;  
    }  

    public MyAsyncTask getData()  
    {  
        return data;  
    }  


}

AsyncTask部分代码:

public class MyAsyncTask extends AsyncTask<Void, Void, Void>  
{  
    private FixProblemsActivity activity;  
    /** 
     * 是否完成 
     */  
    private boolean isCompleted;  
    /** 
     * 进度框 
     */  
    private LoadingDialog mLoadingDialog;  
    private List<String> items;  

    public MyAsyncTask(FixProblemsActivity activity)  
    {  
        this.activity = activity;  
    }  

    /** 
     * 开始时,显示加载框 
     */  
    @Override  
    protected void onPreExecute()  
    {  
        // 使用DialogFragment创建对话框
        mLoadingDialog = new LoadingDialog();  
        mLoadingDialog.show(activity.getFragmentManager(), "LOADING");  
    }  

    /** 
     * 加载数据 
     */  
    @Override  
    protected Void doInBackground(Void... params)  
    {  
        items = loadingData();  
        return null;  
    }  

    /** 
     * 加载完成回调当前的Activity 
     */  
    @Override  
    protected void onPostExecute(Void unused)  
    {  
        isCompleted = true;  
        notifyActivityTaskCompleted();  
        if (mLoadingDialog != null)  
            mLoadingDialog.dismiss();  
    }  

    public List<String> getItems()  
    {  
        return items;  
    }  

    private List<String> loadingData()  
    {  
        try  
        {  
            Thread.sleep(5000);  
        } catch (InterruptedException e)  
        {  
        }  
        return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",  
                "onSaveInstanceState保存数据",  
                "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",  
                "Spark"));  
    }  

    /** 
     * 设置Activity,因为Activity会一直变化,在onDestroy中set null
     *  
     * @param activity 
     */  
    public void setActivity(FixProblemsActivity activity)  
    {  
        // 如果上一个Activity销毁,将与上一个Activity绑定的DialogFragment销毁  
        if (activity == null)  
        {  
            mLoadingDialog.dismiss();  
        }  
        // 设置为当前的Activity  
        this.activity = activity;  
        // 开启一个与当前Activity绑定的等待框  
        if (activity != null && !isCompleted)  
        {  
            mLoadingDialog = new LoadingDialog();  
            mLoadingDialog.show(activity.getFragmentManager(), "LOADING");  
        }  
        // 如果完成,通知Activity  
        if (isCompleted)  
        {  
            notifyActivityTaskCompleted();  
        }  
    }  

    private void notifyActivityTaskCompleted()  
    {  
        if (null != activity)  
        {  
            activity.onTaskCompleted();  
        }  
    }  

}  

Activity部分代码:

public class FixProblemsActivity extends ListActivity  
{  
    private static final String TAG = "MainActivity";  
    private ListAdapter mAdapter;  
    private List<String> mDatas;  
    private OtherRetainedFragment dataFragment;  
    private MyAsyncTask mMyTask;  

    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        Log.e(TAG, "onCreate");  

        // find the retained fragment on activity restarts  
        FragmentManager fm = getFragmentManager();  
        dataFragment = (OtherRetainedFragment) fm.findFragmentByTag("data");  

        // create the fragment and data the first time  
        if (dataFragment == null)  
        {  
            // add the fragment  
            dataFragment = new OtherRetainedFragment();  
            fm.beginTransaction().add(dataFragment, "data").commit();  
        }  
        mMyTask = dataFragment.getData();  
        if (mMyTask != null)  
        {  
            mMyTask.setActivity(this);  
        } else  
        {  
            mMyTask = new MyAsyncTask(this);  
            dataFragment.setData(mMyTask);  
            mMyTask.execute();  
        }  
        // the data is available in dataFragment.getData()  
    }  


    @Override  
    protected void onRestoreInstanceState(Bundle state)  
    {  
        super.onRestoreInstanceState(state);  
        Log.e(TAG, "onRestoreInstanceState");  
    }  


    @Override  
    protected void onSaveInstanceState(Bundle outState)  
    {  
        mMyTask.setActivity(null);  
        super.onSaveInstanceState(outState);  
        Log.e(TAG, "onSaveInstanceState");  
    }  

    @Override  
    protected void onDestroy()  
    {  
        Log.e(TAG, "onDestroy");  
        super.onDestroy();  

    }  
    /** 
     * 回调 
     */  
    public void onTaskCompleted()  
    {  
        mDatas = mMyTask.getItems();  
        mAdapter = new ArrayAdapter<String>(FixProblemsActivity.this,  
                android.R.layout.simple_list_item_1, mDatas);  
        setListAdapter(mAdapter);  
    }  

}  

七、DialogFragment的使用

和Fragment有着基本一致的声明周期。且DialogFragment也允许开发者把Dialog作为内嵌的组件进行重用,类似Fragment(可以在大屏幕和小屏幕显示出不同的效果)。使用DialogFragment至少需要实现onCreateView或者onCreateDIalog方法。onCreateView即使用定义的xml布局文件展示Dialog。onCreateDialog即利用AlertDialog或者Dialog创建出Dialog。

1、重写onCreateView创建Dialog
a.创建一个对话框布局文件
b.继承DialogFragment,重写onCreateView方法:

public class TestFragment extends DialogFragment  
{  


    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
       // 隐藏对话框标题栏
       getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); 
        View view = inflater.inflate(R.layout.fragment_edit_name, container);  
        return view;  
    }  

} 

c.在Activity中调用:

public void showDialog(View view)  
    {  
        TestDialogFragment dialog = new TestDialogFragment();  
        dialog.show(getFragmentManager(), "TestDialog");  
    }  

2、重写onCreateDialog创建Dialog
a.新建对话框布局文件
b.继承DialogFragment重写onCreateDialog方法:

public class TestFragment extends DialogFragment  
{  

    @Override  
    public Dialog onCreateDialog(Bundle savedInstanceState)  
    {  
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());  
        // Get the layout inflater  
        LayoutInflater inflater = getActivity().getLayoutInflater();  
        View view = inflater.inflate(R.layout.fragment_test_dialog, null);  
        // Inflate and set the layout for the dialog  
        // Pass null as the parent view because its going in the dialog layout  
        builder.setView(view)  
                // Add action buttons  
                .setPositiveButton("Test",  
                        new DialogInterface.OnClickListener()  
                        {  
                            @Override  
                            public void onClick(DialogInterface dialog, int id)  
                            {  
                            }  
                        }).setNegativeButton("Cancel", null);  
        return builder.create();  
    }  
} 

c.调用:

public void showDialog(View view)  
    {  
        TestFragment dialog = new TestFragment();  
        dialog.show(getFragmentManager(), "testDialog");  
    } 

八、Fragment的startActivityForResult

在Fragment中存在startActivityForResult()以及onActivityResult()方法,需要通过调用getActivity().setResult(、Fragment.REQUEST_CODE, intent)来设置返回。

部分代码:

// 传入数据
Intent intent = new Intent(getActivity(),ContentActivity.class);  
intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position));  
startActivityForResult(intent, REQUEST_DETAIL);  

    @Override  
    public void onActivityResult(int requestCode, int resultCode, Intent data)  
    {  
        Log.e("TAG", "onActivityResult");  
        super.onActivityResult(requestCode, resultCode, data);  
        if(requestCode == REQUEST_DETAIL)  
        {  
            mTitles.set(mCurrentPos, mTitles.get(mCurrentPos)+" -- "+data.getStringExtra(ContentFragment.RESPONSE));  
            mAdapter.notifyDataSetChanged();  
        }  
    } 




    // 返回数据
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        Bundle bundle = getArguments();  
        if (bundle != null)  
        {  
            mArgument = bundle.getString(ARGUMENT);  
            Intent intent = new Intent();  
            intent.putExtra(RESPONSE, "good");  
            getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);  
        }  

    }  

    public static ContentFragment newInstance(String argument)  
    {  
        Bundle bundle = new Bundle();  
        bundle.putString(ARGUMENT, argument);  
        ContentFragment contentFragment = new ContentFragment();  
        contentFragment.setArguments(bundle);  
        return contentFragment;  
    }  

九、FragmentPagerAdapter与FragmentStatePagerAdapter的区别

使用ViewPager再结合上面任何一个实例的制作APP主页,主要区别就在与对于fragment是否销毁:
FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。
FragmentStatePagerAdapter:会销毁不再需要的fragment,当当前事务提交以后,会彻底的将fragmeng从当前Activity的FragmentManager中移除,state标明,销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。
如上所说,使用FragmentStatePagerAdapter当然更省内存,但是销毁新建也是需要时间的。一般情况下,如果你是制作主页面,就3、4个Tab,那么可以选择使用FragmentPagerAdapter,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。

十、Fragment间的数据传递

调用Fragment.setTargetFragment ,这个方法一般用于当前fragment由其它fragment启动时。

部分代码:

EvaluateDialog dialog = new EvaluateDialog();  
//注意setTargetFragment  
dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);  
dialog.show(getFragmentManager(), EVALUATE_DIALOG); 

//接收返回回来的数据  
@Override  
public void onActivityResult(int requestCode, int resultCode, Intent data)  
{  
    super.onActivityResult(requestCode, resultCode, data);  

    if (requestCode == REQUEST_EVALUATE)  
    {  
        String evaluate = data  
                    .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE);  
        Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show();  
        Intent intent = new Intent();  
        intent.putExtra(RESPONSE, evaluate);  
        getActivity().setResult(Activity.REQUEST_OK, intent);  
        }  

    } 




public class EvaluateDialog extends DialogFragment  
{ 
    ......

    // 设置返回数据  
    protected void setResult(int which)  
    {  
        // 判断是否设置了targetFragment  
        if (getTargetFragment() == null)  
            return;  

        Intent intent = new Intent();  
        intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]);  
        getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE,  Activity.RESULT_OK, intent);            
    }  
}

总结

该章节重要介绍了Fragment的各种应用方式,下一章节主要学习与分析YoKey大神的博客,以及大神开发的Fragment开源框架——Fragmentation。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值