Fragment簡介及使用


概述

Fragment是 Android 3.0(API 11)引入的一種設計,用於大屏幕的設備。

  • Fragment依托於Activity,受宿主Activity生命周期的影響。但它也有自己的生命周期。
  • Fragment可重復使用,一個Activity可以有多個Fragment。一個Fragment可以被多個Acitivy使用。
  • Fragment在Acitivity運行時可以動態的加載或刪除。在不同分辨率設備或者橫豎屏時 調用對應的Fragment布局就能很好的實現設備的適配,提升用戶體驗。

注:

  • AndroidX出來后,使用的Fragment庫就在androidx中,下面的例子都是androidx的。
  • Fragment添加到Activity,一種通過<fragment>元素插入到布局中,另一種通過代碼插入到布局中的<FrameLayout>。下面的例子就包含這兩種。
  • savedInstanceState這個參數在很多時候是很有用的,在例子中的AnimeDetailFragment中簡單的演示了它的使用。
  • 注意不同的設備適配合適的布局,能夠很好的提升用戶體驗。

 

生命周期

如圖,比較詳細,稍微了解點或者熟悉Activity的都能直接看懂,下面例子中也通過log大致顯示了這一過程。

 

 

 

基本使用

先看下例子的效果,這個例子只有一個Activity 和 兩個Fragment組成:

 

上述效果,Activity布局中 是兩個fragment,左側的標題欄和右側的詳情界面。左側的標題使用的<fragment>元素直接插入的,右側的詳情界面 通過點擊動態加載的。

Activity布局文件activity_main.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment android:name="com.flx.testfragment.AnimeTitleFragment"
        android:id="@+id/anime_title_fragment"
        android:layout_width="120dp"
        android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/anime_detail_fragment_layout"
        android:layout_toRightOf="@id/anime_title_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#00BCD4" />
</RelativeLayout>

 

Activity很簡單

這里繼承了androidx.fragment.app.FragmentActivity。

package com.flx.testfragment;

import android.os.Bundle;
import android.util.Log;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;

public class MainActivity extends FragmentActivity implements AnimeTitleFragment.OnAmimeSelectedListener {

    private static final String TAG = "flx_fragment";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );
    }

    @Override
    public void onAnimeSelected(int position) {
        Log.d( TAG, "onArticleSelected: position=" + position );
        //創建一個詳情界面(detailFragment),並出入參數position
        AnimeDetailFragment detailFragment = new AnimeDetailFragment();
        Bundle args = new Bundle();
        args.putInt(AnimeDetailFragment.ARG_POSITION, position);
        detailFragment.setArguments(args);
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

        //用detailFragment替換anime_detail_fragment_layout中的內容
        transaction.replace(R.id.anime_detail_fragment_layout, detailFragment);
        //將事務添加到返回棧中,允許用戶通過按返回按鈕返回上個fragment狀態。該返回棧由Activity管理。
        transaction.addToBackStack(null);

        //提交事務
        transaction.commit();
    }
}

 

AnimeTitleFragment.java:

在布局文件中通過<fragment>元素插入的,在Activity創建時即生成,即效果圖中的左側標題欄。這里通過繼承ListFragment實現,數據和布局使用ArrayAdapter實現關聯(Adapter不太了解的,可以參考我對應的其他文章),同時這里的layout考慮了一點兼容性。具體代碼如下:

package com.flx.testfragment;

import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;

public class AnimeTitleFragment extends ListFragment {
    AnimeTitleFragment.OnAmimeSelectedListener mCallback;

    //Activity實現這個接口
    public interface OnAmimeSelectedListener {
        void onAnimeSelected(int position);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // We need to use a different list item layout for devices older than Honeycomb
        int layout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                android.R.layout.simple_list_item_activated_1 : android.R.layout.simple_list_item_1;

        String[] anime_names = this.getResources().getStringArray( R.array.anime_name );
        //使用ArrayAdapter顯示所有標題數據
        setListAdapter(new ArrayAdapter<String>(getActivity(), layout, anime_names));
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // 確保Activity實現該接口
        try {
            mCallback = (AnimeTitleFragment.OnAmimeSelectedListener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString()
                    + " must implement OnAmimeSelectedListener");
        }
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        //傳入position數據,即選中了那個
        mCallback.onAnimeSelected(position);

        getListView().setItemChecked(position, true);
    }
}

 

AnimeDetailFragment.java:

效果圖中的詳情界面,根據點擊不同的標題顯示不同的內容。這里簡單使用了savedInstanceState,合理的使用對於某些場景是很有幫助的。具體代碼:

