1. 程式人生 > >Fragment的基本應用

Fragment的基本應用

ntb back uil 不起作用 span 有一個 每一個 number listener

轉載請註明出處:http://blog.csdn.net/crazy1235/article/details/50933621


Fragment

還是先來基本介紹。

Fragment –> 片段
在Android3.0的時候被引入,它的出現主要是給大屏幕設備提供更加靈活的UI支持。通過對Activity布局進行分片。更加方便的對每塊進行獨立控制。這些片段能夠被不同的activity復用。


fragment生命周期

每一個fragment擁有自己的生命周期,可是fragment要依賴於activity存在,生命周期受到包含它的activity的生命周期控制。

來兩張神圖~~

技術分享 技術分享

左圖就是fragment的生命周期圖。右圖是fragment與activity各自生命周期的對比。

介紹一下經常使用的幾個生命周期函數:

  • onAttach(Context) –> 當fragment被綁定到activity上時回調

  • onCreate(Bundle) –> 當fragment對象創建的時候回調。一般在此方法裏做參數接收。

  • onCreateView(LayoutInflater, ViewGroup, Bundle) –> 創建fragment視圖時回調

  • onDestoryView –> 視圖銷毀時回調

  • onDestory –> 銷毀fragment時回調

  • onDetech() –> fragment與activity失去關聯時回調


fragment的使用

使用fragment能夠當成一個控件,直接放到activity布局文件中。也能夠在代碼裏面動態的加入、更新或者刪除。

以下的activity布局文件中定義了一個fragment和一個frameLayout。

使用標簽能夠稱之為靜態的Fragment。在activity創建的時候也會去創建並顯示它,而framelayout是一個容器,我們在代碼中能夠動態的加入一個fragment進去。

<?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" android:orientation="horizontal" tools:context=".fragments.ArticleActivity"> <!--headlines--> <fragment android:id="@+id/headline_fragment" android:name="com.jacksen.demo.view.fragments.HeadlinesFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" tools:layout="@layout/fragment_item_list" /> <!--article--> <FrameLayout android:id="@+id/article_frame_layout" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" /> </LinearLayout>

註意:

使用標簽顯示Fragment的時候,須要對這個fragment設置一個id或者tag。否則會出現”Error inflating class fragment”錯誤。

技術分享

Caused by: java.lang.IllegalArgumentException: Binary XML file line #10: Must specify unique android:id, android:tag, or have a parent with an id for com.jacksen.demo.view.fragments.HeadlinesFragment

Fragment的子類繼承的時候。假設你的minSdkVersion <= 11,須要引入V4包,然後倒入android.support.v4.app.Fragment包。

假設是大於11。直接導入android.app.Fragment包就可以。

package com.jacksen.demo.view.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.jacksen.demo.view.R;
import com.jacksen.demo.view.fragments.dummy.ArticleBean;
import com.jacksen.demo.view.fragments.dummy.ArticleBean.ArticleItem;

public class HeadlinesFragment extends Fragment {
    private static final String ARG_COLUMN_COUNT = "column-count";
    private int mColumnCount = 2;
    private OnChangeArticleListener mListener;

    public HeadlinesFragment() {
    }
    public static HeadlinesFragment newInstance(int columnCount) {
        HeadlinesFragment fragment = new HeadlinesFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_COLUMN_COUNT, columnCount);
        fragment.setArguments(args);
        return fragment;
    }
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d("HeadlinesFragment", "onAttach");
        if (context instanceof OnChangeArticleListener) {
            mListener = (OnChangeArticleListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnChangeArticleListener");
        }
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("HeadlinesFragment", "onCreate");
        if (getArguments() != null) {
            mColumnCount = getArguments().getInt(ARG_COLUMN_COUNT);
        }
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d("HeadlinesFragment", "onCreateView");
        View view = inflater.inflate(R.layout.fragment_item_list, container, false);
        // Set the adapter
        if (view instanceof RecyclerView) {
            Context context = view.getContext();
            RecyclerView recyclerView = (RecyclerView) view;
            if (mColumnCount <= 1) {
                recyclerView.setLayoutManager(new LinearLayoutManager(context));
            } else {
                recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount));
            }
            recyclerView.setAdapter(new HeadlinesRecyclerViewAdapter(ArticleBean.ITEMS, mListener));
        }
        return view;
    }
    @Override
    public void onDetach() {
        super.onDetach();
        Log.d("HeadlinesFragment", "onDetach");
        mListener = null;
    }
    public interface OnChangeArticleListener {
        void onChangeArticle(ArticleItem item);
    }
}

