Day4-Fragment

總結(jié)

  • Fragment初始化用newInstance
  • viewPager開多Fragment, 考慮懶加載
  • getActivity空指針,onAttach()拿到mActivity
  • 避免異步中進行commit()
  • 避免在onCreate()commit()
  • genymotion別亂拖,否則橫豎屏切換不會重啟activity
  • fragment第二次進入顯示白原因
  • onBackPressedBUG
  • FragmentStateLoss

生命周期

只有在activity處于onResume()時,fragment的生命周期才會自由,否則,被activity控制
  • onAttach(), 拿到activity, 添加Listener
  • onCreate(), ???
  • onCreateView(), 創(chuàng)建視圖
  • onActivityCreated(), activity的onCreate 返回時調(diào)用
  • onDestroyView(), Fragment視圖被移除時調(diào)用
  • onDestroy(),
  • onDetach(), Fragment 與 activity 取消關(guān)聯(lián)時調(diào)用, 操作與onAttach()相反

加載

靜態(tài)加載,類似 view

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

動態(tài)加載

1.構(gòu)造
Google規(guī)定 Fragment 需要提供一個 public 的無參構(gòu)造函數(shù),在 framework 狀態(tài)恢復(fù)時使用

  • 如果需要接受外部的參數(shù)創(chuàng)建Fragment的, 需保存參數(shù)到 bundle:

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

    viewgroup的addview()不能添加一個已包含父控件的視圖, 如果設(shè)置第三個參數(shù)為false, 則當前布局D 不依賴于根布局G, 返回的是xml為根布局的view

    • 如果不設(shè)置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導(dǎo)入, 直接報 OOM
  • 這個參數(shù)是parentView, 當你創(chuàng)建子布局時, 如果希望這個布局依賴于第二

    //FragmentOne中的newInstance函數(shù)
        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.itdecent.cn/p/caa5d6568faa
    來源:簡書
    著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
    

2.替換

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(布局,F(xiàn)ragment 實例)
fragmentTransaction.commit();

3. 通信

  • activity傳給fragment
    activity 中通過getFragmentManager().findFragmentById(R.id.test_fragment);獲取 Fragment 實例,
    也可以

  • Fragment傳給activity

    1. fragment內(nèi)部創(chuàng)建接口

      public interface FragmentOneBtnClickListener{
            void onOneBtnClick();
      }
      
    2. fragment內(nèi)按鈕點擊時調(diào)用

      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實現(xiàn)接口及方法

      public class MainActivity extends AppCompatActivity implements OneFragment.FragmentOneBtnClickListener{
          @Override
          public void onOneBtnClick() {
              Toast.makeText(this, "one", Toast.LENGTH_SHORT).show();
          }
      }
      
  • Fragment.startActivityForResult();
    Activity 里的 FragmentOne 調(diào)用 startActivityForResult 開啟ActivityTwo, ActivityTwo里的FragmentTwo操作回調(diào)時調(diào)用getActivity().setResult(FragmentOne.REQUEST_DETAIL, intent);

FragmentManager

當activity被殺死重建時,fragment會被保存, 但會創(chuàng)建新的FragmentManager, 新FragmentManager會先去獲取保存下來的fragment隊列, 再去重建, 從而恢復(fù)之前的狀態(tài)

FragmentTransaction (Google)
  • add(); 添加 Fragment
  • remove(); 移除 Fragment, 若添加 addToBackStack(null),則不會執(zhí)行 onDestroy()onDetach();
  • replace(); = remove + add
  • hide(); 隱藏
  • show(); 顯示隱藏的
  • detach(fragment); 移除 view, 但 fragment 依然被 FragmentManager 管理
  • attach(); 重建視圖
  • commit(); 默認異步,并不立刻執(zhí)行,而是加入 UI 線程的隊列中,
    • 后接executePendingTransactions() 立即執(zhí)行所有 pending在隊列中的transaction
  • commitAllowStateLoss();
  • commitNow()^v24^, 只同步執(zhí)行此次 transaction(完善executePendingTransactions),不可與addToBackStack()共用
  • commitNowAllowStateLoss()