package com.flx.testfragment;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class AnimeDetailFragment extends Fragment {
    private static final String TAG = "flx_AnimeDetailFragment";

    public static final String ARG_POSITION = "position";
    private int mCurrentPosition = -1;

    public static String[] ANIME_NAMES;
    public static String[] ANIME_AUTHORS;
    public static int[] COVER_IMGS = {R.drawable.hzw1, R.drawable.jjdjr1, R.drawable.hyrz1,
            R.drawable.zchzt1, R.drawable.qsmy1, R.drawable.xyj1, R.drawable.hlw1};
    private TextView mAnimeName;
    private TextView mAnimeAuthor;
    private ImageView mCoverImg;

    @Override
    public void onAttach(Context context) {
        Log.d( TAG, "onAttach: " + this );
        super.onAttach( context );
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        Log.d( TAG, "onCreate: " + this );
        super.onCreate( savedInstanceState );
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.d( TAG, "onCreateView: " + this );
        ANIME_NAMES = getActivity().getResources().getStringArray( R.array.anime_name );
        ANIME_AUTHORS = getActivity().getResources().getStringArray( R.array.anime_author );

        //savedInstanceState,這個參數很有用,在activity被重新創建時(如旋轉屏幕)等,恢復之前的狀態。
        if (savedInstanceState != null) {
            mCurrentPosition = savedInstanceState.getInt(ARG_POSITION);
        }

        View view = inflater.inflate( R.layout.anime_detail_view, container, false );
        mAnimeName = view.findViewById( R.id.anime_name_txt );
        mAnimeAuthor = view.findViewById( R.id.anime_author_txt );
        mCoverImg = view.findViewById( R.id.anime_cover_img );
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        Log.d( TAG, "onActivityCreated: " + this);
        super.onActivityCreated( savedInstanceState );
    }

    @Override
    public void onStart() {
        Log.d( TAG, "onStart: " + this );
        super.onStart();

        Bundle args = getArguments();
        if (args != null) {
            //根據傳入的參數ARG_POSITION 更新
            updateAnimeDetailView(args.getInt(ARG_POSITION));
        } else if (mCurrentPosition != -1) {
            // Set article based on saved instance state defined during onCreateView
            //根據保存的狀態中信息 更新
            updateAnimeDetailView(mCurrentPosition);
        }
    }

    @Override
    public void onResume() {
        Log.d( TAG, "onResume: " + this );
        super.onResume();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        Log.d( TAG, "onSaveInstanceState: " + this );
        super.onSaveInstanceState(outState);

        //保存fragment當前狀態的信息。
        outState.putInt(ARG_POSITION, mCurrentPosition);
    }

    public void updateAnimeDetailView(int position) {
        mAnimeName.setText(ANIME_NAMES[position]);
        mAnimeAuthor.setText(ANIME_AUTHORS[position]);
        mCoverImg.setImageResource(COVER_IMGS[position]);
        mCurrentPosition = position;
    }

    @Override
    public void onPause() {
        Log.d( TAG, "onPause: " + this );
        super.onPause();
    }

    @Override
    public void onStop() {
        Log.d( TAG, "onStop: " + this );
        super.onStop();
    }

    @Override
    public void onDestroyView() {
        Log.d( TAG, "onDestroyView: " + this );
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        Log.d( TAG, "onDestroy: " + this );
        super.onDestroy();
    }

    @Override
    public void onDetach() {
        Log.d( TAG, "onDetach: " + this );
        super.onDetach();
    }
}

 

一些資源文件:

AnimeDetailFragment的布局文件:anime_detail_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView android:id="@+id/anime_cover_img"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:layout_margin="10dp"
        android:scaleType="fitXY" />

    <TextView android:id="@+id/anime_name_txt"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:gravity="center"
        android:textStyle="bold"
        android:textSize="20sp"/>

    <TextView android:id="@+id/anime_author_txt"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:gravity="center"
        android:textStyle="italic"
        android:textSize="15sp"/>
</LinearLayout>

 

strings.xml:

<array name="anime_name">
        <item>海賊王</item>
        <item>進擊的巨人</item>
        <item>火影忍者</item>
        <item>斬赤紅之瞳</item>
        <item>秦時明月</item>
        <item>西游記</item>
        <item>葫蘆娃</item>
    </array>

    <array name="anime_author">
        <item>尾田榮一郎</item>
        <item>諫山創</item>
        <item>岸本齊史</item>
        <item>タカヒロ</item>
        <item>玄機科技</item>
        <item>央視</item>
        <item>上海美術電影</item>
    </array>

 

 生命周期流程

最后,看下log,看看fragment的生命周期

操作:點擊第一個標題(海賊王),然后點擊第二個標題(進擊的巨人),最后點擊返回鍵,下面log的過程 與 生命周期那個圖 是一致的,不太明白的,可以仔細看下如下log。

//點擊第一個標題,創建了fragment1(52888e9)
2020-03-03 16:22:48.316 20442-20442/com.flx.testfragment D/flx_fragment: onArticleSelected: position=0
2020-03-03 16:22:48.344 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onAttach: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:48.344 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreate: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:48.347 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreateView: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:48.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onActivityCreated: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:48.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStart: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:48.357 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onResume: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
//點擊第二個標題,創建了fragment2(371a2d2), fragment1(52888e9)進入生命周期到onDestroyView
2020-03-03 16:22:51.346 20442-20442/com.flx.testfragment D/flx_fragment: onArticleSelected: position=1
2020-03-03 16:22:51.349 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onAttach: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:51.349 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreate: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:51.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onPause: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:51.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStop: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:51.350 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onDestroyView: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:51.365 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreateView: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:51.369 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onActivityCreated: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:51.369 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStart: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:51.391 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onResume: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
//按了返回鍵,移除了fragment2(371a2d2), fragment1(52888e9)重新恢復。fragment2(371a2d2)執行到onDetach 被銷毀。 
2020-03-03 16:22:53.144 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onPause: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:53.144 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStop: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:53.144 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onDestroyView: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:53.148 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onDestroy: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:53.148 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onDetach: AnimeDetailFragment{371a2d2 #2 id=0x7f090020}
2020-03-03 16:22:53.148 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onCreateView: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:53.150 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onActivityCreated: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:53.150 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onStart: AnimeDetailFragment{52888e9 #1 id=0x7f090020}
2020-03-03 16:22:53.151 20442-20442/com.flx.testfragment D/flx_AnimeDetailFragment: onResume: AnimeDetailFragment{52888e9 #1 id=0x7f090020}

 

Fragment使用並不難,用好了,能構建很靈活的界面,做到很好的適配,提升用戶體驗。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM