Android 點將臺:撒豆成兵[- Fragment -]

張風捷特烈發表於2019-04-27

個人所有文章整理在此篇,將陸續更新收錄:知無涯,行者之路莫言終(我的程式設計之路)

Fragment(碎片)

擁有生命週期,含有檢視,可視可互動的介面 。 本文原始碼可見此處

1.Fragment和Activity生命週期的測試
    --- Fragment和Activity生命週期
    --- Fragment和Fragment切換時生命週期
    --- Fragment與巢狀的子Fragment的生命週期
    
2.Fragment和Activity或其他Fragment資料傳遞
    --- Activity --> Fragment
    --- Fragment --> Activity
    --- Fragment --> Fragment
    
3.Fragment和ViewPager的結合以及懶載入的實現
4.Fragment中如何載入子Fragment
複製程式碼

一、生命週期

0.基本的生命週期方法一覽
setUserVisibleHint(boolean isVisibleToUser)
    |--- 第一個回撥函式(多Fragment時),isVisibleToUser使用者是否可見
onCreate(Bundle savedInstanceState):
    |--- Fragment初始化時
onAttach(Context context):
    |--- Fragment與Context已經繫結時
View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
    |--- 建立Fragment的佈局時(載入佈局和findViewById,不建議執行耗時的操作)
onViewCreated(View view, @Nullable Bundle savedInstanceState)
    |--- 當檢視建立完成
onActivityCreated( Bundle savedInstanceState)
    |--- 與Fragment繫結的Activity的onCreate方法已經執行完成並返回(可以進行與Activity互動的UI操作)
onViewStateRestored(Bundle savedInstanceState)
    |--- 通知fragment,該檢視層已儲存
onStart()
    |--- 介面已經可見
onAttachFragment()
    |--- 有子Fragment被新增時回撥
onResume()
    |--- 介面已經可互動
onPause()
    |--- 介面已經可見不可互動
onStop()
    |--- 介面不可見
onSaveInstanceState(Bundle outState)
    |--- 儲存物件狀態
onDestroyView()
    |--- 銷燬與Fragment有關的檢視,但未與Activity解除繫結
onDestroy()
    |--- 銷燬Fragment
onDetach()
    |--- 解除與Activity
複製程式碼

1、當開啟Activity時Fragment的生命週期回撥:

Android 點將臺:撒豆成兵[- Fragment -]

2019-04-26 16:53:27.702: LifeCycleActivity--onCreate: 
2019-04-26 16:53:27.704: Fragment -卍卍卍卍卍卍卍- onAttach: 
2019-04-26 16:53:27.704: Fragment -卍卍卍卍卍卍卍- onCreate: 
2019-04-26 16:53:27.704: Fragment -卍卍卍卍卍卍卍- onCreateView: 
2019-04-26 16:53:27.711: Fragment -卍卍卍卍卍卍卍- onViewCreated: 
2019-04-26 16:53:27.711: Fragment -卍卍卍卍卍卍卍- onActivityCreated: 
2019-04-26 16:53:27.711: Fragment -卍卍卍卍卍卍卍- onViewStateRestored: 
2019-04-26 16:53:27.714: LifeCycleActivity--onStart: 
2019-04-26 16:53:27.715: Fragment -卍卍卍卍卍卍卍- onStart: 
2019-04-26 16:53:27.720: LifeCycleActivity--onResume: 
2019-04-26 16:53:27.720: Fragment -卍卍卍卍卍卍卍- onResume: 
複製程式碼

2.當跳轉到其他Activity時

Android 點將臺:撒豆成兵[- Fragment -]

2019-04-26 17:35:30.611: Fragment -卍卍卍卍卍卍卍- onPause: 
2019-04-26 17:35:30.611: LifeCycleActivity--onPause: 
2019-04-26 17:35:31.342: Fragment -卍卍卍卍卍卍卍- onSaveInstanceState: 
2019-04-26 17:35:31.344: LifeCycleActivity--onSaveInstanceState: 
2019-04-26 17:35:31.345: Fragment -卍卍卍卍卍卍卍- onStop: 
2019-04-26 17:35:31.346: LifeCycleActivity--onStop: 
複製程式碼

