Androidtab,viewPaper以及frament的使用

  • 2019 年 10 月 11 日
  • 筆記

AndroidTabLayout,ViewPager以及fragment的使用

Demo效果

首先先說一下這個demo的最終效果吧:

項目地址:https://github.com/xiaohuiduan/fragment_demo

 


 

 

我們的Android作業需要我們結合RecyclerView,TabLayout,PaperView以及fragment做一個簡單的Demo,於是便有了這樣的一個demo,其中頁面中的數據來自於玩Android的開放API,使用的是其中的公眾號介面。

  • tabLayout :在圖中表示的上面的能夠進行滑動的tab。

  • fragment:

    fragment 簡單點來說就是一個模組,類似activity,在裡面可以放一些其他的組件(比如說textView等等),並且有著自己的生命周期。但是它必須放在activity中間,具體的一些資訊,可以去看一看官方文檔中文,英文(推薦看英文的,感覺中文的就是機翻,怪怪的)

  • viewPaper:

    這個是一段來自官網的介紹

     


     

     

    ViewPaper就是簡單的頁面切換組件,我們往裡面填充View,然後就可以使用左滑和右滑來進行View的切換。和RecycleView(或者ListView)很類似,我們都需要使用Adapter(PagerAdapter)來填充數據。google官方文檔中,推薦我們使用Fragment來填充ViewPager。

因此簡單點說,就是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) {            }      }  }  

ViewPager的適配器

這個適配器的功能很簡單,就是為了返回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。

Fragment的適配器

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的適配器進行序列化了。

 


 

 

總的來說這個作業還是比較簡單的,只不過在這個序列化的這個地方被坑了好久,查問題感覺自己的頭髮都掉了好多。

Tags: