1. 程式人生 > >Fragment詳解之六——如何監聽fragment中的回退事件與怎樣儲存fragment狀態

Fragment詳解之六——如何監聽fragment中的回退事件與怎樣儲存fragment狀態

前言:計劃總是趕不上變化,無論結局怎樣,只要一直跟隨自己的內心,不放棄,總有一天,你會成為那個人,加油。

相關文章:

經過前幾篇,大家應該對Fragment認識的已經足夠多了,有關Fragment的基礎知識在前幾篇基本就講完了,這篇給大家講兩個可能會用到的知識點。這兩點理解起來可能有點難度,大家可要耐著點性子哈。

一、如何監聽Fragment中的回退事件

1、問題闡述

在Activity中監聽回退事件是件非常容易的事,因為直接重寫onBackPressed()函式就好了,但當大家想要監聽Fragment中的回退事件時,想當然的也想著重寫onBackPressed()方法,這時候你會發現:Fragment中根本就沒有onBackPressed()方法給你重寫。這可怎麼破!

想想,在前面的例子中,我們在Activity的一個fragment_container裡依次Add進fragment1,fragment2,fragment3,fragment4,在我們點選回退棧時,會將Transaction回退棧中的fragment操作一個個出棧!那,這些回退事件Fragment是從哪來的?

首先,回退事件總是發給Activity的!在發給Activity以後再由Activity自己處理。比如它將Fragment回退棧中的內容一個個出棧這種操作。
其次:大家要知道:Fragment只是Activity中的一個控制元件而已,雖然我們可能把他做成了像Activity一樣大小覆蓋整個頁面,看起來跟Activity樣子上沒什麼區別,但他還是個控制元件!系統怎麼會給一個控制元件分發回退事件呢?這當然是不可能的。

2、解決方案

既然清楚了Fragment只是一個控制元件,而回退事件也只能在Activity中攔截。那我們就可以想辦法了。
首先,我們可以在Fragment類中咱們自己寫一個onBackPressed()方法來處理回撥事件。
然後,可以利用回撥,將要處理回退事件的fragment例項,傳給Activity。
最後,在拿到fragment例項以後,就可以在Activity的onBackPress()方法中,呼叫這個fragment例項的onBackPressed()方法了。
這樣,我們就在fragment中攔截了回退事件了。

3、例項

下面,我們就通過一個例子來看下效果。
效果圖如下:


大家從下面的效果圖中也可以看到,當fragment3中點選返回按鈕時,捕捉了返回事件,並將fragment3上的TextView顯示為”ragment3捕捉到了回退事件哦!”,但我只捕捉一次,當第二次點選時,就退出執行預設操作:即Transaction出棧。

下面看下具體的實現過程:
有關MainActivity佈局及fragment的新增就不再講了,下面直接從回撥開始
1、在Fragment3中定義onBackPress()函式及處理:

public class Fragment3 extends Fragment {
    private boolean mHandledPress = false;
    TextView tv;
    