3.跳轉後返回時

Android 點將臺:撒豆成兵[- Fragment -]

2019-04-26 17:41:28.254: LifeCycleActivity--onRestart: 
2019-04-26 17:41:28.255: LifeCycleActivity--onStart: 
2019-04-26 17:41:28.256: Fragment -卍卍卍卍卍卍卍- onStart: 
2019-04-26 17:41:28.258: LifeCycleActivity--onResume: 
2019-04-26 17:41:28.258: Fragment -卍卍卍卍卍卍卍- onResume: 
複製程式碼

4.退出該Activity

Android 點將臺:撒豆成兵[- Fragment -]

2019-04-26 18:12:25.328: Fragment -卍卍卍卍卍卍卍- onPause: 
2019-04-26 18:12:25.328: LifeCycleActivity--onPause: 
2019-04-26 18:12:25.864: Fragment -卍卍卍卍卍卍卍- onStop: 
2019-04-26 18:12:25.865: LifeCycleActivity--onStop: 
2019-04-26 18:12:25.865: Fragment -卍卍卍卍卍卍卍- onDestroyView: 
2019-04-26 18:12:25.867: Fragment -卍卍卍卍卍卍卍- onDestroy: 
2019-04-26 18:12:25.867: Fragment -卍卍卍卍卍卍卍- onDetach: 
2019-04-26 18:12:25.868: LifeCycleActivity--onDestroy: 
複製程式碼

5.Fragment被替換時的生命週期

後者先建立,前者再銷燬

Android 點將臺:撒豆成兵[- Fragment -]

2019-04-26 20:10:20.095 : Fragment -卍卍卍卍卍卍卍- onAttach: 
2019-04-26 20:10:20.095 : Fragment -卍卍卍卍卍卍卍- onCreate: 
2019-04-26 20:10:20.095 : Fragment -卍卍卍卍卍卍卍- onCreateView: 
2019-04-26 20:10:20.112 : Fragment -卍卍卍卍卍卍卍- onViewCreated: 
2019-04-26 20:10:20.112 : Fragment -卍卍卍卍卍卍卍- onActivityCreated: 
2019-04-26 20:10:20.113 : Fragment -卍卍卍卍卍卍卍- onViewStateRestored: 
2019-04-26 20:10:20.113 : Fragment -卍卍卍卍卍卍卍- onStart: 
2019-04-26 20:10:20.115 : Fragment -卍卍卍卍卍卍卍- onResume: 

2019-04-26 20:10:20.115 : Fragment -卍卍卍卍卍卍卍- onPause: 
2019-04-26 20:10:20.115 : Fragment -卍卍卍卍卍卍卍- onStop: 
2019-04-26 20:10:20.116 : Fragment -卍卍卍卍卍卍卍- onDestroyView: 
2019-04-26 20:10:20.118 : Fragment -卍卍卍卍卍卍卍- onDestroy: 
2019-04-26 20:10:20.118 : Fragment -卍卍卍卍卍卍卍- onDetach: 
複製程式碼

6.巢狀Fragment的生命週期

卍卍卍卍卍卍卍表示父, ☯☯☯☯☯☯☯☯表示子

