Android 知识点的小总结Android开发经验谈Android开发

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

2017-09-18  本文已影响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

上一篇 下一篇

猜你喜欢

热点阅读