Back Stack
  • Activity 的 BackStack,系統(tǒng)維護,每個 task 一個 BackStack
  • Fragment 的 BackStack,宿主 Activity 維護,每個 activity 一個
  • 通過 addToBackStack 調(diào)用,按 Back 鍵后執(zhí)行 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. 某個事件觸發(fā)下,F(xiàn)ragmentB replace FragmentA,提交的是 commitAllowStateLoss;
  4. 此時可能會發(fā)生兩種情況
    • 第一種,系統(tǒng)殺死了activity,activity重建,使用步驟2的onSaveInstanceState恢復(fù),A恢復(fù),B沒有
    • 第二種,activity 沒被殺死,F(xiàn)ragmentB 顯示,到下次 Activity stop時,這個包含了 B 的狀態(tài)被保存了下來
例2 fragment中執(zhí)行異步
  1. activity 執(zhí)行AsyncTask, 同時打開 ProgressDialog (API26被棄用,推薦ProgressBar和Notification)
  2. 執(zhí)行過程中, 進行旋轉(zhuǎn)屏幕 可能的情況如下:
    • 上個線程還在執(zhí)行, 又開一個線程, 可能操作一些已經(jīng)被處理的控件, 報空
    • 關(guān)閉dialog的代碼在onPostExecute, 但是上個線程被殺死, 無法關(guān)閉
  • 解決: DialogFragment
例3 內(nèi)存泄漏 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占用的內(nèi)存, 但因為sInstance 是靜態(tài)的, 一直持有fragment的引用, 即使destroy也不行
  • 解決:
    1. getApplicationContext()
    2. 弱引用
      • 把sInstance用WeakReference包起來, 需要的時候wr.get();
例4 橫豎屏切換fromHongYang
  • 橫豎屏切換時, activity重建, fragment生命周期跟著變, 同時因為activity的onSaveInstanceState, 之前的fragment們也被還原出來,會產(chǎn)生多個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自己實現(xiàn)

  1. Fragment 的 xml 里添加 toolBar

  2. onCreate加入 setHasOptionsMenu(true)

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }
    
  3. 設(shè)置依賴的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. 實現(xiàn)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實現(xiàn)

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: 調(diào)用depatch(), 只銷毀視圖, 適合主界面
  • FragmentPagerAdapter: 銷毀, 可存數(shù)據(jù)進bundle, 然后保存在onSaveInstanceState

DialogFragment 替代 Dialog和AlertDialog

Dialog和繼承的AlertDialog無法在橫豎屏切換時保存數(shù)據(jù), DialogFragment依靠FragmentManager自動重建并恢復(fù)數(shù)據(jù)

  • 自定義view 的 DialogFragment 創(chuàng)建
    
    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 創(chuàng)建
            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來保存數(shù)據(jù)

系統(tǒng)幫助數(shù)據(jù)恢復(fù)

  • 少量數(shù)據(jù) onSaveInstanceStateonRestoreInstanceState

  • 大量數(shù)據(jù), 無法序列化, 如bitmap, 用Fragment存放, 但是切勿傳遞任何包含context的對象

    1. 創(chuàng)建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調(diào)用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());
        }
    }
    

自行處理數(shù)據(jù)變更(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 回調(diào)AsyncTask, 并在onCreate時調(diào)用 setData

  1. 創(chuàng)建Fragment, 不過setData保存的是AsyncTask
public class OtherRetainedFragment extends Fragment
{

    // data object we want to retain
    // 保存一個異步的任務(wù)
    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. 創(chuàng)建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");
    }

    /**
     * 加載數(shù)據(jù)
     */
    @Override
    protected Void doInBackground(Void... params) {
        items = loadingData();
        return null;
    }

    /**
     * 加載完成回調(diào)當前的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保存大量數(shù)據(jù)",
                "onSaveInstanceState保存數(shù)據(jù)",
                "getLastNonConfigurationInstance已經(jīng)被棄用", "RabbitMQ", "Hadoop",
                "Spark"));
    }

    /**
     * 設(shè)置Activity,因為Activity會一直變化
     *
     * @param activity
     */
    public void setActivity(FixProblemsActivity activity) {
        // 如果上一個Activity銷毀,將與上一個Activity綁定的DialogFragment銷毀
        if (activity == null) {
            mLoadingDialog.dismiss();
        }
        // 設(shè)置為當前的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. 創(chuàng)建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();

    }
    /**
     * 回調(diào)
     */
    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

參考

建議閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容