Day4-Fragment

总结

  • Fragment初始化用newInstance
  • viewPager开多Fragment, 考虑懒加载
  • getActivity空指针,onAttach()拿到mActivity
  • 避免异步中进行commit()
  • 避免在onCreate()commit()
  • genymotion别乱拖,否则横竖屏切换不会重启activity
  • fragment第二次进入显示白原因
  • onBackPressedBUG
  • FragmentStateLoss

生命周期

只有在activity处于onResume()时,fragment的生命周期才会自由,否则,被activity控制
  • onAttach(), 拿到activity, 添加Listener
  • onCreate(), ???
  • onCreateView(), 创建视图
  • onActivityCreated(), activity的onCreate 返回时调用
  • onDestroyView(), Fragment视图被移除时调用
  • onDestroy(),
  • onDetach(), Fragment 与 activity 取消关联时调用, 操作与onAttach()相反

加载

静态加载,类似 view

  • activity 的布局中
    <fragment  
         android:id="@+id/id_fragment_title"  **必须包含ID**
         android:name="com.包名.路径.TitleFragment"  
         android:layout_width="fill_parent"  
         android:layout_height="45dp" />  
    
    

动态加载

1.构造
Google规定 Fragment 需要提供一个 public 的无参构造函数,在 framework 状态恢复时使用

  • 如果需要接受外部的参数创建Fragment的, 需保存参数到 bundle:

    inflate(int resources,  当前布局D
             ViewGroup root, 根布局G
             boolean attachToRoot 是否依赖根布局G),
    

    viewgroup的addview()不能添加一个已包含父控件的视图, 如果设置第三个参数为false, 则当前布局D 不依赖于根布局G, 返回的是xml为根布局的view

    • 如果不设置attachToRoot的false,
    • 当fragmentTransaction add 时, java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
    • 当viewpager导入, 直接报 OOM
  • 这个参数是parentView, 当你创建子布局时, 如果希望这个布局依赖于第二

    //FragmentOne中的newInstance函数
        public static FragmentOne newInstance(String text){
            FragmentOne fragmentOne = new FragmentOne();
            Bundle bundle = new Bundle();
            bundle.putString("name", text);//传参
            fragmentOne.setArguments(bundle);
            return fragmentOne;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            text = getArguments() != null ? getArguments().getString("name") : "";
        }
    
        @Override
        public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
          View view = inflater.inflate(R.layout.fragmentm, container, false);
          return view;
        }
    
    作者:DrunkPian0
    链接:http://www.jianshu.com/p/caa5d6568faa
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

2.替换

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(布局,Fragment 实例)
fragmentTransaction.commit();

3. 通信

  • activity传给fragment
    activity 中通过getFragmentManager().findFragmentById(R.id.test_fragment);获取 Fragment 实例,
    也可以

  • Fragment传给activity

    1. fragment内部创建接口

      public interface FragmentOneBtnClickListener{
            void onOneBtnClick();
      }
      
    2. fragment内按钮点击时调用

      Button button = (android.widget.Button) view.findViewById(R.id.btn_frag_one);
      button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             if (getActivity() instanceof FragmentOneBtnClickListener){
                 ((FragmentOneBtnClickListener)getActivity()).onOneBtnClick();
             }
         }
      });
      
    3. activity实现接口及方法

      public class MainActivity extends AppCompatActivity implements OneFragment.FragmentOneBtnClickListener{
          @Override
          public void onOneBtnClick() {
              Toast.makeText(this, "one", Toast.LENGTH_SHORT).show();
          }
      }
      
  • Fragment.startActivityForResult();
    Activity 里的 FragmentOne 调用 startActivityForResult 开启ActivityTwo, ActivityTwo里的FragmentTwo操作回调时调用getActivity().setResult(FragmentOne.REQUEST_DETAIL, intent);

FragmentManager

当activity被杀死重建时,fragment会被保存, 但会创建新的FragmentManager, 新FragmentManager会先去获取保存下来的fragment队列, 再去重建, 从而恢复之前的状态

FragmentTransaction (Google)
  • add(); 添加 Fragment
  • remove(); 移除 Fragment, 若添加 addToBackStack(null),则不会执行 onDestroy()onDetach();
  • replace(); = remove + add
  • hide(); 隐藏
  • show(); 显示隐藏的
  • detach(fragment); 移除 view, 但 fragment 依然被 FragmentManager 管理
  • attach(); 重建视图
  • commit(); 默认异步,并不立刻执行,而是加入 UI 线程的队列中,
    • 后接executePendingTransactions() 立即执行所有 pending在队列中的transaction
  • commitAllowStateLoss();
  • commitNow()^v24^, 只同步执行此次 transaction(完善executePendingTransactions),不可与addToBackStack()共用
  • commitNowAllowStateLoss()