---->[開啟時]-----------先父後子,注意在onStart之後子Fragment開始attach---------------
2019-04-27 08:36:44.332 : Fragment -卍卍卍卍卍卍卍- onAttach: 
2019-04-27 08:36:44.332 : Fragment -卍卍卍卍卍卍卍- onCreate: 
2019-04-27 08:36:44.346 : Fragment -卍卍卍卍卍卍卍- onCreateView: 
2019-04-27 08:36:44.351 : Fragment -卍卍卍卍卍卍卍- onViewCreated: 
2019-04-27 08:36:44.351 : Fragment -卍卍卍卍卍卍卍- onActivityCreated: 
2019-04-27 08:36:44.351 : Fragment -卍卍卍卍卍卍卍- onViewStateRestored: 
2019-04-27 08:36:44.352 : Fragment -卍卍卍卍卍卍卍- onStart: 
2019-04-27 08:36:44.353 : Fragment -☯☯☯☯☯☯☯☯- onAttach: 
2019-04-27 08:36:44.353 : Fragment -卍卍卍卍卍卍卍- onAttachFragment:  <--- 注意這裡
2019-04-27 08:36:44.353 : Fragment -☯☯☯☯☯☯☯☯- onCreate:  
2019-04-27 08:36:44.354 : Fragment -☯☯☯☯☯☯☯☯- onCreateView: 
2019-04-27 08:36:44.358 : Fragment -☯☯☯☯☯☯☯☯- onViewCreated: 
2019-04-27 08:36:44.358 : Fragment -☯☯☯☯☯☯☯☯- onActivityCreated: 
2019-04-27 08:36:44.358 : Fragment -☯☯☯☯☯☯☯☯- onViewStateRestored: 
2019-04-27 08:36:44.358 : Fragment -☯☯☯☯☯☯☯☯- onStart: 
2019-04-27 08:36:44.365 : Fragment -卍卍卍卍卍卍卍- onResume: 
2019-04-27 08:36:44.365 : Fragment -☯☯☯☯☯☯☯☯- onResume: 

---->[退出時]-----------先子後父交替回撥---------------
2019-04-27 08:44:13.697 : Fragment -☯☯☯☯☯☯☯☯- onPause: 
2019-04-27 08:44:13.698 : Fragment -卍卍卍卍卍卍卍- onPause: 
2019-04-27 08:44:14.639 : Fragment -☯☯☯☯☯☯☯☯- onStop: 
2019-04-27 08:44:14.639 : Fragment -卍卍卍卍卍卍卍- onStop: 
2019-04-27 08:44:14.654 : Fragment -☯☯☯☯☯☯☯☯- onDestroyView: 
2019-04-27 08:44:14.654 : Fragment -卍卍卍卍卍卍卍- onDestroyView: 
2019-04-27 08:44:14.655 : Fragment -☯☯☯☯☯☯☯☯- onDestroy: 
2019-04-27 08:44:14.655 : Fragment -☯☯☯☯☯☯☯☯- onDetach: 
2019-04-27 08:44:14.655 : Fragment -卍卍卍卍卍卍卍- onDestroy: 
2019-04-27 08:44:14.655 : Fragment -卍卍卍卍卍卍卍- onDetach: 
複製程式碼

二、資料傳遞

1. Activity-->Fragment

實現:在Activity傳入顏色資料,在Fragment中接收資料並使用

Android 點將臺:撒豆成兵[- Fragment -]

---->[在Activity中設定Fragment的引數]-----------------------------------
FragmentManager fm = getFragmentManager();//1.獲取FragmentManager
FragmentTransaction ft = fm.beginTransaction();//2.fm開啟事務

Bundle bundle = new Bundle();//建立Bundle物件
bundle.putString("data", "#ff0000");//為bundle賦值
BoxFragment boxFragment = new BoxFragment();
boxFragment.setArguments(bundle);//為Fragment設定Arguments

//3.動態新增 (控制元件id,fragment物件,tag)
ft.add(R.id.fl_fragmemt_content, new ResultFragment());
ft.commit();//4.提交事務

---->[在BoxFragment中讀取引數並使用]-----------------------------------
TextView txt = view.findViewById(R.id.id_tv_txt);
Bundle bundle = getArguments();
if (bundle != null) {
    String result = bundle.getString("color");
    view.setBackgroundColor(Color.parseColor(result));
    txt.setText(result);
}
複製程式碼

這可以稍微改進一下:將Bundle在Fragment中建立,通過一個靜態方法+入參建立Fragment