     …………
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        tv = (TextView)getView().findViewById(R.id.fragment3_tv);
    }

    public boolean onBackPressed(){
        if (!mHandledPress){
            tv.setText("Fragment3 \n 捕捉到了回退事件哦!");
            mHandledPress = true;
            return true;
        }
        return false;
    }
}
上面的程式碼,沒什麼難度,就是定義了一個onBackPressed()函式,其返回一個布林值;意思是,如果對返回事件進行了處理就返回TRUE,如果不做處理就返回FALSE,讓上層進行處理。
變數mHandledPress用來指定只處理一次,當處理一次以後這裡的onBackPressed()就返回FALSE了.
2、在Fragment3中定義回撥函式,將自己例項的引用傳出去
(1)、先定義一個介面用做回撥,以及對應的變數:
protected BackHandlerInterface backHandlerInterface;
public interface BackHandlerInterface {
    public void setSelectedFragment(Fragment3 backHandledFragment);
}
注意,在回撥中傳進去的是Fragment3的例項!因為我們要在主Activity處理onBackPress()時,呼叫我們在Fragment3中自己寫的onBackPressed()函式,所以我們要傳進去Fragment3的例項
(2)、然後是給backHandlerInterface變數賦值
跟上篇一樣,我們要強制Activity實現這個介面,所以我們使用強制轉換的方式來賦值。在上篇中,我們在onAttach()函式中進行的強制轉換,程式碼如下:
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try{
        backHandlerInterface = (BackHandlerInterface) getActivity();
    }catch (Exception e){
        throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
    }
}
其實在onAttach()回撥時就已經把Fragment與Activity繫結在了一起,所以只要生命流程在onAttach()之後的任意一個生命週期,我們都可以通過getActivity來獲取Activity的例項,來進行強制轉換,所以在這裡我們就換個地方,在onCreate()函式中來做:
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (!(getActivity() instanceof BackHandlerInterface)) {
        throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
    } else {
        backHandlerInterface = (BackHandlerInterface) getActivity();
    }
}
這裡丟擲異常也沒有使用try...catch...來做,而是直接利用instanceof來判斷當前Activity是不是BackHandlerInterface的例項,即是否已經派生了BackHandlerInterface,如果沒有就直接拋異常,如果派生了就強制轉換賦值。
(3)、在適當的位置將自己的例項通過回撥傳過去。程式碼如下:
backHandlerInterface.setSelectedFragment(this);
有關這個設定Fragment3例項的程式碼,只要在生命週期中Fragment3例項已經產生了都可以設定,即可以放在生命週期在onCreate()後的函式裡,即onCreate()、onCreateView()、onActivityCreated()、onStart();雖然經過我測試,放在這幾個函式中的任意一個都是可行的,但onActivityCreated()後才是Activity最終onCreate()執行完,所以放在onActivityCreated()或onStart()中是最保險的。所以這裡放在了onStart()中來處理,程式碼如下:
public void onStart() {
    super.onStart();
    backHandlerInterface.setSelectedFragment(this);
}
所以完整的程式碼邏輯是這樣的:
public class Fragment3 extends Fragment {
	//定義回撥函式及變數
    protected BackHandlerInterface backHandlerInterface;
    public interface BackHandlerInterface {
        public void setSelectedFragment(Fragment3 backHandledFragment);
    }
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		//回撥函式賦值
        if(!(getActivity()  instanceof BackHandlerInterface)) {
            throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
        } else {
            backHandlerInterface = (BackHandlerInterface) getActivity();
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        //將自己的例項傳出去
        backHandlerInterface.setSelectedFragment(this);
    }
}
3、在MainActivity中,回退攔截,程式碼如下:
public class MainActivity extends FragmentActivity implements Fragment3.BackHandlerInterface {
    private Fragment3 selectedFragment;
	…………
    @Override
    public void setSelectedFragment(Fragment3 backHandledFragment) {
        this.selectedFragment = backHandledFragment;
    }

    @Override
    public void onBackPressed() {
        if(selectedFragment == null || !selectedFragment.onBackPressed()) {
            super.onBackPressed();
        }
    }

}
(1)、首先,將MainActivity實現Fragment3.BackHandlerInterface介面
在這裡實現setSelectedFragment()函式,程式碼如下:
public class MainActivity extends FragmentActivity implements Fragment3.BackHandlerInterface {
    private Fragment3 selectedFragment;
	…………
    @Override
    public void setSelectedFragment(Fragment3 backHandledFragment) {
        this.selectedFragment = backHandledFragment;
    }
}
(2)、然後在onBackPressed()回撥中進行回退攔截
public void onBackPressed() {
    if(selectedFragment == null || !selectedFragment.onBackPressed()) {
        super.onBackPressed();
    }
}
注意這裡的邏輯,在呼叫super.onBackPressed();的前提是selectedFragment.onBackPressed()返回FALSE,即Fragment3中的onBackPressed()返回FALSE,即不再攔截回退事件,才會執行預設的操作。
原始碼在文章底部給出

二、執行Replace操作後,怎樣儲存fragment狀態

首先,我們先闡述一個現象,大家先看下面這個DEMO:


這個過程是這樣的:
1、首先在Fragment1的EditText中先幾個字
2、然後如果呼叫addFragment()新增Fragment2,然後當從fragment2返回時,發現這幾個字還是有的。
3、但如果我們通過呼叫replace()新增Fragment2的話,會發現,當返回的時候,那幾個字沒了!

