从零开始打卡:第五天

碎片

Fragment是一种可以嵌入在活动中的UI片段,它能让程序更合理充分的利用大屏幕的空间,它能包含布局,有自己的生命周期。

碎片的使用方式:

在一个活动中添加两个碎片,并让这两个碎片平分活动空间。

新建一个左侧碎片布局left_fragment.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Button"
        android:layout_height="wrap_content" />

</LinearLayout>

右侧布局right_fragment.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:background="@color/colorAccent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="This is right fragment"/>

</LinearLayout>

接着新建类继承Fragment(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;
    }
}

同样,右边布局

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

        return view;
    }
}

接着是activity_main.xml中的代码:

<fragment
    android:id="@+id/left_fragment"
    android:name="com.example.myapplication12.LeftFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1" />

<fragment
    android:id="@+id/right_fragment"
    android:name="com.example.myapplication12.RighrFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1" />

运行效果:

从零开始打卡:第五天

动态加载碎片

碎片的真正强大之处在于它可以在程序运行时动态的加载到活动中。

在上述代码基础上完善,新建another_right_fragment.xml,代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:background="#ffff00"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="This is another right fragment"/>

</LinearLayout>
这个布局的代码和right_fragment.xml中的代码基本相同,只是将背景色改成了黄色,并改变了文字的显示。

然后新建AnotherRightFragment作为另一个右侧碎片,代码如下:

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

代码同样非常简单,在onCreateView()方法中加载了刚刚创建的another_right_fragment布局。

修改activity_main.xml代码:

<fragment
    android:id="@+id/left_fragment"
    android:name="com.example.myapplication12.LeftFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1" />

<FrameLayout
    android:id="@+id/right_layout"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1">

</FrameLayout>

可以看到,现在将右侧碎片替换成了一个FrameLayout中,由于这里仅需在布局里放入一个碎片,不需要任何定位,因此非常适合FrameLayout。接下来我们会在FrameLayout里添加内容,从而实现动态添加碎片的功能,修改MainActivity中的代码。

public class MainActivity extends AppCompatActivity implements View.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 RighrFragment());
    }
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button:
                replaceFragment(new AnotherRightFragment());
                break;
                default:
                    break;
        }

    }

    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager=getSupportFragmentManager();
        FragmentTransaction transaction=fragmentManager.beginTransaction();
        transaction.replace(R.id.right_layout,fragment);
        transaction.commit();
    }

}

首先我们给左侧碎片中的按钮注册了一个点击事件,然后调用replaceFragment()方法动态的添加了RightFragment这个碎片,当点击左侧碎片中的按钮时,又会调用replaceFragment()方法将右侧碎片替换成AnotherRightFragment.结合replaceFragment()方法可以看出,动态添加碎片主要分5步。

1.创建待添加的碎片实例

2.获取FragmentManager,在活动中可以直接通过调用getSupportFragmentManager()方法得到。

3.开启一个事务,通过beginTransaction()方法开启。

4.向容器内添加或替换碎片,一般使用replace()方法实现,需要传入的容器的id和待添加的碎片实例。

5.提交事务,调用commit()方法完成。

运行结果如下:

从零开始打卡:第五天

点击按钮:

从零开始打卡:第五天

在碎片中模拟返回栈

我们已经实现了向活动中动态添加碎片的功能,但我们按下BACK键后程序会直接退出,接下来我们模仿类似于返回栈的效果,修改MainActivity中的代码:

从零开始打卡:第五天

这里我们在事务提交之前调用了Fragment的addToBackStack()方法,它可以接收一个名字用于描述返回栈的状态,一般传入null即可,现在运行程序,按下BACK键,会发现程序没有退出,而是回到RightFragment界面,继续按下BACK键,RightFragment界面也会消失,再次按下,程序才会退出。

碎片和活动之间进行通信

从零开始打卡:第五天

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

RightFragment rightFragment=

(RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);

调用FragmentManager的findFragmentById()方法,可以在活动中得到相应碎片的实例,然后轻松的调用碎片里的方法了。

在每个碎片中都可以通过调用getActivity()方法来得到和当前碎片相关联的活动实例。代码如下:

MainActivity activity=(MainActivity) getActivity();

有了活动实例之后,在碎片中调用活动里的方法就变得简单了。当碎片中需要使用Context对象时,也可以使用getActivity()方法,因为获取到的活动本身就是一个Context对象。

在一个碎片中可以得到与它相关联的活动,然后通过这个活动去获取另外一个碎片的实例,这样就实现了不同碎片之间的通信功能。

碎片的生命周期

从零开始打卡:第五天

onAttach()                                               当碎片和活动建立关联时调用。

onCreateView()                                        为碎片创建视图(加载布局)时调用

onActivityCreated()                                   确保与碎片相关联的活动一定已经创建完毕的时候调用,

onDestroyView()                                       当与碎片关联的活动一定已经创建完毕的时候调用

onDetach()                                               当碎片和活动接触关联的时候调用

从零开始打卡:第五天

体验碎片的生命周期

在RightFragment中的回调方法里加入打印日志,如:

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

打印如下:

从零开始打卡:第五天

点击按钮:

从零开始打卡:第五天

动态加载布局的技巧

使用限定符:判断使用单页模式还是双页模式

修改activity_main.xml文件,

<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:name="com.example.myapplication12.LeftFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
</LinearLayout>
将多余的代码删掉,只留下一个左侧碎片,并充满整个布局,在res目录下新建layout-large文件夹,在文件

夹下新建一个叫activity_main.xml,代码如下:

<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:name="com.example.myapplication12.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <fragment
        android:id="@+id/right_fragment"
        android:name="com.example.myapplication12.RighrFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3" />

</LinearLayout>
layout/activity_main布局只包含一个碎片,即单页模式,而layout-large/activity_main布局下包含

两个碎片,即双页模式,其中large就是一个限定符,那些屏幕被认为是large的设备就会自动加载layout-large文件夹

下的布局,而小屏幕设备就会加载layout文件夹下的布局。

将MainActivity中replaceFragment()方法里的代码注释掉,并运行。这样我们就实现了程序运行时动态加载布局的功能。

从零开始打卡:第五天

从零开始打卡:第五天

使用最小宽度限定符

最小宽度限定符允许我们队屏幕的宽度指定一个最小值(dp)然后以这个最小值为临界点,屏幕宽度大于这个值得设备就

加载一个布局。宽度小于这个值得设备就加载另一个布局。

在res目录下新建layout-sw600dp文件夹,然后在这个文件夹下新建activity_main.xml布局,代码如下;

<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:name="com.example.myapplication12.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <fragment
        android:id="@+id/right_fragment"
        android:name="com.example.myapplication12.RighrFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3" />


</LinearLayout>
当程序运行在屏幕宽度大于600dp的设备上时,就会加载layout-sw600dp/activity_main布局,当程序运行在

屏幕宽度小于600dp的设备上时,则任然加载默认的layout/activity_main布局