---->[在BoxFragment中新增靜態方法]-----------------------------------
public static BoxFragment newInstance(String color) {
    BoxFragment fragment = new BoxFragment();
    Bundle bundle = new Bundle();
    bundle.putString("color", color);
    fragment.setArguments(bundle);
    return fragment;
}

---->[在Activity中建立物件]-----------------------------------
BoxFragment boxFragment = BoxFragment.newInstance("#238AF8")
複製程式碼

好處在於方便建立Fragment,比如加個紅色:

Android 點將臺:撒豆成兵[- Fragment -]

BoxFragment radFragment = BoxFragment.newInstance("#ff0000")
複製程式碼

2. Fragment-->Activity

點選Fragment內的View,Fragment將顏色值傳給Activity

Android 點將臺:撒豆成兵[- Fragment -]

  • 方式一、通過回撥
---->[在BoxFragment中新增回撥介面]-----------------------------------
public interface OnDataSend {
    void send(String data);
}
private OnDataSend mOnDataSend;
public void setmOnDataSend(OnDataSend mOnDataSend) {
    this.mOnDataSend = mOnDataSend;
}

---->[在BoxFragment#onViewCreated觸發回撥]-----------------------------------
txt.setOnClickListener(v -> {
    if (mOnDataSend != null) {
        mOnDataSend.send(txt.getText().toString().toUpperCase());
    }
});

---->[在Activity中設定回撥]-----------------------------------
radFragment.setmOnDataSend {
    id_tv_result.text = it
}
複製程式碼

  • 方式二、向上轉型,獲取宿主物件並呼叫方法
---->[在Activity中新增公共方法]-----------------------------------
fun setData(s: String) {
    id_tv_result.text = s
}

---->[在BoxFragment#onViewCreated獲取宿主物件並呼叫方法]-----------
txt.setOnClickListener(v -> {//這裡強轉最好加個instanceof判斷
    LifeCycleActivity activity = (LifeCycleActivity) getActivity();
    activity.setData(txt.getText().toString().toUpperCase());
});
複製程式碼

挺方便的,不過這對宿主Activity增加了負擔,不須強轉成宿主型別,感覺有點狹隘
感覺新建一個介面用來規定資料傳輸的方法,應該會好一些,就像正規軍和游擊隊吧...

//資料傳送的介面(當然你可以定義很多介面,這裡只舉個例子)
public interface IBoxSender {
    void setData(String data);
}

---->[在Activity實現介面]-----------------------------------
class LifeCycleActivity : AppCompatActivity(), IBoxSender {
...
    override fun setData(s: String) {
        id_tv_result.text = s
    }
}

---->[在BoxFragment#onViewCreated獲取宿主物件並呼叫方法]-----------
txt.setOnClickListener(v -> {//這裡強轉最好加個instanceof判斷
    IBoxSender sender = (IBoxSender) getActivity();
    sender.setData(txt.getText().toString().toUpperCase());
});
//從我個人審美來說,這樣似乎更優雅一點
複製程式碼

當然你也可以將控制元件直接傳給Fragment,但感覺太無聊了,就不說了


3.Fragment-->Fragment

紅色Fragment在點選時,將自己的顏色值傳送給藍色Fragment,藍色Fragment接收後變色
很容易想到分兩步:紅色Fragment --> Activity , Activity --> 藍色Fragment

Android 點將臺:撒豆成兵[- Fragment -]

//資料傳送的介面
public interface IBoxSender {
    void setData(String data);
    void update(String color); //增加介面
}

---->[在Activity實現介面方法]-----------------------------------
override fun update(color: String) {
    fragmentManager.beginTransaction()
        .replace(R.id.fl_title, BoxFragment.newInstance(color))
        .commit()
}

---->[在BoxFragment#onViewCreated獲取宿主物件並呼叫方法]-----------
txt.setOnClickListener(v -> {//這裡強轉最好加個instanceof判斷
    IBoxSender sender = (IBoxSender) getActivity();
    sender.update(txt.getText().toString());
});
複製程式碼

4.Fragment可以寫建構函式傳入引數嗎?

這是曾經讓我疑惑的一點:建構函式入參來傳參不是挺好的嗎?但是:
貌似AS 不給我們用構造,需要通過Fragment#setArguments(Bundle)來傳參

Android 點將臺:撒豆成兵[- Fragment -]

如果我任性,偏要用呢?----雖然畫紅線但是還是執行還是能跑起來的,效果也沒有差別, 於是乎,問題來了: 為什麼谷歌的大佬不推薦我們在Fragment中使用建構函式呢?

|--- 在旋轉螢幕時:Fragment將面臨 銷燬+重建 ,但測試中Fragment並沒有什麼變化
|--- 重建的Fragment是系統幫我們做的,那它怎麼還原剛才的引數呢(顏色)?一個詞 :Bundle
|--- 下面是Fragment中建立ragment靜態方法,其中用反射例項化了無參構造,並將配置引數進行還原
|--- 所以為什麼不能自定義Fragment的建構函式不言而喻:Fragment銷燬+重建只會使用無參構造

public static Fragment instantiate(Context context, String fname) {
    return instantiate(context, fname, null);
}

public static Fragment instantiate(Context context, String fname, @Nullable Bundle arg)
    try {
        Class<?> clazz = sClassMap.get(fname);
        if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = context.getClassLoader().loadClass(fname);
            if (!Fragment.class.isAssignableFrom(clazz)) {
                throw new InstantiationException("Trying to instantiate a class " + f
                        + " that is not a Fragment", new ClassCastException());
            }
            sClassMap.put(fname, clazz);
        }
        Fragment f = (Fragment) clazz.getConstructor().newInstance(); <--- 注意這裡用的是無參構造建立f物件
        if (args != null) { <--- 當Binder物件非空時,會將args再給f物件,也就還原了配置
            args.setClassLoader(f.getClass().getClassLoader());
            f.setArguments(args);
        }
        return f;
    } catch (ClassNotFoundException e) {
        .....
    }
}
複製程式碼

你可以試一下:使用Fragment一參構造,然後轉屏時,程式會崩掉,所以我們們還是別任性...


三、Fragment與ViewPager的愛恨情仇

1.最簡單的Fragment + ViewPager

Android 點將臺:撒豆成兵[- Fragment -]

public class ViewPagerActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewpager);
        ViewPager viewPager = findViewById(R.id.id_vp);
        //顏色陣列
        String[] colors = new String[]{"#F73815", "#FAA43E", "#FCE73C", "#51F81E", "#1E94F8", "#8CE9F4", "#B24DF4"};
        //詳情陣列
        String[] info = new String[]{"紅", "橙", "黃", "綠", "藍", "靛", "紫"};
        
        viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {//設定FragmentPagerAdapter
            @Override
            public Fragment getItem(int position) {
                return BoxFragment.newInstance(colors[position],info[position]);
            }
            @Override
            public int getCount() {
                return colors.length;
            }
        });
    }
}
複製程式碼

2.滑動時Fragment的生命週期回撥
  • 開啟時

isVisibleToUser 出現了,而且是最先呼叫的,一開始是紅色 isVisibleToUser= true
總的來說就是生成了紅色和黃色兩個Fragment並對兩者其進行了初始化

2019-04-26 23:12:59.444 - isVisibleToUser: 紅---false
2019-04-26 23:12:59.444 - isVisibleToUser: 橙---false
2019-04-26 23:12:59.444 - isVisibleToUser: 紅---true
2019-04-26 23:12:59.446 - onAttach: 紅
2019-04-26 23:12:59.446 - onCreate: 紅
2019-04-26 23:12:59.446 - onAttach: 橙
2019-04-26 23:12:59.446 - onCreate: 橙
2019-04-26 23:12:59.447 - onCreateView: 紅
2019-04-26 23:12:59.455 - onViewCreated: 紅
2019-04-26 23:12:59.456 - onActivityCreated: 紅
2019-04-26 23:12:59.456 - onViewStateRestored: 紅
2019-04-26 23:12:59.456 - onStart: 紅
2019-04-26 23:12:59.456 - onResume: 紅
2019-04-26 23:12:59.457 - onCreateView: 橙
2019-04-26 23:12:59.463 - onViewCreated: 橙
2019-04-26 23:12:59.463 - onActivityCreated: 橙
2019-04-26 23:12:59.463 - onViewStateRestored: 橙
2019-04-26 23:12:59.464 - onStart: 橙
2019-04-26 23:12:59.464 - onResume: 橙
複製程式碼

  • 滑到下一屏()橙色

