fragment最佳实践

作者: 08_carmelo | 来源:发表于2017-06-01 23:03 被阅读388次

前言

fragment在项目中广泛使用,但是怎么使用才能发挥它的最大价值呢,我认为有个标准就是:你的fragment是否能复用,随便扔到哪个Acvtivity都玩得转。从实际开发中我总结了以下几点,旨在更规范地使用fragment(当然不是所有fragment都必须来复用,也可以专门处理某些业务,那就没必要刻意追求fragment的规范了,随意用就好。。)

Fragment or Activity?

我的看法是不要去比较两者的性能,没有意义,google让他们都存在肯定都是有价值的。它们的应用场景不同。Activity更倾向于一个整体模块容器,而Fragment是其中的子模块。可以理解成一个工厂(App)有N个生产不同产品的产房(Activity),每个厂房(Activity)里面有生产N类子产品的机器(Fragment)。所以,Activity的存在可以对应用更好的结构化和模块化的划分,让应用有更健壮和清晰的层次,而Fragment可以让将应用的功能细化和具象化。两者没有好坏之分,根据功能划分粒度来选取合适的载体才是正确的架构方式。

基本使用

FragmentManager fm = getSupportFragmentManager();  
        mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container);  

        if(mContentFragment == null )  
        {  
            mContentFragment = new ContentFragment();  
            fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();  
        }  

1、为什么需要判null呢?
主要是因为,当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。
2、add(R.id.id_fragment_container,mContentFragment)中的布局的id有何作用?
一方面呢,是告知FragmentManager,此fragment的位置;另一方面是此fragment的唯一标识;就像我们上面通过fm.findFragmentById(R.id.id_fragment_container)查找

封装BaseFragment基类

public abstract class BaseFragment extends Fragment {
    protected View mRootView;@Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(null == mRootView){
           mRootView = inflater.inflate(getLayoutId(), container, false);
        }
        return mRootView;
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        afterCreate(savedInstanceState);
    }
   //实现fragment的布局
    protected abstract int getLayoutId();
    protected abstract void afterCreate(Bundle savedInstanceState);
}

使用静态工厂方法newInstance(...)来获取Fragment实例

为啥要这么弄?fragment应用到不同的Activity,我们可以重载newInstance方法,根据需求new不同的fragment实例

public class WeatherFragment extends Fragment{
private static final String ARG1 = "arg1";
public static WeatherFragment newInstance(String cityName) {
    Bundle args = new Bundle();
    args.putString(cityName,ARG1);
    WeatherFragment fragment = new WeatherFragment();
    fragment.setArguments(args);
    return fragment;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
            String city = bundle.getString(ARG1);
        }
    }
}

fragment监听虚拟按键和back按键

//我见过很多方法,这个方法是最好的,给rootView设置一个OnKeyListener来监听key事件
mRootView.setFocusable(true);
mRootView.setFocusableInTouchMode(true);
mRootView.setOnKeyListener(new View.OnKeyListener() {
    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            //不一定是要触发返回栈,可以做一些其他的事情,我只是举个栗子。
            getActivity().onBackPressed();
            return true;
        }
        return false;
    }
});

回退栈

如果一个Activity有FragmentOne打开了FragmentTwo,想要back回退,可以使用回退栈
http://img.blog.csdn.net/20140720144355734

FragmentThree fThree = new FragmentThree();  
        FragmentManager fm = getFragmentManager();  
        FragmentTransaction tx = fm.beginTransaction();  
        tx.hide(this);  
        tx.add(R.id.id_content , fThree, "THREE");  //如果是hide,add那么fragment的视图不会重绘,比如edittext输入文字还会保存
// tx.replace(R.id.id_content, fThree, "THREE");  //如果是replace  那么视图重绘
        tx.addToBackStack(null);  
        tx.commit();  

Activity接收fragment数据

这个是fragment与Activity解耦的关键,如果你觉得这个fragment可以被复用,那么在fragment不要处理任何事件,全部以接口形式抛到Activity,在需要回调的activity实现这个接口,这么一来Fragment就如同一个空壳子,真正的逻辑都让调用者Activity去做:

public class FragmentOne extends Fragment implements OnClickListener  
{  
    private Button mBtn;  

    /** 
     * 设置按钮点击的回调 
     * @author zhy 
     * 
     */  
    public interface FOneBtnClickListener  
    {  
        void onFOneBtnClick();  
    }  

    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState)  
    {  
        View view = inflater.inflate(R.layout.fragment_one, container, false);  
        mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);  
        mBtn.setOnClickListener(this);  
        return view;  
    }  

    /** 
     * 交给宿主Activity处理,如果它希望处理 
     */  
    @Override  
    public void onClick(View v)  
    {  
        if (getActivity() instanceof FOneBtnClickListener)  
        {  
            ((FOneBtnClickListener) getActivity()).onFOneBtnClick();  
        }  
    }  

}  

不同Activity的fragment间传递数据

依旧是一个简单的场景:两个Fragment,一个展示文章列表的Fragment(叫做ListTitleFragment),一个显示详细信息的Fragment(叫做:ContentFragment),当然了,这两个Fragment都有其宿主Activity。
现在,我们点击列表Fragment中的列表项,传入相应的参数,去详细信息的Fragment展示详细的信息,在详细信息页面,用户可以进行点评,当用户点击back以后,我们以往点评结果显示在列表的Fragment对于的列表项中;
也就是说,我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数,那么我们肯定是使用Fragment.startActivityForResult ;
在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是呢,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);

单个Fragment的Activity封装

为何会有单个fragment的Activity,直接用Activity不行吗?这是为以后的扩展,比如需求变了这个Activity界面需要再放两个页签,像之前只有一个Activity就麻烦了,现在直接加fragment就完事!

package com.example.demo_zhy_23_fragments;  

import android.os.Bundle;  
import android.support.v4.app.Fragment;  
import android.support.v4.app.FragmentActivity;  
import android.support.v4.app.FragmentManager;  

public abstract class SingleFragmentActivity extends FragmentActivity  
{  
    protected abstract Fragment createFragment();  

    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  

        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_single_fragment);  

        FragmentManager fm = getSupportFragmentManager();  
        Fragment fragment =fm.findFragmentById(R.id.id_fragment_container);  

        if(fragment == null )  
        {  
            fragment = createFragment() ;  

            fm.beginTransaction().add(R.id.id_fragment_container,fragment).commit();  
        }  
    }  

}  

相关文章

网友评论

  • 凤鸣游子:你这个示例是不是有点问题呀, 在onCreateView中,如果viewroot不为null, 你直接返回会因为已经添加过不能再添加的呀(exception of already has a parent), 我是把他remove才返回的哦,你确定没有问题吗?
    08_carmelo:@阿水哥哥 这里是只是返回rootview,至于是否add是你自己在activity中控制的。
    凤鸣游子:@08_carmelo 晕倒,返回的rootview是要被add的, 你已经添加过一次了,不能再被添加啊
    08_carmelo:你说封装BaseFragment的这一段?
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(null == mRootView){
    mRootView = inflater.inflate(getLayoutId(), container, false);
    }
    return mRootView;
    }

    没问题啊,onCreateView就是要返回一个rootView,你是怎么用的

本文标题:fragment最佳实践

本文链接:https://www.haomeiwen.com/subject/gahyfxtx.html