動態的加入fragment就須要在代碼裏面通過FragmentManager等類進行操作:

frameLayout = (FrameLayout) findViewById(R.id.article_frame_layout);
fragmentManager = getSupportFragmentManager();
articleFragment = ArticleFragment.newInstance();
fragmentManager.beginTransaction().replace(R.id.article_frame_layout, articleFragment).commit();

Fragment的事務管理

android裏通過FragmentManager類進行管理fragment。一組對fragment的操作(加入、刪除、替換等)稱為一個事務,通過FragmentTransaction類來提交運行。

也能夠把事務加入到回退棧中,進行回滾。這有點相似於數據庫的事務機制。

註意:

  • 事物操作最後必須調用commit()才幹運行。

  • 調用commit()方法之後,也不是立馬運行;假設須要立馬運行,能夠使用executePendingTransactions()方法。

  • 一次性add多個fragment,顯示的是最後一個。

  • 任務棧回退針對的是事務。而不是fragment。一次事務操作過程中能夠有非常多個對fragment的操作。

  • 僅僅能在activity處於可保存狀態的情況下。進行事務操作。

    否則引發例如以下異常:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

比方。在onPause()或者onStop()中提交事務,就會出現以上問題,假設非要在這些生命周期裏面進行事務提交。請使用FragmentTransaction類的commitAllowingStateLoss()方法,同意狀態丟失。

  • 假設activity繼承的是AppCompatActivity。onBackPressed()回調函數裏面是利用的V4包的getSupportFragmentManager()進行的棧回退,所以做fragment回退的時候須要註意引用的是不是V4包的Fragment類
/**
     * Take care of popping the fragment back stack or finishing the activity
     * as appropriate.
     */
    public void onBackPressed() {
        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            supportFinishAfterTransition();
        }
    }

Fragment之間的切換

Fragment的切換就是基本就是利用add()hide()show()replace()這四個方法。

情況一:採用add方式切換fragment

activity:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_fragments2);
        ButterKnife.bind(this);
        FragmentManager fragmentManager = getSupportFragmentManager();
        TabFragment1 tabFragment1 = TabFragment1.newInstance();
        fragmentManager.beginTransaction().add(R.id.test_fragments_layout, tabFragment1).addToBackStack("tab1").commit();
    }

TabFragment1代碼:

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d("TabFragment1", "onCreateView");
        View view = inflater.inflate(R.layout.fragment_tab_fragment1, container, false);
        ButterKnife.bind(this, view);
        openNextBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                TabFragment2 tabFragment2 = TabFragment2.newInstance();
                fragmentManager.beginTransaction().add(R.id.test_fragments_layout, tabFragment2).addToBackStack(null).commit();
            }
        });
        return view;
    }

TabFragment2代碼:

/**
 * Tab Fragment 2
 */
public class TabFragment2 extends Fragment {
    public TabFragment2() {
    }
    public static TabFragment2 newInstance() {
        TabFragment2 fragment = new TabFragment2();
        return fragment;
    }
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d("TabFragment2", "onAttach");
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("TabFragment2", "onCreate");
        if (getArguments() != null) {
        }
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d("TabFragment2", "onCreateView");
        return inflater.inflate(R.layout.fragment_tab_fragment2, container, false);
    }
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d("TabFragment2", "onDestroyView");
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("TabFragment2", "onDestroy");
    }
    @Override
    public void onDetach() {
        super.onDetach();
        Log.d("TabFragment2", "onDetach");
    }
}

此時都是採用add的方式進行顯示fragment。每次都會把加入fragment的事務疊加到回退棧上面。

在TabFragment1界面點擊button加入TabFragment2界面,然後在按返回鍵回退,打印出生命周期例如以下:

技術分享

情況二:採用replace的方式切換fragment

activity代碼不變。

TabFragment1的代碼例如以下:

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d("TabFragment1", "onCreateView");
        View view = inflater.inflate(R.layout.fragment_tab_fragment1, container, false);
        ButterKnife.bind(this, view);
        openNextBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                TabFragment2 tabFragment2 = TabFragment2.newInstance();
                fragmentManager.beginTransaction().replace(R.id.test_fragments_layout, tabFragment2).addToBackStack(null).commit();
            }
        });
        return view;
    }

TabFragment1切換到TabFragment2的時候,使用的replace方法。replace方法的作用是remove掉全部加入到同樣id的容器裏的fragment。然後加入參數裏的fragment。所以會回調TabFragment1的onDestoryView()方法,等到從TabFragment2返回的時候,去運行replace事務的相反操作,也就會又一次創建TabFragment1的視圖,回調onCreateView()方法。

