• 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使用并不难,用好了,能构建很灵活的界面,做到很好的适配,提升用户体验。

  • 相关阅读:
    秋招结束了
    面向萌新的日麻面麻规则简化/改进
    DataTable 转换为Tree 树状结构
    jmeter beanshell断言
    jmeter请求参数包含中文
    Centos7 yum缓存rpm安装包
    nginx针对某个url限制ip访问,常用于后台访问限制
    有关json-lib2.4在转java对象时超过7位数的Double类型会出现科学计数法和精度丢失的问题
    SAP 下载安装
    android okhttp + retrofit使用
  • 原文地址:https://www.cnblogs.com/fanglongxiang/p/12149066.html
Copyright © 2020-2023  润新知