Back Stack
  • Activity 的 BackStack,系统维护,每个 task 一个 BackStack
  • Fragment 的 BackStack,宿主 Activity 维护,每个 activity 一个
  • 通过 addToBackStack 调用,按 Back 键后执行 commit 进去的 transaction 的逆操作

v4.fragment 和 app.fragment(以下简称v4和app)

  1. 支持版本不同, v4支持到4, app只支持11及以上
  2. v4需要jar包
  3. 获取 FragmentManager
    • v4: getSupportFragmentManager
    • app: getFragmentManager
  4. 包含 v4 的 Activity 需要继承 FragmentActivity
  5. v4的不支持 objectAnimator, Animator, 即不支持属性动画,只支持位移动画。参考
  6. mStateSaved 何时置为true
    • v4在 onSaveInstanceStateonStop
    • app在 onSaveInstanceState
举例
例1 commitAllowStateLoss
  1. acticity 中放入 FragmentA;
  2. activity 被后台,运行 onStoponSaveInstanceState;
  3. 某个事件触发下,FragmentB replace FragmentA,提交的是 commitAllowStateLoss;
  4. 此时可能会发生两种情况
    • 第一种,系统杀死了activity,activity重建,使用步骤2的onSaveInstanceState恢复,A恢复,B没有
    • 第二种,activity 没被杀死,FragmentB 显示,到下次 Activity stop时,这个包含了 B 的状态被保存了下来
例2 fragment中执行异步
  1. activity 执行AsyncTask, 同时打开 ProgressDialog (API26被弃用,推荐ProgressBar和Notification)
  2. 执行过程中, 进行旋转屏幕 可能的情况如下:
    • 上个线程还在执行, 又开一个线程, 可能操作一些已经被处理的控件, 报空
    • 关闭dialog的代码在onPostExecute, 但是上个线程被杀死, 无法关闭
  • 解决: DialogFragment
例3 内存泄漏 from简书
  1. util.class
public class Util {   
  private Context mContext;  
  private static Util sInstance;  
    private Util(Context context) {  
        this.mContext = context;  
    }  
    public static Util getInstance(Context context) {  
        if (sInstance == null) {  
            sInstance = new Util(context);  
        }  
        return sInstance;  
    }  
    //other methods  
}
  1. Fragment用了上面的类
  2. Fragment被干掉, GC想回收Fragment占用的内存, 但因为sInstance 是静态的, 一直持有fragment的引用, 即使destroy也不行
  • 解决:
    1. getApplicationContext()
    2. 弱引用
      • 把sInstance用WeakReference包起来, 需要的时候wr.get();
例4 横竖屏切换fromHongYang
  • 横竖屏切换时, activity重建, fragment生命周期跟着变, 同时因为activity的onSaveInstanceState, 之前的fragment们也被还原出来,会产生多个fragment
  • 第一次切换
  • 第二次切换
  • 按下Home键后的生命周期
  • 解决: activity 的 onCreate方法中添加bundle非空后再进行transactioncommit,
    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      ...
      if (savedInstanceState == null) {
          FragmentManager fragmentManager = getFragmentManager();
          FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
          fragmentTransaction.add(R.id.layout, oneFragment, "one");
          fragmentTransaction.commit();
      }
      ...
    }
    
    

Fragment+ActionBar(ToolBar)

Fragment自己实现

  1. Fragment 的 xml 里添加 toolBar

  2. onCreate加入 setHasOptionsMenu(true)

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }
    
  3. 设置依赖的activity.setSupportActionBar

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one, container, false);
        Toolbar toolbar = (Toolbar) view.findViewById(R.id.fragment_toolbar);
        mActivity.setSupportActionBar(toolbar);
        return view;
    }
    
  4. 实现onCreateOptionsMenu

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
       super.onCreateOptionsMenu(menu, inflater);
       inflater.inflate(R.menu.toolbar, menu);
    }
    
  5. 点击事件onOptionsItemSelected

    @Override  
    public boolean onOptionsItemSelected(MenuItem item)  {  
        switch (item.getItemId())  {  
          case R.id.id_menu_fra_test:  
              Toast.makeText(getActivity(), "FragmentMenuItem1", Toast.LENGTH_SHORT).show();  
              break;  
        }  
        return true;  
    }  
    