技術分享

技術分享

remove:

我們在TabFragment2界面中調用remove方法,把TabFragment1移除掉。

TabFragment1.java:

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d("TabFragment1", "onCreateView");
        View view = inflater.inflate(R.layout.fragment_tab_fragment1, container, false);
        ButterKnife.bind(this, view);
        openNextBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                TabFragment2 tabFragment2 = TabFragment2.newInstance();
                fragmentManager.beginTransaction().add(R.id.test_fragments_layout, tabFragment2).addToBackStack(null).commit();
            }
        });
        return view;
    }

TabFragment2.java:

private onBtnClickListener onBtnClickListener;
    public TabFragment2() {
    }
    public static TabFragment2 newInstance() {
        TabFragment2 fragment = new TabFragment2();
        return fragment;
    }
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d("TabFragment2", "onAttach");
        if (context instanceof onBtnClickListener){
            onBtnClickListener = (TabFragment2.onBtnClickListener) context;
        }else{
            throw new RuntimeException(context.toString()
                    + " must implement onBtnClickListener");
        }
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("TabFragment2", "onCreate");
        if (getArguments() != null) {
        }
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d("TabFragment2", "onCreateView");
        View view = inflater.inflate(R.layout.fragment_tab_fragment2, container, false);
        ButterKnife.bind(this, view);
        removeFragmentBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBtnClickListener.removeFragment1();
            }
        });
        return view;
    }
    public interface onBtnClickListener{
        void removeFragment1();
    }

activity:

 @Override
    public void removeFragment1() {
        fragmentManager.beginTransaction().remove(tabFragment1).commit();
    }

從代碼裏看到,remove操作的事務沒有加入到回退棧中。所以從TabFragment2中返回的時候,直接退到了activity界面。

技術分享

可是從界面來看,從TabFragment2會退到activity之後。再次按返回鍵並沒有退出activity,然後再按返回鍵的時候才會退出activity。原因就是。盡管remove了TabFragment1,可是僅僅是回調了onDestoryView()方法銷毀了視圖,此時TabFragment1的對象資源和與activity的關聯還沒有斷開。所以點擊返回鍵的時候會有一個沒有“響應”。

以下看看我們把reomve()操作加入到回退棧的情況:

@Override
    public void removeFragment1() {
        fragmentManager.beginTransaction().remove(tabFragment1).addToBackStack("remove").commit();
    }

技術分享

與上面的情況相比,多了TabFragment1的onCreateView()這一步。這是由於把remove事務加入到了任務棧,回退的時候逆向運行該操作。

使用replace()方法和remove()方法會導致視圖銷毀,所以。切換fragment的時候,假設須要視圖保留視圖。就不能用這兩個方法。


Fragment與Activity之間傳值

  • fragment –> activity 99%的做法都是通過接口回調來做的。定義一個接口。activity中實現此接口方法,在fragment裏面調用接口方法

  • activity –> fragment 通過findFragmentById()後者findFragmentByTag()方法獲取fragment實例調用fragment裏的public方法就可以。

接著往下看~~

TabFragment2調用接口方法:

 @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Log.d("TabFragment2", "onCreateView");
        View view = inflater.inflate(R.layout.fragment_tab_fragment2, container, false);
        ButterKnife.bind(this, view);
        removeFragmentBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBtnClickListener.removeFragment1();
            }
        });
        tellSthBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBtnClickListener.tellSth("這是我要對你說的話!

"); } }); return view; } /** * 回調接口 */ public interface onBtnClickListener { void removeFragment1(); void tellSth(String str); }

activity實現該接口的方法:

public class TestFragments extends AppCompatActivity implements TabFragment2.onBtnClickListener {
    @Bind(R.id.test_fragments_layout)
    FrameLayout testFragmentsLayout;
    private FragmentManager fragmentManager;
    private TabFragment1 tabFragment1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_fragments2);
        ButterKnife.bind(this);
        fragmentManager = getSupportFragmentManager();
        tabFragment1 = TabFragment1.newInstance();
        fragmentManager.beginTransaction().add(R.id.test_fragments_layout, tabFragment1, "tab1").addToBackStack("tab1").commit();
    }
    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }
    @Override
    public void removeFragment1() {
        fragmentManager.beginTransaction().remove(tabFragment1).addToBackStack("remove").commit();
    }
    @Override
    public void tellSth(String str) {
        ((TabFragment1)fragmentManager.findFragmentByTag("tab1")).showSth("hello: " + str);
    }
}