這說明了一個問題,呼叫addFragment新增的fragment的View會儲存到檢視樹(ViewTree)中,其中各個控制元件的狀態都會被儲存。但如果呼叫replace()來新增fragment,我們前面講到過,replace()的實現是將同一個container中的所有fragment檢視從ViewTree中全部清空!然後再新增指定的fragment。由於repalce操作會把以前的所有檢視全部清空,所以當使用Transaction回退時,也就只有重建每一個fragment檢視,所以就導致從replace操作回退回來,所有的控制元件都被重建,以前的使用者輸入全部沒了。

到這裡,大家首先要明白一個問題,repalce()操作,會清空同一個container中的所有fragment檢視!注意用詞:請空的是fragment的VIEW!fragment的例項並不會被銷燬!因為fragment的例項是通過FragmentManager來管理的。當fragment的VIEW被銷燬時,fragment例項並不會被銷燬。他們兩個不是同時的,即在fragment中定義的變數,所上次執行中被賦予的值是一直存在的。那fragment例項什麼時候會被銷燬呢,當然是在不會被用到的時候才會被銷燬。那什麼時候不會被用到呢,即不可能再回退到這個操作的時候,就會被銷燬。
在上面的例子中,fragment1雖然被fragment2的repalce操作把它的檢視給銷燬了,但在執行replace操作時,將操作加入到了回退棧,這時候,FragmentManager就知道,使用者還可能通過回退再次用到fragment1,所以就會保留fragment1的例項。相反,如果,在執行repalce操作時,沒有加入到回退棧,那FragmentManager就肯定也知道,使用者不可能再回到上次那個Fragment1介面了,所以它的fragment例項就會在清除fragment1檢視的同時也被清除了。

說了那麼多,現在如果我們想在利用repace操作的時候,同時儲存上一個fragment介面的狀態,那要怎麼辦?

方法一:控制元件狀態儲存與還原

上面我們講到,在清除Fragment檢視的時候,如果我們將操作同時加入到回退棧,那麼它的VIEW雖然從ViewTree中清除了,但它的例項會被儲存在FragmentManager中,那它的變數也會一直儲存著,直到下次回來。但檢視在回來的時候會重建。
那第一個方法來了,我們可以用一個變數來儲存EditText當前字串,在replace前將EditText中的值儲存在這個變數中,當返回來再次建立檢視時,再次給EditTxt賦值不就好了。
程式碼如下:

public class Fragment1 extends Fragment {
    private String mEditStr;
    private EditText editText;
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		View rootView = inflater.inflate(R.layout.fragment1, container, false);
        editText = (EditText)rootView.findViewById(R.id.fragment1_edittext);
        editText.setText(mEditStr);
        return rootView;
	}

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Button btnReplace = (Button)getView().findViewById(R.id.fragment1_repalce);
        btnReplace.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
	            mEditStr = editText.getText().toString();
                
                …………
            }
        });
	    …………
    }
}
上面的程式碼總是就是兩步:
第一步:在repalce前儲存狀態
mEditStr = editText.getText().toString();
第二步:在建立時還原狀態
editText.setText(mEditStr);
這雖然能完成工作,但如果我們的控制元件非常多呢?內容非常複雜呢?這將不是一個好辦法。因為很多變數的初始化及賦值將會使程式碼看的異常醜陋難懂。

方法二:只需要為控制元件新增ID值

在實時中還遇到一個解決方法,就是給EditText控制元件新增上id,只要給EditText控制元件新增上id,不需要上面的那些replace前的值的儲存即建立時的還原,它的內容就會被儲存。不知道其它控制元件是否也可以通過新增ID值的方式來儲存使用者的輸入值,即:

<EditText
    android:id="@+id/fragment1_edittext"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="top|left"
    android:background="#ffffff"
    android:hint="這裡是EditText,在這裡輸入文字哦"/>

方法三:儲存FragmentView檢視

方法一和方法二感覺都還是太靠譜的解決方法,既然fragment中的變數都會被儲存,那我們直接將Fragment的檢視直接儲存到變數中,在系統在利用onCreateView()建立檢視的時候,我們直接返回儲存的檢視不就得了。
基於上面的想法,程式碼上我們這樣做:
1、建立一個變數,儲存Fragment的檢視:
private View rootView;
2、然後來看onCreateView的實現
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    return getPersistentView(inflater, container, savedInstanceState, R.layout.fragment1);
}
可以看到,相比以前直接返回inflater.inflate(R.layout.fragment1, container,false);重建檢視,這裡返回的是一個getPersistentView()函式,下面看看這個函式的實現:
public View getPersistentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState, int layout) {
    if (rootView == null) {
        // Inflate the layout for this fragment
        rootView = inflater.inflate(layout, container,false);
    } else {
        ((ViewGroup) rootView.getParent()).removeView(rootView);
    }
    return rootView;
}
這段程式碼就是返回rootView的。即當rootView==null,即第一次建立時,就利用inflater.inflate()來建立初始化狀態的檢視,當下次再進到這個介面時,比如下面的通過回退操作進入到fragment1時,這時候的rootView就不再是空了。但在onCreateView()中返回的檢視是要新增到ViewTree中去的。而這裡的rootView檢視在上次已經新增到裡面去了,一個檢視例項不能被add兩次,不然就會被下面這個錯誤!所以,我們針對這種情況,如果rootView已經存在於ViewTree中的時候,要先從ViewTree中移除。

好了,到這裡就講完了,原始碼都會在下面給出。下面先來看看最終的效果圖吧:


本文參考文章:

如果本文有幫到你,記得加關注哦

本系列參考文章:

相關推薦

Fragment——如何fragment退事件怎樣儲存fragment狀態

前言:計劃總是趕不上變化,無論結局怎樣,只要一直跟隨自己的內心,不放棄,總有一天,你會成為那個人,加油。相關文章:經過前幾篇,大家應該對Fragment認識的已經足夠多了,有關Fragment的基礎知識在前幾篇基本就講完了,這篇給大家講兩個可能會用到的知識點。這兩點理解起來可

JS 瀏覽器的退 事件 [二〇一八年九月二十五日]

從網上找了點資源都比較少的 . 我找到了兩種方法: <script type="text/javascript"> //加入以下倆行程式碼,才能觸發 window.history.pushState('forward', null, '#'); wind

Android進階——Fragment操作原理(三)

引言 前一篇文章總結了Fragment 的基本概念和基本的用法,相信大家也能夠掌握一些知識了,但是對於一些操作可能還是不知其所以然,說實話曾經很長一段時間為也是暈乎乎的,後來才慢慢重視去學習瞭解,才略知一二,遂分享之。 一、管理Fragement所涉及到

Fragment三——管理Fragment(1)

前言:follow your heart,be your own king相關文章:前面給大家稍微看了要怎麼使用fragment,在上篇中,我們也初步接觸到了add,replace這些fragment操作的函式,下面就再詳細講講如何管理Fragment頁面吧。一、概述 1、F

Android入門——Fragment基本概念用法(一)

引言 Android在3.0中引入了Fragments的概念,其目的是用在大螢幕裝置上–例如平板電腦上,支援更加動態和靈活的UI設計。平板電腦的螢幕要比手機的大得多,有更多的空間來放更多的UI元件,並且這些元件之間會產生更多的互動。Fragment允許這樣的一

機器學習的概率模型和概率密度估計方法及VAE生成式模型(第3章 VI/VB算法)

dac term http 51cto -s mage 18C watermark BE ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

第九課--09_03_磁盤及文件系統管理.avi

window over 管理 cache 重復 允許 虛擬內存 空間 -- 一。swap分區--交換分區--高性能磁盤,機械磁盤就放在靠外的磁道page outpage infree--查看物理內存和交換空間的情況-m 按 M 查看buffer 緩沖---緩沖區,速度慢的

Spark運算元執行流程

coalesce顧名思義為合併,就是把多個分割槽的RDD合併成少量分割槽的RDD,這樣可以減少任務排程的時間,但是請記住:合併之後不能保證結果RDD中的每個分割槽的記錄數量是均衡的,因為合併的時候並沒有考慮合併前每個分割槽的記錄數,合併只會減少RDD的分割槽個數,因此並不能利用它來解決資料傾斜的問題。 d

