首先先說一下這個demo的最終效果吧:php
項目地址:https://github.com/xiaohuiduan/fragment_demohtml
咱們的安卓做業須要咱們結合RecyclerView,TabLayout,PaperView以及fragment作一個簡單的Demo,因而便有了這樣的一個demo,其中頁面中的數據來自於玩安卓的開放API,使用的是其中的公衆號接口。java
tabLayout :在圖中表示的上面的可以進行滑動的tab。android
fragment:git
fragment 簡單點來講就是一個模塊,相似activity,在裏面能夠放一些其餘的組件(好比說textView等等),而且有着本身的生命週期。可是它必須放在activity中間,具體的一些信息,能夠去看一看官方文檔中文,英文(推薦看英文的,感受中文的就是機翻,怪怪的)github
viewPaper:app
這個是一段來自官網的介紹async
ViewPaper就是簡單的頁面切換組件,咱們往裏面填充View,而後就可使用左滑和右滑來進行View的切換。和RecycleView(或者ListView)很相似,咱們都須要使用Adapter(PagerAdapter)來填充數據。google官方文檔中,推薦咱們使用Fragment來填充ViewPager。ide
所以簡單點說,就是TabLayout和ViewPager相關聯,而後經過滑動ViewPager實現Fragment的切換,而後在Fragment中使用RecycleView來顯示數據。函數
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical">
<com.google.android.material.tabs.TabLayout android:id="@+id/tablayout" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" app:tabMode="scrollable" xmlns:android="http://schemas.android.com/apk/res/android" />
<androidx.viewpager.widget.ViewPager android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/article_title_view_pager" />
</LinearLayout>
MainActivity的代碼
public class MainActivity extends AppCompatActivity {
private TabLayout tabLayout;
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = findViewById(R.id.article_title_view_pager);
init();
}
private void init() {
// 下面的這個表明目前是橫屏仍是豎屏,不須要理會
if (findViewById(R.id.land_content) != null) {
PhoneMsg.isTwoPane = true;
} else {
PhoneMsg.isTwoPane = false;
}
initTab();
}
public void initTab() {
try {
// 下面的表明獲取數據,咱們只須要知道PhoneMsg.wxAuthorList保存了文章的信息便可
AsyncTask<ProcessInterface, Integer, Object> asyncTask = new AsyncRequest().execute(new GetWxAuthorList());
PhoneMsg.wxAuthorList = (WxAuthorList) asyncTask.get();
tabLayout = findViewById(R.id.tablayout);
// 建立Tab頁
for (int i = 0; i < PhoneMsg.wxAuthorList.getData().size(); i++) {
tabLayout.addTab(tabLayout.newTab());
}
// ArticleTitlePageAdapter表示的就是ViewPager的適配器(後面會說明),其中咱們須要傳過去的參數是,getSupportFragmentManager():爲了拿到Fragment的控制器, tabLayout.getTabCount(),得到view的個數
ArticleTitlePageAdapter adapter = new ArticleTitlePageAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
viewPager.setAdapter(adapter);
// 這一步是爲了將tabLayout與viewpaper同步,值得注意的點是:若是將這一步放在tablayOut的setText後面,則會致使tab的名字空白
tabLayout.setupWithViewPager(viewPager);
// 建立Tab頁
for (int i = 0; i < PhoneMsg.wxAuthorList.getData().size(); i++) {
String name = PhoneMsg.wxAuthorList.getData().get(i).getName();
int id = PhoneMsg.wxAuthorList.getData().get(i).getId();
tabLayout.getTabAt(i).setText(name);
// ArticleListAdapter表明的是RecycleView的適配器,咱們將這個Adapter保存起來(爲何要這樣作我後面說)
PhoneMsg.articleListAdapters.add(i,new ArticleListAdapter(MainActivity.this,id));
}
// 添加選擇事件
tabLayout.addOnTabSelectedListener(new TabClick(this));
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class TabClick implements TabLayout.OnTabSelectedListener {
Context context;
public TabClick(Context context) {
this.context = context;
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
// 將viewPaper的位置改變
viewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
}
}
這個適配器的功能很簡單,就是爲了返回Fragment去填充ViewPager。能夠看到下面的代碼,就是根據不一樣position來產生不一樣的Fragment。
public class ArticleTitlePageAdapter extends FragmentStatePagerAdapter {
private int num;
private HashMap<Integer,ArticleTitleFra> map;
public ArticleTitlePageAdapter(@NonNull FragmentManager fm, int num) {
super(fm, num);
this.num = num;
map = new HashMap(num);
}
/** * 返回對應位置的Fragment * @param position 表明目前滑動所處的位置 * @return */
@NonNull
@Override
public Fragment getItem(int position) {
if (map.containsKey(position)){
return map.get(position);
}
return createFragment(position);
}
/** * 建立一個frame * @param position * @return */
private Fragment createFragment(int position) {
// ArticleTitleFra.newInstance(position)會返回一個Fragment
map.put(position, ArticleTitleFra.newInstance(position));
return map.get(position);
}
@Override
public int getCount() {
return num;
}
}
下面就是關於Fragment的適配器,其中在Fragment中是一個RecycleView。
public class ArticleTitleFra extends Fragment {
private View view;
private RecyclerView recyclerView;
private ArticleListAdapter adapter;
public static ArticleTitleFra newInstance(int position) {
Bundle bundle = new Bundle();
bundle.putInt("Position", position);
ArticleTitleFra articleTitleFra = new ArticleTitleFra();
articleTitleFra.setArguments(bundle);
return articleTitleFra;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
int position = getArguments().getInt("Position");
// PhoneMsg.articleListAdapters裏面保存了RecycleView的適配器
adapter = PhoneMsg.articleListAdapters.get(position);
// article_title_fra表示的就是Fragment 的xml文件
view = inflater.inflate(R.layout.article_title_fra, container, false);
// 設置recyclerView
initFrame();
// refresh()目的是爲了設置recycleView的Adapter
refresh();
return view;
}
private void initFrame() {
recyclerView = view.findViewById(R.id.article_list);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
// 設置 ItemAnimator動畫
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
/** * 刷新數據 * * @param */
public void refresh() {
recyclerView.setAdapter(this.adapter);
}
}
RecycleView的適配器我就不展現code了。在這裏個裏面咱們有一個值得注意的點,在Fragment中,咱們是使用了一個newInstance來示例化一個對象,而且將position保存在Bundle中,爲何咱們這樣作呢?假設咱們不這樣作,那麼在橫豎屏切換的時候會產生一個問題。就是在豎屏切換到橫屏的時候,recycleView的數據會消失。(值得注意點的是:在MainActivity中onCreate方法會再次執行一遍)。按照道理來講,不該該出現這種狀況的,那麼爲何會出現這種狀況呢?初略的瀏覽了一下源代碼,我覺的多是這樣的:
FragmentStatePagerAdapter中會將Fragment進行序列化,在加載新的頁面的時候,它會看之前有沒有進行加載,若是加載了,則從序列化的數據中將Fragment拿出來。咱們能夠用Log進行日誌輸出,而後會發現,在橫豎屏轉動的時候,這一個日誌並不會進行打印。
那麼,爲何咱們使用newInstance來實例化對象而不是經過構造方法來示例化對象呢?很惋惜,不行,由於在Fragment中,會經過反射調用Fragment的無參構造函數來得到Fragment的實例。因此若是咱們在Fragment中只寫了有參構造器而沒有寫無參構造器,那麼程序則會報
java.lang.RuntimeException: Unable to start activity ComponentInfo{cc.weno.android_news/cc.weno.android_news.MainActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment cc.weno.android_news.fragment.ArticleTitleFra: could not find Fragment constructor
的錯誤。而在Fragment序列化的過程當中,會將Bundle進行序列化,而在反序列化中,又會將boundlei進行反序列化,so,咱們就能夠在建立實例化的過程當中將同時設置Bundle,所以下面的狀況也是ok的。
public ArticleTitleFra(){ } public ArticleTitleFra(int position){ Bundle bundle = new Bundle(); bundle.putInt("Position", position); setArguments(bundle); }
這也就解釋了爲何我使用PhoneMsg.articleListAdapters
來保存recycleView的適配器了,由於在Fragment createView的時候,咱們根據position的位置來得到對應的適配器。那麼咱們將recycleview的適配器添加到Bundle中呢?固然沒有問題,不過咱們就須要對recycleview的適配器進行序列化了。
總的來講這個做業仍是比較簡單的,只不過在這個序列化的這個地方被坑了很久,查問題感受本身的頭髮都掉了好多。