橙色和使用者見面了,所以橙色的 isVisibleToUser= true
這時對黃色Fragment(即下一頁)進行了初始化,俗稱預載入

2019-04-26 23:16:53.738 - isVisibleToUser: 黃---false
2019-04-26 23:16:53.738 - isVisibleToUser: 紅---false
2019-04-26 23:16:53.738 - isVisibleToUser: 橙---true
2019-04-26 23:16:53.739 - onAttach: 黃
2019-04-26 23:16:53.740 - onCreate: 黃
2019-04-26 23:16:53.743 - onCreateView: 黃
2019-04-26 23:16:53.766 - onViewCreated: 黃
2019-04-26 23:16:53.767 - onActivityCreated: 黃
2019-04-26 23:16:53.767 - onViewStateRestored: 黃
2019-04-26 23:16:53.767 - onStart: 黃
2019-04-26 23:16:53.767 - onResume: 黃
複製程式碼

  • 滑到下一屏(黃色)

黃色和使用者見面了,所以黃色的 isVisibleToUser= true 紅色(上上頁)銷燬了,綠色(下一頁)進行初始化

2019-04-26 23:19:38.122 - isVisibleToUser: 綠---false
2019-04-26 23:19:38.123 - isVisibleToUser: 橙---false
2019-04-26 23:19:38.123 - isVisibleToUser: 黃---true
2019-04-26 23:19:38.124 - onAttach: 綠
2019-04-26 23:19:38.125 - onCreate: 綠
2019-04-26 23:19:38.129 - onPause: 紅
2019-04-26 23:19:38.130 - onStop: 紅
2019-04-26 23:19:38.132 - onDestroyView: 紅
2019-04-26 23:19:38.140 - onCreateView: 綠
2019-04-26 23:19:38.154 - onViewCreated: 綠
2019-04-26 23:19:38.155 - onActivityCreated: 綠
2019-04-26 23:19:38.155 - onViewStateRestored: 綠
2019-04-26 23:19:38.155 - onStart: 綠
2019-04-26 23:19:38.156 - onResume: 綠
複製程式碼

之後都是相似的,當前頁的上上頁(如果有的話)會被銷燬,下一頁(如果有的話)會被初始化到onResume


3.懶載入的實現

也就是不想要預載入,畢竟有些時候不想提前為以後的消耗買單

  • 方法一、針對Fragment,讓其不要載入下一頁資料(但Fragment還是會建立的)
private boolean initialization; // 介面是否已初始化完畢
private boolean isVisibleToUser; // 是否對使用者可見

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    this.isVisibleToUser = isVisibleToUser;
    lazyLoad();
}

private void lazyLoad() {
    if (initialization && isVisibleToUser) {
        loadData();
    }
}

/**
 * 核心載入方法(可抽象)
 */
private void loadData() {
    Log.e("loadData", "initData: "+info);
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    Log.e(TAG, "Fragment -卍卍卍卍卍卍卍- onActivityCreated: " + info);
    initialization = true;
    lazyLoad();
    super.onActivityCreated(savedInstanceState);
}
複製程式碼

  • 方法2:針對ViewPager

