Android Fragment DialogFragment

一、一个活动添加两个碎片的例子

参考Android Fragment完全解析,关于碎片你所需知道的一切

1.left_fragment.xml
LinearLayout...
Button...

2.right_fragment xml
LinearLayout...
textView...

3.LeftFragment.java

//------------------------------------------------------------
public class LeftFragment extends Fragment{
   @Override
   public View onCreateView(LayoutInflater inflater,
   ViewGroup container,Bundle savedInstanceState){
      View view = inflater.inflate(R.layout.left_fragment,container,false);
      return view;
   }
}

4.RightFragment.java
同上
5.activity_main.xml


   
   
   

6.MainActivity.java

//动态替换碎片
AnotherRightFragment fragment = new AnotherRightFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout,fragment);
//transaction.add(R.id.right_layout,fragment);
transaction.addToBackStack(null);//将碎片加入返回栈
transaction.commit();

7.活动中获取碎片实例
(RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);
8.碎片获取活动:
MainActivity activity = (MainActivity)getActivity();
9.碎片和碎片:
先通过碎片取得关联的活动,再通过这个活动去获取另一个碎片……

二、DialogFragment

参考Android中Dialog与DialogFragment的对比
详细解读DialogFragment

DialogFragment有一个非常好的特性(在手机配置变化,导致Activity需要重新创建时,例如旋屏,基于DialogFragment的对话框将会由FragmentManager自动重建,然而基于Dialog实现的对话框则没有这样的能力)。