在TabFragment1中定義方法供activity調用:

    public void showSth(String str) {
        getSthEt.setText(str);
    }

技術分享


Fragment與Fragment傳值

activity給fragment傳值你回了,fragment給activity傳值你也會了。那麽這個問題就不要問我了!。!


Fragment切換動畫

Fragment的切換動畫能夠使用系統的標準動畫,也能夠自己定義動畫。
使用系統的須要用到setTransition(),可是僅僅能設置系統提供的有限的動畫效果。

  • FragmentTransaction.TRANSIT_FRAGMENT_OPEN

  • FragmentTransaction.TRANSIT_FRAGMENT_CLOSE

  • FragmentTransaction.TRANSIT_FRAGMENT_FADE

自己定義動畫須要用到的類:

/**
     * Set specific animation resources to run for the fragments that are
     * entering and exiting in this transaction. These animations will not be
     * played when popping the back stack.
     */
    public abstract FragmentTransaction setCustomAnimations(@AnimRes int enter,
            @AnimRes int exit);
/**
     * Set specific animation resources to run for the fragments that are
     * entering and exiting in this transaction. The <code>popEnter</code>
     * and <code>popExit</code> animations will be played for enter/exit
     * operations specifically when popping the back stack.
     */
    public abstract FragmentTransaction setCustomAnimations(@AnimRes int enter,
            @AnimRes int exit, @AnimRes int popEnter, @AnimRes int popExit);

註意:

setCustomAnimations()必須在add()、remove()、replace()調用之前設置。否則不起作用。

比方:有兩個fragment A和B,從A切換到B的時候

  • @AnimRes int enter
    表示Fragment B的進入動畫

  • @AnimRes int exit
    表示Fragment A的退出動畫

  • @AnimRes int popEnter
    表示當從B界面pop回到A時,Fragment A的進入動畫

  • @AnimRes int popExit
    表示當從B界面pop回到A是,Fragment B的退出動畫

假設使用add的方式顯示下一個fragment。則僅僅會觸發enter 和 popExit動畫。由於這樣的情況下A並沒有被移除,僅僅是觸發了與B相關的動畫。

比方:
從TabFragment1使用add()方式顯示TabFragment2:

FragmentManager fragmentManager = getFragmentManager();
TabFragment2 tabFragment2 = TabFragment2.newInstance();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.slide_in_from_right, R.anim.slide_out_to_left, R.anim.slide_in_from_top, R.anim.slide_out_to_bottom);
fragmentTransaction.add(R.id.test_fragments_layout, tabFragment2, "tab2");
fragmentTransaction.addToBackStack("tab2");
fragmentTransaction.commit();

4個動畫都是使用的簡單的view動畫,用屬性動畫能夠做出更加絢麗的動畫:

slide_in_from_right.xml

<?xml version="1.0" encoding="utf-8"?

> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="800" android:fromXDelta="100.0%" android:interpolator="@android:interpolator/accelerate_decelerate_interpolator" android:toXDelta="0.0" />

slide_out_to_left.xml

<?xml version="1.0" encoding="utf-8"?

> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="800" android:fromXDelta="0.0" android:interpolator="@android:interpolator/accelerate_decelerate_interpolator" android:toXDelta="-100%" />

slide_in_from_top.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromYDelta="-100.0%"
    android:interpolator="@android:interpolator/accelerate_decelerate"
    android:toYDelta="0.0" />

slide_out_to_bottom.xml

<?xml version="1.0" encoding="utf-8"?

> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:fromYDelta="0" android:interpolator="@android:interpolator/accelerate_decelerate" android:toYDelta="100%p" />

效果圖:

技術分享

假設採用的replace的方式,則會正常的觸發4個動畫。

FragmentManager fragmentManager = getFragmentManager();
TabFragment2 tabFragment2 = TabFragment2.newInstance();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.slide_in_from_right, R.anim.slide_out_to_left, R.anim.slide_in_from_top, R.anim.slide_out_to_bottom);
fragmentTransaction.replace(R.id.test_fragments_layout, tabFragment2, "tab2");
fragmentTransaction.addToBackStack("tab2");
fragmentTransaction.commit();

技術分享

這裏僅僅介紹了setCustomAnimations()的使用方法,setTransition()方式的動畫非常easy,就不介紹了。


本篇blog到此結束。
如有錯誤,歡迎留言。
謝謝~~~

Fragment的基本應用