預載入是ViewPager的鍋,雖然可以設定預載入多個,但是不能不預載入...
鍋是這行程式碼的private static final int DEFAULT_OFFSCREEN_PAGES = 1;// 預設的載入頁面
所以自定義一個View 將ViewPager程式碼拷貝一份,上面哪行改成0,程式碼不貼了,詳見此處
但是,考慮到相容問題,還是用懶載入Fragment比較好,畢竟建立兩個物件也沒什麼大不了,載入資料限制住就OK了


4.ViewPager的動畫效果

既然提到ViewPager就簡單說一下吧

Android 點將臺:撒豆成兵[- Fragment -]

|--- 使用方式 ------------------------------------------
viewPager.setPageTransformer(true, new VPTFadeScale());

public class VPTFadeScale implements ViewPager.PageTransformer {
    private static float MIN_SCALE = 0.7f;

    //A==>B  A的position 0==>-1   B的position 1==>0
    @Override
    public void transformPage(View page, float position) {
        int width = page.getWidth();
        int height = page.getHeight();

        if (position < -1) {//非A、B頁
            page.setAlpha(1);
        } else if (position <= 0) {//A頁的動畫
            page.setAlpha(1 + position * 2);
            page.setScaleX(1);
            page.setScaleY(1);

            page.setPivotX(0);
            page.setPivotY(height / 2);

            page.setRotationX(-100 * position);
            page.setRotationY(-100 * position);

        } else if (position <= 1) {//B頁的動畫
            page.setAlpha(1 - position);
            page.setTranslationX(width * (-position));
//            0.75~1
            float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position));
            page.setScaleX(scaleFactor);
            page.setScaleY(scaleFactor);
        }
    }
}
複製程式碼

5、ViewPager滑動監聽

根據滑動時的引數可以做一些好玩的事

Android 點將臺:撒豆成兵[- Fragment -]

//[]ViewPager滑動監聽
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    /**
     * 當頁面滑動過程中的回撥
     * @param position             當前滑動頁面的位置
     * @param positionOffset       下一頁在當前頁所佔的寬度百分比
     * @param positionOffsetPixels 下一頁在當前頁所佔的寬度畫素值
     */
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels
        Log.e(TAG, "onPageScrolled: " + "position==>"
                + position + "----positionOffset==>"
                + positionOffset + "----positionOffsetPixels" + positionOffsetPixels);
    }
    /**
     * 某個頁面被選中(從0計數) 翻頁成功才會呼叫
     * @param position 翻頁後的檢視在集合中位置
     */
    @Override
    public void onPageSelected(int position) {
        Log.e(TAG, "onPageSelected: " + position);
    }
    /**
     * 頁面狀態發生變化的回撥  1 滑動開始到手指離開前  2 手指離開後到結束之間 0  滑動結束
     * @param state 狀態
     */
    @Override
    public void onPageScrollStateChanged(int state) {
        Log.e(TAG, "onPageScrollStateChanged: " + state);
    }
});
複製程式碼

四、子Fragment的使用

1.基本使用

關鍵是通過getChildFragmentManager獲取管理器,注意要在onStart或之後獲取

Android 點將臺:撒豆成兵[- Fragment -]

---->[PagerFragment]------------一個孩子----------
 @Override
 public void onStart() {
     SideFragment fragment = new SideFragment();
     getChildFragmentManager()
             .beginTransaction()
             .add(R.id.fl_side, fragment)
             .show(fragment)
             .commit();
 }
 
---->[PagerFragment]------------多個孩子----------
@Override
public void onStart() {
    super.onStart();
    SideFragment side = new SideFragment();
    BoxFragment title = BoxFragment.newInstance("#eeeeee");
    BoxFragment footer = BoxFragment.newInstance("#eeeeee");
    getChildFragmentManager()
            .beginTransaction()
            .add(R.id.fl_side, side)
            .add(R.id.fl_title, title)
            .add(R.id.fl_bottom, footer)
            .show(title)
            .show(side)
            .show(footer)
            .commit();
}
複製程式碼

2.隱藏子Fragment

效果是點選主Fragment側邊欄會顯示/隱藏切換

private boolean sideShowing = true;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    Log.e(TAG, "Fragment -卍卍卍卍卍卍卍- onViewCreated: ");
    super.onViewCreated(view, savedInstanceState);
    view.setOnClickListener(v -> {
        if (sideShowing) {
            hideAt(0);
        } else {
            showAt(0);
        }
        sideShowing = !sideShowing;
    });
}
public void hideAt(int i) {
    List<Fragment> fragments = getChildFragmentManager().getFragments();
    getChildFragmentManager().beginTransaction().hide(fragments.get(i)).commit();
}
public void showAt(int i) {
    List<Fragment> fragments = getChildFragmentManager().getFragments();
    getChildFragmentManager().beginTransaction().show(fragments.get(i)).commit();
}
複製程式碼

四、其他要說的

1. startActivityForResult + onActivityResult

Android 點將臺:撒豆成兵[- Fragment -]

view.setOnLongClickListener(v -> {
    Intent i = new Intent(Intent.ACTION_PICK,
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(i, 0);// 設定結果返回
    return false;
});

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (data != null) {
        switch (requestCode) {
            case 0:
                //開啟相簿並選擇照片,這個方式選擇單張
                // 獲取返回的資料,這裡是android自定義的Uri地址
                Uri selectedImage = data.getData();
                Log.e("startActivityForResult", "startActivityForResult: " + selectedImage);
                break;
        }
    }
}
複製程式碼

2.動態許可權申請

注意申請時用XXXFragment.this.requestPermissions 否則onRequestPermissionsResult 無法回撥

private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS = {
        Manifest.permission.RECORD_AUDIO,//錄音許可權
        Manifest.permission.CAMERA,//相機許可權
        Manifest.permission.WRITE_EXTERNAL_STORAGE//SD卡寫許可權
};

view.setOnClickListener(v -> {//點選申請許可權
    if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
            checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID) &&
            checkSelfPermission(REQUESTED_PERMISSIONS[2], PERMISSION_REQ_ID)) {
        //執行到此處說明已有許可權成功
        Toast.makeText(getActivity(), "已有許可權成功", Toast.LENGTH_SHORT).show();
    }
});

/**
 * 檢查許可權的方法
 *
 * @param permission  許可權
 * @param requestCode 請求碼
 * @return 是否擁有許可權
 */
public boolean checkSelfPermission(String permission, int requestCode) {
    if (ContextCompat.checkSelfPermission(getActivity(), permission)
            != PackageManager.PERMISSION_GRANTED) {
        //傳送許可權請求
        PagerFragment.this.requestPermissions(REQUESTED_PERMISSIONS, requestCode); <-- 注意申請時用XXXFragment.this.
        return false;
    }
    return true;
}


@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case PERMISSION_REQ_ID: {//請求碼
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED ||
                    grantResults[1] != PackageManager.PERMISSION_GRANTED ||
                    grantResults[2] != PackageManager.PERMISSION_GRANTED) {
                //三個許可權有任意的未被允許,彈吐司,退出
                Toast.makeText(getActivity(), "使用者沒有允許許可權", Toast.LENGTH_SHORT).show();
                getActivity().finish();
                break;
            }
            Log.e(TAG, "onRequestPerm: OK");
            break;
        }
    }
}
複製程式碼

3.Fragment的優勢
[1].將整個介面的責任碎片化,分散到各個部分,緩解Activity的負擔
[2].方便修改/更新:那個地方出現問題/需要更新介面樣式,可以直接去找對應的Fragment,而不是像以前在Activity裡大海撈針
[3].方便複用: Fragment遷移很方便,哪裡需要哪裡搬。遇到差不多的需求,改改就能用了。
[4].執行中可以動態地移除、加入、交換,使用靈活
[5].可以`startActivityForResult + onActivityResult`,有目的的開啟一個Activity
[6].可以動態申請許可權 requestPermissions + onRequestPermissionsResult
[7].ViewPager和Fragment結合容易實現切換效果
複製程式碼

相關文章