tensorflow-GPU的配置(細數配置過程遇見的坑解決方式)

細數自己搭建TensorFlow-gpu環境中遇到的坑及解決方式:1、安裝anaconda,一路下來點選next,然後預設配置(這裡不再細講)2、安裝裝TensorFlow-gpu -->pip install tensorflow-gpu(也可以選擇自己想要的版本安裝

fragment的setUserVisibleHint方法不懂使用容易出大問題

一開始不知道這個方法,用到tablayout+ viewpager,viewpager裡面包含的是fragment,有這樣的需求就是,每次滑動需要重新整理當前頁面,百度一看,有兩個方法可以實現,1.setUserVisibleHint 2.onHiddenChanged 如果使用的是上面這張

Android UIFragment例項

http://blog.csdn.net/eclipsexys/article/details/8708024 上一篇我們講解了Fragment的載入方式,這次我們以一個例項來講解: 佈局: <LinearLayoutxmlns:android="htt

WebView使用WebChromeClient的常用事件

一、WebChromeClient1、概述(1)、 與WebViewClient的區別很多同學一看到這裡有Chrome,立馬就會想到google 的Chrome瀏覽器;這裡並不是指Chrome瀏覽器的意思,而是泛指瀏覽器,WebView的內部實現並不是完全使用Chrome的核

androidAnimation方法AnimationListener(一)

先寫一個類繼承AnimationListener,看看具體方法: 具體方法大家也已經從圖片中也有些瞭解了,那接下來就看看實戰中,又怎麼使用呢: 1. 先看看佈局檔案和效果圖:     

安卓專案實戰強大的網路請求框架okGo使用):擴充套件專案okServer,更強大的下載上傳功能,支援斷點和多工管理

OkGo與OkDownload的區別就是,OkGo只是簡單的做一個下載功能,不具備斷點下載,暫停等操作,但是這在很多時候已經能滿足需要了。 而有些app需要有一個下載列表的功能,就像迅雷下載一樣,每個下載任務可以暫停,可以繼續,可以重新下載,可以有下載優先順序,這時候OkDownload就有

docker容器技術Dockerfile

上一篇文章的連線:docker容器技術之儲存卷(五) 目錄 一、前言 二、Dockerfile 2.1製作映象有兩種: 2.2 什麼是Dockerfile? 2.3 Dockerfile的語法格式 dockerfile做映象時的工作邏輯: .dockering

二、Fragment

Fragment為什麼稱為第五大元件 有自己的生命週期 可以靈活的新增到activity中 使用頻率比較高 依附於activtiy 1.fagment載入到activity的兩種方式 新增frament到activity的佈局檔案中 動態在activity中新增fra

各種音視訊編解碼學習 編解碼學習筆記():H.26x系列

    最近在研究音視訊編解碼這一塊兒,看到@bitbit大神寫的【各種音視訊編解碼學習詳解】這篇文章,非常感謝,佩服的五體投地。奈何大神這邊文章太長,在這裡我把它分解成很多小的篇幅,方便閱讀。大神部落格傳送門:https://www.cnblogs.com/skyofbitbi

OpenLayers官方示例偽造點(Synthetic Points)

目錄 一、示例簡介 二、程式碼詳解 一、示例簡介     本示例首先隨機生成20000個點資料,並加入到地圖中,然後實現了將離滑鼠最近的點高亮顯示的功能。 二、程式碼詳解 <!DOCTYPE html> <html lang="

Android Fragment 2016 乾貨

最近看了很多動畫和一些效果很好的自定義控制元件,發現了一件事,就是Android的View層設計思想和古老的JavaSwing是如此的相似。這是在原來的基礎上加入了一些輸入移動端的生命週期,使其在使用和效能上更好。但是對核心的理解還是可以借鑑一些的。 如果說A

Android Fragment

Fragment產生原因: 同時適配手機和平板、UI和邏輯的共享。 介紹: Fragment也會被加入回退棧中。Fragment擁有自己的生命週期和接受、處理使用者的事件。可以動態的新增、替換和移除某個Fragment。必須依存於Activity生命週期 Fragmen