从零开始打卡:第五天
碎片
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布局