public class MainActivity extends Activity {
    private Button clk;
    private Dialog dialog;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        clk = (Button) findViewById(R.id.clk);
        dialog = new Dialog(this);
        dialog.setContentView(R.layout.dialog);
        clk.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                dialog.show();
            }
        });

        //用户恢复对话框的状态
        if(savedInstanceState != null && 
                savedInstanceState.getBoolean("dialog_show"))
            clk.performClick();
    }

    /**
     * 用于保存对话框的状态以便恢复
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if(dialog != null && dialog.isShowing())
            outState.putBoolean("dialog_show", true);
        else
            outState.putBoolean("dialog_show", false);
    }

    /**
     * 在Activity销毁之前,确保对话框以关闭
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(dialog != null && dialog.isShowing())
            dialog.dismiss();
    }
}

public class MyDialogFragment extends DialogFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.dialog, container, false);
        return v;
    }
}

public class MainActivity extends FragmentActivity {
    private Button clk;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        clk = (Button) findViewById(R.id.clk);
        clk.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                MyDialogFragment mdf = new MyDialogFragment();
                FragmentTransaction ft = 
                getSupportFragmentManager().beginTransaction();
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                mdf.show(ft, "df");
            }
        });
    }
}
三、Android开发之Fragment最佳实践

参考关于 Android,用多个 activity,还是单 activity 配合 fragment?
在业务场景下到底是使用Fragment还是Activity我在答案最后也有提及到,我所参与的项目的三个子项目(包括我没参与过的N多项目,之前做code review有看过源码的那部分),基本上都是大量使用Fragment来做视图部分,组装更加灵活。

我们的视频文档组件开发也是以Fragment为基础来做的,我们接入项目的时候只需要预留Layout,之后组件会用Fragment的形式来填充Layout。

Fragment+Activity并不意味着一定只有一个Activity,我们项目就分LoginActivity,SplashActivity,HomeActivity,参见我最后的回答。

所以我觉得到底用不用是肯定毋庸置疑的,我们不能因为一个东西学习成本高,需要注意的点多,就否认它来带的便利。

Fragment还存在一些坑,是我在项目中遇到过的,明天会补充一下。

首先Fragment带来的便利以及足以让我们无视它可能会导致的麻烦了,而且很多麻烦都是自己使用方法不正确导致的。

毕竟相比于Activity来说,创建一个Fragment所需系统资源相比Activity来说更少,然而控制却更为灵活。

我所参与的项目基本上不用Activity来做UI展示,这部分职责都移交给fragment来实现。

Fragment一般分为两类,一类是有UI的Fragment,可以作为页面,作为View来展示,另一类是用没有UI的Fragment,一般用作保存数据。

至于题主所说的重叠,以及创建多个的问题,都是自己使用不当导致的,你需要get正确的fragment使用方法。

1.封装BaseFragment基类
例如为了实例化View,抽象一个getLayoutId方法,子类无需关心具体的创建操作,父类来做View的创建处理。同时可以提供一个afterCreate抽象函数,在初始化完成之后调用,子类可以做一些初始化的操作,你也可以添加一些常用的方法在基类,例如ShowToast().

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);
    }

    protected abstract int getLayoutId();

    protected abstract void afterCreate(Bundle savedInstanceState);
}

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

可以在Google的代码中发现这种写法,好处是接收确切的参数,返回一个Fragment实例,避免了在创建Fragment的时候无法在类外部知道所需参数的问题,在合作开发的时候特别有用。还有就是Fragment推荐使用setArguments来传递参数,避免在横竖屏切换的时候Fragment自动调用自己的无参构造函数,导致数据丢失。

public static WeatherFragment newInstance(String cityName) {
    Bundle args = new Bundle();
    args.putString(cityName,"cityName");
    WeatherFragment fragment = new WeatherFragment();
    fragment.setArguments(args);
    return fragment;
}

3.Fragment状态保存/现场恢复

不要在Fragment里面保存ViewState!
不要在Fragment里面保存ViewState!
不要在Fragment里面保存ViewState!

为了让你的代码更加清晰和稳定,最好区分清楚fragment状态保存和view状态保存,如果某个属性属于View,则不要在Fragment中做它的状态保存,除非属性属于Fragment。每一个自定义View都有义务实现状态的保存,可以像EditText一样,设置一个开关来选择是否保存比如说:android:freezeText="true/false"。

public class CustomView extends View {
 
    ...
 
    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        // 在这里保存当前状态
        return bundle;
    }
 
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        // 恢复保存的状态
    }
 
    ...
 
}

处理fragment状态保存,例如保存从服务器获取的数据。

private String serverData;
     
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("data", serverData);
    }
 
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        serverData = savedInstanceState.getString("data");
    }

4.避免错误操作导致Fragment的视图重叠

这个问题很简单,在add或者replace的时候,调用含有TAG参数的那个方法,之后再add相同TAG的Fragment的话,之前的会被替换掉,也就不会同时出现多个相同的Fragment了。

public class WeatherFragment extends Fragment {
    //TAG
    public static final String TAG = WeatherFragment.class.getSimpleName();

不过为了最大限度的重用,可以在Activity的onCreate(Bundle savedInstanceState)中判断savedInstanceState是否不为空;

不为空的话,先用getSupportFragmentManager(). findFragmentByTag()找一下,找到实例就不用再次创建。

WeatherFragment fragment = null;

if(savedInstanceState!=null){
fragment = getSupportFragmentManager().findFragmentByTag(WeatherFragment.TAG);
}

if(fragment == null){
   fragment = WeatherFragment.newInstance(...);
}

5.Fragment里监听虚拟按键和实体按键的返回事件
我见过很多方法,这个方法是最好的,给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;
    }
});
四、小结

参考
Android开发中,Fragment真的有大家说的那么不堪吗?

Fragment 的出现一方面是为了缓解 Activity 任务过重的问题,另一方面是为了处理在不同屏幕上 UI 组件的布局问题,而且它还提供了一些新的特性(例如 Retainable)来处理一些在 Activity 中比较棘手的问题。

  1. Fragment 拥有和 Activity 一致的生命周期,它和 Activity 一样被定义为 Controller 层的类。有过中大型项目开发经验的开发者,应该都会遇到过 Activity 过于臃肿的情况,而 Fragment 的出现就是为了缓解这一状况,可以说 它将屏幕分解为多个「Fragment(碎片)」(这句话很重要),但它又不同于 View,它干的实质上就是 Activity 的事情,负责控制 View 以及它们之间的逻辑。

  2. 将屏幕碎片化为多个 Fragment 后,其实 Activity 只需要花精力去管理当前屏幕内应该显示哪些 Fragments,以及应该对它们进行如何布局就行了。这是一种组件化的思维,用 Fragment 去组合了一系列有关联的 UI 组件,并管理它们之间的逻辑,而 Activity 负责在不同屏幕下(例如横竖屏)布局不同的 Fragments 组合。

  3. Fragment一般分为两类,一类是有UI的Fragment,可以作为页面,作为View来展示,另一类是用没有UI的Fragment,一般用作保存数据。retainInstance 属性,能够在 Activity 因为屏幕状态发生改变(例如切换横竖屏时)而销毁重建时,依然保留实例。这示意着我们能在 RetainedFragment 里面执行一些在屏幕状态发生改变时不被中断的操作。例如在 ToastAndroid/StartActivity.kt at master 我使用了 RetainedFragment 来缓存在线音乐文件,它在横竖屏切换时依然维持下载进度,并通过一个 DialogFragment 来展示进度。

  4. 使用fragment来显示页面,系统资源消耗更小,直观的表现就是切换view时的速度变快。参考微信ANDROID客户端-会话速度提升70%的背后

5.标准转场动画:
  可以通过setTransition(int transit)给Fragment指定标准的转场动画
  该方法可传入的三个参数是:
  TRANSIT_NONE,
  TRANSIT_FRAGMENT_OPEN,
  TRANSIT_FRAGMENT_CLOSE
  分别对应无动画、打开形式的动画和关闭形式的动画。
  标准动画设置好后,在Fragment添加和移除的时候都会有。

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

三、类似QQ切换页签的例子

参考Android Fragment应用实战,使用碎片向ActivityGroup说再见

Android Fragment DialogFragment_第1张图片
效果

布局及相应的Fragment代码略去,看一下切换代码:

/**
 * 项目的主Activity,所有的Fragment都嵌入在这里。
 * 
 * @author guolin
 */
public class MainActivity extends Activity implements OnClickListener {

    /**
     * 用于展示消息的Fragment
     */
    private MessageFragment messageFragment;

    /**
     * 用于展示联系人的Fragment
     */
    private ContactsFragment contactsFragment;

    /**
     * 用于展示动态的Fragment
     */
    private NewsFragment newsFragment;

    /**
     * 用于展示设置的Fragment
     */
    private SettingFragment settingFragment;

    /**
     * 消息界面布局
     */
    private View messageLayout;

    /**
     * 联系人界面布局
     */
    private View contactsLayout;

    /**
     * 动态界面布局
     */
    private View newsLayout;

    /**
     * 设置界面布局
     */
    private View settingLayout;

    /**
     * 在Tab布局上显示消息图标的控件
     */
    private ImageView messageImage;

    /**
     * 在Tab布局上显示联系人图标的控件
     */
    private ImageView contactsImage;

    /**
     * 在Tab布局上显示动态图标的控件
     */
    private ImageView newsImage;

    /**
     * 在Tab布局上显示设置图标的控件
     */
    private ImageView settingImage;

    /**
     * 在Tab布局上显示消息标题的控件
     */
    private TextView messageText;

    /**
     * 在Tab布局上显示联系人标题的控件
     */
    private TextView contactsText;

    /**
     * 在Tab布局上显示动态标题的控件
     */
    private TextView newsText;

    /**
     * 在Tab布局上显示设置标题的控件
     */
    private TextView settingText;

    /**
     * 用于对Fragment进行管理
     */
    private FragmentManager fragmentManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        // 初始化布局元素
        initViews();
        fragmentManager = getFragmentManager();
        // 第一次启动时选中第0个tab
        setTabSelection(0);
    }

    /**
     * 在这里获取到每个需要用到的控件的实例,并给它们设置好必要的点击事件。
     */
    private void initViews() {
        messageLayout = findViewById(R.id.message_layout);
        contactsLayout = findViewById(R.id.contacts_layout);
        newsLayout = findViewById(R.id.news_layout);
        settingLayout = findViewById(R.id.setting_layout);
        messageImage = (ImageView) findViewById(R.id.message_image);
        contactsImage = (ImageView) findViewById(R.id.contacts_image);
        newsImage = (ImageView) findViewById(R.id.news_image);
        settingImage = (ImageView) findViewById(R.id.setting_image);
        messageText = (TextView) findViewById(R.id.message_text);
        contactsText = (TextView) findViewById(R.id.contacts_text);
        newsText = (TextView) findViewById(R.id.news_text);
        settingText = (TextView) findViewById(R.id.setting_text);
        messageLayout.setOnClickListener(this);
        contactsLayout.setOnClickListener(this);
        newsLayout.setOnClickListener(this);
        settingLayout.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.message_layout:
            // 当点击了消息tab时,选中第1个tab
            setTabSelection(0);
            break;
        case R.id.contacts_layout:
            // 当点击了联系人tab时,选中第2个tab
            setTabSelection(1);
            break;
        case R.id.news_layout:
            // 当点击了动态tab时,选中第3个tab
            setTabSelection(2);
            break;
        case R.id.setting_layout:
            // 当点击了设置tab时,选中第4个tab
            setTabSelection(3);
            break;
        default:
            break;
        }
    }

    /**
     * 根据传入的index参数来设置选中的tab页。
     * 
     * @param index
     * 每个tab页对应的下标。0表示消息,1表示联系人,2表示动态,3表示设置。
     */
    private void setTabSelection(int index) {
        // 每次选中之前先清楚掉上次的选中状态
        clearSelection();
        // 开启一个Fragment事务
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 先隐藏掉所有的Fragment,以防止有多个Fragment显示在界面上的情况
        hideFragments(transaction);
        switch (index) {
        case 0:
            // 当点击了消息tab时,改变控件的图片和文字颜色
            messageImage.setImageResource(R.drawable.message_selected);
            messageText.setTextColor(Color.WHITE);
            if (messageFragment == null) {
                // 如果MessageFragment为空,则创建一个并添加到界面上
                messageFragment = new MessageFragment();
                transaction.add(R.id.content, messageFragment);
            } else {
                // 如果MessageFragment不为空,则直接将它显示出来
                transaction.show(messageFragment);
            }
            break;
        case 1:
            // 当点击了联系人tab时,改变控件的图片和文字颜色
            contactsImage.setImageResource(R.drawable.contacts_selected);
            contactsText.setTextColor(Color.WHITE);
            if (contactsFragment == null) {
                // 如果ContactsFragment为空,则创建一个并添加到界面上
                contactsFragment = new ContactsFragment();
                transaction.add(R.id.content, contactsFragment);
            } else {
                // 如果ContactsFragment不为空,则直接将它显示出来
                transaction.show(contactsFragment);
            }
            break;
        case 2:
            // 当点击了动态tab时,改变控件的图片和文字颜色
            newsImage.setImageResource(R.drawable.news_selected);
            newsText.setTextColor(Color.WHITE);
            if (newsFragment == null) {
                // 如果NewsFragment为空,则创建一个并添加到界面上
                newsFragment = new NewsFragment();
                transaction.add(R.id.content, newsFragment);
            } else {
                // 如果NewsFragment不为空,则直接将它显示出来
                transaction.show(newsFragment);
            }
            break;
        case 3:
        default:
            // 当点击了设置tab时,改变控件的图片和文字颜色
            settingImage.setImageResource(R.drawable.setting_selected);
            settingText.setTextColor(Color.WHITE);
            if (settingFragment == null) {
                // 如果SettingFragment为空,则创建一个并添加到界面上
                settingFragment = new SettingFragment();
                transaction.add(R.id.content, settingFragment);
            } else {
                // 如果SettingFragment不为空,则直接将它显示出来
                transaction.show(settingFragment);
            }
            break;
        }
        transaction.commit();
    }

    /**
     * 清除掉所有的选中状态。
     */
    private void clearSelection() {
        messageImage.setImageResource(R.drawable.message_unselected);
        messageText.setTextColor(Color.parseColor("#82858b"));
        contactsImage.setImageResource(R.drawable.contacts_unselected);
        contactsText.setTextColor(Color.parseColor("#82858b"));
        newsImage.setImageResource(R.drawable.news_unselected);
        newsText.setTextColor(Color.parseColor("#82858b"));
        settingImage.setImageResource(R.drawable.setting_unselected);
        settingText.setTextColor(Color.parseColor("#82858b"));
    }

    /**
     * 将所有的Fragment都置为隐藏状态。
     * 
     * @param transaction
     *            用于对Fragment执行操作的事务
     */
    private void hideFragments(FragmentTransaction transaction) {
        if (messageFragment != null) {
            transaction.hide(messageFragment);
        }
        if (contactsFragment != null) {
            transaction.hide(contactsFragment);
        }
        if (newsFragment != null) {
            transaction.hide(newsFragment);
        }
        if (settingFragment != null) {
            transaction.hide(settingFragment);
        }
    }
}

说一下为什么使用add方法,没有使用replace()方法呢?这是因为replace()方法会将被替换掉的那个Fragment彻底地移除掉,该Fragment的生命周期就结束了。当再次点击刚才那个Tab项的时候,就会让该Fragment的生命周期重新开始,onCreate()、onCreateView()等方法都会重新执行一遍。这显然不是我们想要的,也和ActivityGroup的工作原理不符,因此最好的解决方案就是使用hide()和show()方法来隐藏和显示Fragment,这就不会让Fragment的生命周期重走一遍了。

五、复杂的生命周期

参考
Android Activity和Fragment的生命周期
1、Fragment全解析系列(一):那些年踩过的坑
2、Fragment全解析系列(二):正确的使用姿势
3、Fragment全解析系列(三)我的解决方案:Fragmentation

  • onAttach方法:Fragment和Activity建立关联的时候调用。
  • onCreateView方法:为Fragment加载布局时调用。
  • onActivityCreated方法:当Activity中的onCreate方法执行完后调用。
  • onDestroyView方法:Fragment中的布局被移除时调用。
  • onDetach方法:Fragment和Activity解除关联的时候调用。


    Android Fragment DialogFragment_第2张图片
    Paste_Image.png

你可能感兴趣的:(Android Fragment DialogFragment)