第十四章 Android常见的Fragment的使用

作者: 忆念成风 | 来源:发表于2017-09-18 09:52 被阅读102次

    1. 引言

      现如今移动设备的发展非常的迅速,手机和平板都非常的普及了。这两者的差距除了屏幕的大小以外,其他的差距都非常的小。我们知道一般的手机的屏幕是4-6英寸,平板一般是7-10英寸。屏幕大小的差距会让同样的界面的视觉效果有较大的差异。有些控件会被过分的拉伸,元素之间的排列空隙变大,图片的效果失真。

    2.碎片的概念

      Android自从3.0开始引入Fragment(碎片),它可以让界面在平板上更好的显示。Fragment是一种可以镶嵌在Activity当中的UI片段。它可以让程序更加合理充分的利用大屏幕的空间。因此在平板上应用非常的普遍。在你对Actiivty有过了解之后,你再来了解Fragment的话,简单了很多,他们之间很多地方都非常的相像。你甚至可以说Fragment是Activity的翻版。因为我们知道,平板的屏幕比手机的屏幕要大。程序员在设计的时候就得兼顾手机和平板。假设我们在开发今日头条的新闻App,一个界面展示一组的新闻标题,另外一个界面展示新闻的内容。现在手机开发,可以是两个Activity,一个Activity展示标题,一个Activity展示内容。那么如果是平板呢?新闻的标题在手机界面上能展示出来,在平板是当然也可以,但是你能容忍还有一大片空白以及标题被拉伸么?这时候就得用上碎片了,使用两个碎片分别放置标题和内容,再将这两个碎片引入到同一个活动里。这样屏幕控件就被充分利用了么。如下图所示:

    碎片的使用

    3. 碎片的使用

    3.1 碎片的简单用法

    在一个活动中添加两个碎片,并让这两个碎片平分活动空间。left_fragment.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">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="left_Fragment"/>
    
    </LinearLayout>
    

    接着新建一个LeftFragment类继承Fragment。注意,这里可能会有两个不同的包下的Fragment可以选择。一个是系统内置的android.app.Fragment,一个是support-v4库下的android.support.v4.app.Fragment。这里强烈建议你用V4包下的Fragment,因为它可以在所有的Android系统版本中保持功能的一致性。而系统包下的Fragment在4.2之前的设备不能使用。
    在使用的时候,不需要在build.gradle文件中添加support-v4库的依赖,builder.gradle文件中添加了appcompat_v7库的依赖,包括了support_v4库。

    
    public class LeftFragment  extends Fragment {
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    
            View view=inflater.inflate(R.layout.left_fragment,container, false);
            return view;
        }
    }
    
    

    right_fragment.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"
        android:background="#ffff00">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="right_fragment"
            android:textSize="20sp"
            />
    
    </LinearLayout>
    

    right的写法与left都一样。然后修改activity_main.xml。之前讲过设置权重,然后就是通过使用<fragment>标签在布局中添加碎片。最后还有设置android:name 属性,来指明需要添加的碎片全包名。项目结果如下图所示:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.demo.fragmentdemo.MainActivity">
    
        <fragment
            android:id="@+id/left_fragment"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:name="com.demo.fragmentdemo.LeftFragment"/>
    
        <fragment
            android:id="@+id/right_fragment"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:name="com.demo.fragmentdemo.RightFragment"/>
    </LinearLayout>
    
    
    fragment

    3.2 碎片的高级用法

    1.上面只是讲了碎片在布局文件中的用法,碎片的真正强大之处在于他可以在程序中动态的添加到Activity当中。新建代码new_right_fragment:

    <?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"
        android:background="#00ff00">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="新的Fragment"
            android:textSize="20sp"
            />
    
    </LinearLayout>
    

    2.新建Fragment,NewRightFragment继承 Fragment

    
    public class NewRightFragment extends Fragment {
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View view=inflater.inflate(R.layout.new_right_fragment,container,false);
            return  view;
        }
    }
    

    3.修改activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.demo.fragmentdemo.MainActivity">
    
        <fragment
            android:id="@+id/left_fragment" android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:name="com.demo.fragmentdemo.LeftFragment"/>
    
        <FrameLayout
            android:id="@+id/new_right_fragment"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    
    

    之前的文章介绍了FrameLayout,所有的控件默认放在左上角。这里只有一个碎片,不需要任何定位,非常适合FrameLayout。
    4.修改MainActivity,这里我们设计通过点击左边的碎片的按钮,实现右边的碎片进行替换。最后设置点击Back键,返回之前的碎片。

    public class MainActivity extends AppCompatActivity  implements OnClickListener{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button button= (Button) findViewById(R.id.button);
            button.setOnClickListener(this);
            replaceFragment(new RightFragment());
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.button:
                    replaceFragment(new NewRightFragment());
                    break;
                default:
                    break;
            }
        }
    
    
        private   void replaceFragment(Fragment fragment){
            FragmentManager  fragmentManager=getSupportFragmentManager();
            FragmentTransaction  fragmentTransaction=fragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.right_layout,fragment);
            fragmentTransaction.addToBackStack(null);
            fragmentTransaction.commit();
        }
    }
    
    

    可以看到,首先我们给左侧的碎片中的按钮button注册了点击事件,然后调用 replaceFragment()这个方法来动态替换掉右边的fragment。换成NewRightFragment。结合代码可以看到:
    1.创建带添加的代码实例
    2.获取FragmentManager,可以在Activity中直接调用getSupportFragmentManager()方法得到
    3.开启一个事务。通过调用beginTransaction()的方法开启
    4.向容器内添加或替换碎片。一般使用replace()方法实现,需要传入容器的id,和待添加的碎片实例。
    5.在提交事务之前,调用FragmentTransaction 的addToBackStack()的方法,它可以接受一个名字用于描述返回栈的状态,一般传入null就可以了。点击Back键,你会发现程序没有退出,会回退到上一层RightFragment,再点击一次,RightFreagment界面会消失,再点一次,才会退出程序。

    6.提交事务,调用commit()的方法
    效果如下图所示:

    fragment的动态添加

    4.碎片和活动之间的通信

    我们知道碎片是镶嵌在活动Activity中显示的,但是他们之间的关系并不是特别的亲密。从上面的代码中,你可以看出Fragment和Actiivty都各自存在一个类中。哪么他们之间有没有明显的方式来直接通信呢?肯定是有的。

    4.1 活动中获取碎片的方法

    为了方便碎片和活动之间进行通信,FragmentManager提供了一个类似于findviewbyId的方法来从布局文件中获取碎片的实例,代码如下:

    RightFragment  right=(RightFragment)  getSupportFragmentManager().findFragmentById(R.id.right_fragment);
    

    通过FragmentManager的findFragmentById可以在活动中得到相应的碎片的实例。然后调用碎片里面的方法。

    4.2 碎片中获取活动的方法

    因为我们知道碎片是嵌入到活动中的,那么每个碎片想要获取对象和的Activity的方法就非常简单了,只需要调用getActivity()的方法来获取和当前碎片相关的实例就可以了。

    MainActivity activity =(MainActivity) getActivity();
    

    这样就获取到了活动的实例,有了活动的实例就好办了,你可以随便调用这个活动的方法。另外当碎片需要使用Context对象的时候,也可以使用getActivity()的方法。因为获取的活动本身就是一个Context对象。

    4.3 碎片与碎片之间的进行通信

    看到这里,我和你说碎片和碎片之间不能通信,你是不是一板砖直接拍过来的啊。好好说话不动手。咳咳,这个 碎片都是在活动中的,每个碎片都是独立的fragment,那么如果fragment之间想要通信怎么办?当然是找爸爸啊,啊不对,是找Activity的。一个活动首先得得到它相关联的活动,然后通过它的活动再去过去另一个碎片的实例。这样就可以实现碎片之间的通信功能了。

    5.Fragment的生命周期

    看到生命周期是不是很熟悉啊,都说了 Fragment是嵌入到Activity中的,Activity有生命周期,Fragment也肯定会有的啊。来,看图说话:

    生命周期的对比

    联想一下Activity的生命周期,它在生命周期内一会共有 运行状态,暂停状态,停止状态和销毁四种状态。所以fragment也有这四种状态,在一些小的地方会有所差别。

    1.运行状态
    当一个碎片可见的时候,并且所关联的Activity正处在运行状态中,该碎片也处于运行状态。
    2.暂停状态
    当Activity进入暂停的状态时,与它相关的可见的碎片就会进入暂停状态
    3.停止状态
    当一个活动进入停止状态,与它相关联的碎片就会进入到停止状态,或者通过调用Fragment中的FragmentTransaction()的remove(),replace的方法将碎片从活动中移除,但是如果在事务提交之前调用addToBackStack()方法的话,碎片也会进入到停止状态。总的来说,进入停止的碎片对用户来说是不可见的,有可能会被回收掉。
    4.销毁状态
    碎片依附于活动,活动被销毁了,碎片也要被销毁。Fragment中的FragmentTransaction在提交事务之前调用remove(),replace()的方法将碎片从活动中移除。但是事务提交之前没有调用addToBackStack()的方法的话,碎片也会进入销毁状态。
    碎片的几个附加的回调方法:
    1.onAttach() 当碎片和活动简历关联的时候,调用
    2.onCreateView() 碎片加载布局的时候调用
    3.onActivityCreated() Activity已经创建的时候调用
    4.onDestoryView() 与碎片相关联的activity被移除的时候调用
    5.onDetch() 当碎片和活动解除关联的时候调用

    将RightFragment的生命周期方法加上

    
    public class RightFragment extends Fragment {
        private static final String TAG = "RightFragment";
    
        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
            Log.d(TAG, "onAttach: ");
        }
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.d(TAG, "onCreate: ");
        }
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            Log.d(TAG, "onCreateView: ");
            View view=inflater.inflate(R.layout.right_fragment,container,false);
            return  view;
        }
    
        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            Log.d(TAG, "onActivityCreated: ");
        }
    
        @Override
        public void onStart() {
            super.onStart();
            Log.d(TAG, "onStart: ");
        }
    
        @Override
        public void onResume() {
            super.onResume();
            Log.d(TAG, "onResume: ");
        }
    
        @Override
        public void onPause() {
            super.onPause();
            Log.d(TAG, "onPause: ");
        }
    
    
        @Override
        public void onStop() {
            super.onStop();
            Log.d(TAG, "onStop: ");
        }
    
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            Log.d(TAG, "onDestroyView: ");
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.d(TAG, "onDestroy: ");
        }
    
        @Override
        public void onDetach() {
            super.onDetach();
            Log.d(TAG, "onDetach: ");
        }
    }
    

    然后继续代码,点击fragment左边的button,替换右边的fragment,然后点击Back键回退。

    打印日志

    1.通过日志发现,在运行项目代码的时候,第一次启动项目,会调用onAttach() , onCreate() , onCreateView(),onActivityCreated(),onStart(),onResume()方法,然后点击左边的fragment的按钮,日志改变,调用了onPause() , onStop(),onDestoryView(),这时候RightFragment已经被停止了。因为调用了addToBackStack()方法,如果没有调用会被销毁。onDestory()和onDetach()方法会得到执行。
    2.接着,点击Back键,RightFragment重新回到了屏幕,这时候重新调用了onCreateView(),onActivityCreated(),onStart(),onResume()方法。因为借助addToBackStack(),RightFragment方法没有被销毁,所以不会执行onCreate()方法。
    3.最后,再次点击Back键依次执行 onPause() , onStop() , onDestoryView() , onDestory() , onDetach() 方法。这样完整体验了一遍Fragment的生命周期了。

    6.Fragment动态加载布局的小技巧

      因为我们知道,同样的布局,手机放一个,平板可以放两个。假如程序能够根据设备的分辨率或者屏幕大小来判定加载哪个布局,那样我们发挥的控件就更多了,这里说点小技巧

    1.通过限定符判断 Android: layout_width="Match_parent"

    很多平板都采用双页模式(左侧显示列表,右侧显示内容),因为屏幕大,能放得下,但是手机屏幕小,放不下,只能显示一页的内容,这时候咋办?举个栗子:
    1.新建activity_main3.xml的文件

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <fragment
            android:id="@+id/left_fragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="com.demo.fragmentdemo.LeftFragment"/>
    
    
    </LinearLayout>
    
    

    2.然后在res目录下创建一个layout_larger包,再新建一个activity_main2.xml的文件。如果创建了包,但是文件无法创建的,是因为没有对包做引用。可以参考我的这篇文章:Android Studio 创建一个layout_large文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <fragment
            android:id="@+id/left_fragment"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:name="com.demo.fragmentdemo.LeftFragment"/>
        <fragment
            android:id="@+id/right_fragment"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:name="com.demo.fragmentdemo.RightFragment"/>
    
    </LinearLayout>
    

    这时候我们可以看到layout/activity_main2 布局只包含了一个碎片,而layout_larger/activity_main2布局包括了两个布局。其中large就是一个限定符,哪些屏幕被认为是large的设备会自动加载layout_large文件夹下的布局。小屏幕则会加载layout下的布局。效果如下图所示:

    手机显示效果图 平板显示效果图

    2. 使用最小宽度限定符

    我们使用了Large解决单双页的问题,但是我们不知道到底屏幕多大才符合large呢,这时候可以使用最小宽度限定符。同样的方法创建layout_sw600dp文件夹,创建activity_main2.xml文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <fragment
            android:id="@+id/left_fragment"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"
            android:name="com.demo.fragmentdemo.LeftFragment"/>
        <fragment
            android:id="@+id/right_fragment"
            android:layout_width="0dp"
            android:layout_weight="3"
            android:layout_height="match_parent"
            android:name="com.demo.fragmentdemo.RightFragment"/>
    </LinearLayout>
    
    

    这就意味着,当程序运行在屏幕宽度大于600dp的时候,回家在layout_sw600dp/activity_main2这个文件,屏幕小于600的时候仍然会加载layout/activity_main2.xml文件。
    好了,关于fragment的一些用法就差不多了。

    最后奉上github地址:https://github.com/wangxin3119/fragment1

    相关文章

      网友评论

      本文标题:第十四章 Android常见的Fragment的使用

      本文链接:https://www.haomeiwen.com/subject/obcssxtx.html