安卓tab,viewPaper以及frament的使用

安卓TabLayout,ViewPager以及fragment的使用

Demo效果

首先先說一下這個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) {

        }
    }
}

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

 


 

 

總的來講這個做業仍是比較簡單的,只不過在這個序列化的這個地方被坑了很久,查問題感受本身的頭髮都掉了好多。

相關文章
相關標籤/搜索