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>
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()的方法
效果如下图所示:
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