Activity实现

PS: Activity自身ToolBar

  1. manifest 把 theme 的 parent 改成 NoActionBar

  2. activity布局里添加ToolBar

  3. activity的onCreate方法setSupportActionBar();

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    
  4. activity 重写 onCreateOptionsMenu 添加布局

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

    重写 onOptionsItemSelected 添加点击事件

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case R.id.backup:
                Toast.makeText(this, "backup", Toast.LENGTH_SHORT).show();
                break;
    
            case R.id.delete:
                Toast.makeText(this, "del", Toast.LENGTH_SHORT).show();
                break;
    
            case R.id.settings:
                Toast.makeText(this, "set", Toast.LENGTH_SHORT).show();
                break;
        }
        return true;
    }
    

FragmentPagerAdapter 和 FragmentStatePagerAdapter

  • FragmentPagerAdapter: 调用depatch(), 只销毁视图, 适合主界面
  • FragmentPagerAdapter: 销毁, 可存数据进bundle, 然后保存在onSaveInstanceState

DialogFragment 替代 Dialog和AlertDialog

Dialog和继承的AlertDialog无法在横竖屏切换时保存数据, DialogFragment依靠FragmentManager自动重建并恢复数据

  • 自定义view 的 DialogFragment 创建
    
    public class EditDialogFragment extends DialogFragment {
       @Nullable
       @Override
       public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
           View inflate = inflater.inflate(R.layout.fragment_dialog, container);
           getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
           return inflate;
       }
    
  • AlertDialog 的 DialogFragment 创建
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    
            LayoutInflater layoutInflater = getActivity().getLayoutInflater();
            View view = layoutInflater.inflate(R.layout.fragment_dialog, null);
    //        editName = (EditText) view.findViewById(R.id.id_txt_your_name);
            builder.setView(view)
                    .setNegativeButton("sign",
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Toast.makeText(getContext(),"test",Toast.LENGTH_SHORT).show();
    //                                LoginCompleteListener loginCompleteListener = (LoginCompleteListener) getActivity();
    //                                loginCompleteListener.onLoginComplete(editName.getText().toString(), "123");
                                }
                            }).setPositiveButton("Cancel", null);
            return builder.create();
    

Fragment来保存数据

系统帮助数据恢复

  • 少量数据 onSaveInstanceStateonRestoreInstanceState

  • 大量数据, 无法序列化, 如bitmap, 用Fragment存放, 但是切勿传递任何包含context的对象

    1. 创建Fragment, 添加 setRetainInstance
    public class RetainedFragment extends Fragment {
    
        // data object we want to retain
        private MyDataObject 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(MyDataObject data) {
            this.data = data;
        }
    
        public MyDataObject getData() {
            return data;
        }
    }
    
    1. activity调用fragment保存
    public class MyActivity extends Activity {
    
        private RetainedFragment dataFragment;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            // find the retained fragment on activity restarts
            FragmentManager fm = getFragmentManager();
            dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
    
            // create the fragment and data the first time
            if (dataFragment == null) {
                // add the fragment
                dataFragment = new DataFragment();
                fm.beginTransaction().add(dataFragment, “data”).commit();
                // load the data from the web
                dataFragment.setData(loadMyData());
            }
    
            // the data is available in dataFragment.getData()
            ...
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            // store the data in the fragment
            dataFragment.setData(collectMyLoadedData());
        }
    }
    

自行处理数据变更(activity不重走生命周期, 不推荐)

  1. 在manifest的<Activity>里添加属性
<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">
  1. 屏幕切换时可通过onConfigurationChanged自行处理
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

进阶-Fragment保存AsyncTask

activity在 onCreateonSaveInstanceState 回调AsyncTask, 并在onCreate时调用 setData

  1. 创建Fragment, 不过setData保存的是AsyncTask
public class OtherRetainedFragment 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;
    }


}
  1. 创建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() {
        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会一直变化
     *
     * @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();
        }
    }

}
  1. 创建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);
    }

}

BUGS

  • findFragmentByTag 查不到
    • 解决: 在 onCreate() 里提交commit()后, 在 onStart() 里find, 不能直接在commit()之后

嵌套

WTFs/min = 2^fragment count

参考

建议阅读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容