美文网首页
Day4-Fragment

Day4-Fragment

作者: 我不是死胖子 | 来源:发表于2017-08-10 15:37 被阅读134次

    总结

    • 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
    • 第一次切换
    • 第二次切换
    • 解决: 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

    参考

    建议阅读

    相关文章

      网友评论

          本文标题:Day4-Fragment

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