您的位置:首页 > 移动开发 > Android开发

fragment的使用以及fragment大家族解析

2016-07-28 23:31 459 查看
fragment作为用户交互的组件之一,大家都经常接触到。对于fragment的操作不熟悉,会导致很多逻辑的错误。尤其是对它的生命周期的模糊不清,很容易导致fragment初始化失败。而通过fragment事务的操作,可以让我们写代码的时候更加模块化,所以很有必要了解FragmentTransaction这个东西。其次,fragment经常作为屏幕适配的一个解决方案,所以在后面的例子会讲解同一个屏幕使用双fragment的情况以及fragment与activity的相互交互的情况。下面逐一介绍FragmentTransaction,Fragmentmanger,Fragment,ListFragment,DialogFragment,WebViewFragment。

一、FragmentTransaction

FragmentTransaction是一个定义了一系列关于fragment操作的抽象类。在调用FragmentManager对fragment进行添加、替换等操作时 ,都是通过事务的方式来启动的。所以对于FragmentTransaction的了解有助于在开发中更好的操作fragment的添加替换的操作。对于此类,有下面几个重要的方法需要理解:

①、添加fragment事务的方法操作:

public abstract FragmentTransaction add(int containerViewId, Fragment fragment, String tag)


将fragment添加至activity里,如果fragment是含有自身的视图的话(即onCreateView返回值不为null),那么fragment就会添加到activity的视图容器里。注意执行完添加操作记得使用FragmentManager.commit方法进行事务的提交。

参数解析:

containerViewId:这是一个可选的值,表示一个用于安置fragment的视图容器的ID,当此值为0是,表示当前的fragment不会被安置到视图容器里。

fragment:要添加的fragment对象,要注意的是,这个对象必须不能是此前在activity添加过的。

tag:可选的fragment的标签名称,主要目地是在后续操作中,FragmentManager.findFragmentByTag可以根据tag来查找对应的fragment。此值可传值null,但是后续就不能通过上述方法查找fragment 了。

②、替换fragment的事务操作:

public abstract FragmentTransaction replace(int containerViewId, Fragment fragment, String tag);


用于替换已经存在的安置于containerViewId的fragment。注意执行完操作记得使用FragmentManager.commit方法进行事务的提交。此方法等同于通过移除所有与containerViewid的相关的fragment,然后再通过add方法将当前fragment添加进去。

参数解析:

containerViewId:表示要被替代的fragment的视图容器的id,同时替换成功后,也会成为当前fragment视图容器的id。

fragment:用于替换的fragment对象。

tag:和add方法一样。

③、移除fragment的事务操作:

public abstract FragmentTransaction remove(Fragment fragment);


从activity中移除指定的已存在的fragment,如果当前fragment正好添加进了activity的视图容器了,那么fragment的视图也会被移除。

参数解析:

fragment:将要被移除的对象。

④、隐藏fragment的事务操作:

public abstract FragmentTransaction hide(Fragment fragment);


隐藏指定的fragment对象,此方法只有在指定的fragment刚好在activity的视图容器里,才会把fragment的视图隐藏起来。

⑤、显示fragment的事务操作:

public abstract FragmentTransaction show(Fragment fragment);


显示指定的fragment,这个fragment通常是被隐藏的fragment对象并且已经添加到activity的视图容器里,这样才会重新显示被影藏的fragment视图。

⑥、移除fragment视图的事务操作:

public abstract FragmentTransaction detach(Fragment fragment);


此方法用于将fragment的视图销毁,但是fragment仍然是可用的,可通过fragmentManager重新复用(在FragmentManager里面其实保存了一个栈,存放了很多fragment事务)。

⑦、附加fragment视图的事务操作:

public abstract FragmentTransaction attach(Fragment fragment);


此方法用于将此前被移除的fragment视图重新恢复,调用后就可以将视图重新显示出来。

⑧、设置在此事务中所有fragment的显示显现动画:

public abstract FragmentTransaction setCustomAnimations(int enter, int exit)


其中enter表示的是fragment显现的布局动画,exit是小时的。

⑨、提交事务的操作:

public abstract int commit();


将事务操作提交,此操作不会立即出发事务的执行,而是会进行排队等待,当线程准备好了才会执行,且必须是在主线程中调用这个方法。此方法必须在包含fragment的activity保存状态之前调用,否则的话会抛出异常。因为在保存状态之后调用commit方法,此fragment的状态就无法得到保存,对于activity恢复状态(比如配置改变了)来说就会发生错误。

细心的读者可以发现,上面所说的所有方法都是抽象方法,也就是说并不能直接被我们所用,那是不是说需要我们自己去实现呢。其实不是的,上面提到的这个方法只是为后面做铺垫。我们真正用于操控fragment的类是FragmentManager,FragmentManager方法有一个实现类叫做FragmentManaegerImpl,这个类里面包含了一个实现了FragmentTransaction的BackStackRecord类,后续的很多操作都是调用这个类来执行fragment的事务操作的。下面介绍一下FragmentManager。

⑩、将事务压入后退栈的操作:

public abstract FragmentTransaction addToBackStack(String name)


fragment的事务可以被压入后退栈(activity维护的一个用于保存fragmentd的事务操作的栈),在执行了commit方法之后,此事务就会被押入栈,之后,我们在按返回键返回的时候,此事务就会出栈并会还原成上次的视图状态出现在用户面前。

参数解析:

name:与当前事务相关的名字,可以为null。

二、FragmentManager

FragmentManager是一个抽象类,里面包含了大量的与Activity中的Fragment进行交互的接口,也是我们直接打交道的进行管理Fragment的类。对于这个类,有以下几点要注意:

①、开始一个Fragment事务。

public abstract FragmentTransaction beginTransaction()


此方法用于启动一系列与当前FragmentManager相关联的Fragment的编辑操作。注意Fragment事务的创建和提交都必须在activity保存状态之前调用(Acitivty.onSaveInstanceState之前),否则会报错。因为fragment的状态也是要保存的。

②、立即执行事务操作:

public abstract boolean executePendingTransactions();


前面说过,事务调用commit方法之后,不是立即执行的,而是会在线程里等待执行。如果需要立即执行的话,就可以调用此方法。此方法必须在主线程里面调用。

③、查找Fragment:

public abstract Fragment findFragmentById(int id);


public abstract Fragment findFragmentByTag(String tag);


上述两个方法都可以用于查找Fragment,前者是通过ID查找,即containerViewId,后者是通过tag查找。通过FragmentTransaction的介绍可以知道,tag是可以为null的,也就是说如果后续我们想查找Fragment操作的话,建议在添加fragment事务的时候,指定一个唯一的tag是个好习惯。上述两个方法都是先从当前activity查找fragment,找不到的话才会从后退栈查找。

对于FragmentManger的比较重要的方法就是这些了,因为这里边的大部分实现都是围绕着FragmentTransaction来开展的,也就是说,了解了FragmentTransaction各个方法的目的就可以实现基本的操作了。下面开始了解Fragment大家族。对于Fragment来说,主要了解它的生命周期和一些需要注意的点就可以了。另外就是需要了解ListFragment,WebViewFragment,DialogFragment的特性以及使用方法。最后会通过一个综合的例子来介绍如何应用。



三、Fragment

Fragment是一种依赖于Activity的可用于与用户交互的组件,与Fragment进行交互主要通过FragmentManager来管理,获取FragmentManager的方法有activity.getFragmentManager和fragement.getFragmentManager。fragment的用途是非常广泛的,但核心用途还是作为Activity的组件来与用户进行交互。fragment是依赖于activity存在的,但是却有着自己的生命周期,尽管如此,fragment的生命周期还是建立在activity的生命周期内的。比如说,activity处于stop状态,fragment是不可能启动的。需要注意的一点事,我们继承fragment的时候,不应该含有拥有参数的构造方法,而必须拥有一个public状态的无参构造方法(因为一个类没有构造方法的话,系统会默认有一个无参构造函数的,所以我们往往不用重写构造函数)。因为系统在恢复状态的时候,会多次调用无参构造函数进行初始化fragment,如果找不到这个函数就会进行报错。下面讲解一下Fragment的生命周期:

前面说了Fragment是依赖于activity的,也就是说,除了拥有activity生命周期的方法外,也拥有自己的生命周期。Fragment中生命周期可分为两个阶段,一个是fragment从没有到与用户交互的阶段。另一个是销毁阶段。第一个阶段的生命周期是:

onAttach:一旦fragment和activity关联上了就回调。

onCreate:创建fragment的时候回调。

onCreateView:创建和返回与fragment关联的视图。

onActivityCreated:当activity的onCreate方法完成了回调。

onViewStateResotred:但所有已保存的fragment的视图恢复完毕的时候调用。

onStart:fragment变得可见,但这个需要基于activity的onStart方法。

onResume:可与用户交互,基于activity的onRresume方法。

当fragment处于销毁阶段,需要经历一下回调方法:

onPause:fragment变得不可交互,当activity的onPause方法调用或者当前的fragment被移除的时候被调用。

onStop:fragment变得不可见,当activityd的onStop方法调用或者fragment被移除。

onDestoryView:清除fragment的视图相关的资源。

onDestory:清除fragement的状态。

onDetach:当fragment和activity分离的时候调用。

Fragment可以作为布局的一部分来使用,比如可以在布局文件里放置两个fragment标签,分别对应两个fragment,在activity创建的时候,这个fragment就会成为activity的一部分也被创建。稍后通过一个例子来说明。

Fragment的事务可以压入acitivty的一个后退栈里面,当我们按返回键的时候,它就会重新重新弹出来并恢复之前的fragment,当栈内没有事务的时候返回键事件才会被activity拦截处理。

Fragment是和Activity紧密相关的,因此,除了不能直接操作资源等与context相关的操作之外(可以通过getContext或者getAcitivty获取相关的context进行操作)其他的和我们使用activity没什么不同。对于比较不一样的地方,除了自身的生命周期之外,就是FragmentManagerImpl所提供的方法,但是这个方法是j继承了FragmentManager的,所以等于我们也知道了它所提供的方法都是什么作用。

下面介绍一些系统实现的具有特殊用途的Fragment的子类。



四、ListFragment

ListFragment是fragment的一个子类,用于显示数据列表的(用的是ListView),并且里面封装好了handler处理时间的方法,如果单击某项数据需要有特殊行动的话,就需要重写该方法(后面会详解)。用于当作数据源的一般是Array或者Cursor。因为显示数据的是ListView,所以ListFragment的用于连接数据源与listView的adpter必须是ListAdapter的子类。ListFragment默认情况下包含一个含有ListView的布局,如果我们希望自定义布局内容,那么只要重写onCreateView方法就可以了,但是有一个条件,自定义布局必须含有ListView且id必须是@android:id/list。当ListView的数据为空时,没有数据需要显示的时候,我们可以指定任意控件作为数据为空的时候的显示控件,此视图的id必须是@android:id/empty。当数据为空时,listView会隐藏并且空数据提示控件(空数据显示的视图)会显示出来。典型的布局如下:

<?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:paddingLeft="8dp"
*         android:paddingRight="8dp">
*
*     <ListView android:id="@id/android:list"
*               android:layout_width="match_parent"
*               android:layout_height="match_parent"
*               android:background="#00FF00"
*               android:layout_weight="1"
*               android:drawSelectorOnTop="false"/>
*
*     <TextView android:id="@id/android:empty"
*               android:layout_width="match_parent"
*               android:layout_height="match_parent"
*               android:background="#FF0000"
*               android:text="No data"/>
* </LinearLayout>


每一项数据的布局文件可通过设置Adpter的时候设置,这个 后面来讲。主要方法盘点:

①:创建布局视图,先看一下源码:

public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(com.android.internal.R.layout.list_content,
container, false);
}


可以看到,如果没有自定义布局的需要,我们根本不需要重写这个方法,但是如果我们有这个需求,就必须重写这个方法,并且布局必须含有一个id为@id/android:list的listView,如果希望数据为空的时候,显示提示视图,那么只要将该视图的id设为@id/android:empty即可。

②、点击事件:

public void onListItemClick(ListView l, View v, int position, long id) {
}


如果对于点击数据项需要做出反应,需要重写上述方法。
参数解析:

l:发生点击事件的listView。
v;被点击的视图。
position:被点击的视图的小标。
id:被点击的是图的id。

③、设置数据适配器:

public void setListAdapter(ListAdapter adapter) {


注意必须是ListAdapter的子类,如果对于Adapter不是很了解,可以参考Adapter大家族详解。每项数据的样式也是通过adpter设置的。

以上就是ListFragment的解析。接下来讲解WebViewFragment。



五、WebViewFragment:

顾名思义,这是一个嵌套了webView的Fragment,并且该webView会跟随Fragment的pause而暂停,start而启动。这个类其实没啥好说的,就跟我们自己写个布局文件,里面放个webView接着在Fragment里操作一样的。不过如果使用这个类的话,可以通过getWebView方法获取WebViewFragment的Webview,然后我们就可对它进行一些设置。下面讲解DialogFragment。

六、DialogFragment:

这个Fragment含有一个Dialog对象,这个对象会根据fragment的状态来决定什么时候hide、show、dismiss。如果我们要控制dialog不要直接对它执行操作,而是应该使用这个fragment给我们提供的api。如果要继承DialogFragment,应该重写onCreateView来设置dialog的布局。如果想要完全控制dialog的样式,可以通过onCreateDialog来控制。这个类的优点在于将dialog与fragment的生命周期紧密相连起来了,但是笔者认为如果处理好dialog与fragment的关系,自己直接继承fragment是更好的选择。如果读者对此类感兴趣,可自行学习。

接下来使用一个综合运用了上述所有知识的例子来讲述具体的用法。下面这个例子是关于显示数字的。当处于竖屏模式的时候,会显示一个显示1-10的数字列表,点击其中一个就会跳转到另一个activity显示相应的数据。当时横屏模式的时候,会显示一个有两个fragment的activity ,单击左边的数据会在右边显示出来。

首先资源文件:

strings-xml:

<resources>
<string name="app_name">fragment</string>
<string-array name="number">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
</string-array>
</resources>


布局文件分为竖屏模式和横屏模式,先看目录结构:



layout/activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.cw.fragment.MainActivity">

<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
</LinearLayout>


layout-land/activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="2"
tools:context="com.cw.fragment.MainActivity">

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

<fragment
android:id="@+id/rightFragment"
android:name="com.cw.fragment.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
tools:layout="@layout/right_layout" />
</LinearLayout>


上述的rightfragment在后面会提到。
layout/right_layout:

<?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:background="#efefef"
android:gravity="center"
android:orientation="vertical">

<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="30dp"
android:gravity="center"
android:text="内容" />
</LinearLayout>

接着定义RightFragment:

package com.cw.fragment;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
* Created by Myy on 2016/7/28.
*/
public class RightFragment extends Fragment {

private View view;
private TextView textView;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.right_layout, null);
textView = (TextView) view.findViewById(R.id.content);
return view;
}

public void setContent(int i) {
textView.setText(i + "");
}
}


然后是RightActivity:

package com.cw.fragment;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

/**
* //用于单Fragment
* Created by Myy on 2016/7/28.
*/
public class RightActivity extends AppCompatActivity {

private TextView textView;
private RightFragment fragment;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragment = new RightFragment();
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment).commit();

}

@Override
protected void onStart() {
super.onStart();
int i = getIntent().getIntExtra("position", 0);
fragment.setContent(i);
}
}


最后是mainActivity:

package com.cw.fragment;

import android.content.Intent;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

private List<String> numbers = null;
//注意包的引用
private ListFragment leftFragment;
private RightFragment rightFragment;
private ListView listView;
private boolean isTwoPage = false;//用于判断是否是双fragment模式,在这里竖屏模式设为双fragment模式

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
numbers = Arrays.asList(getResources().getStringArray(R.array.number));
initListFragment();
FragmentManager manager = getSupportFragmentManager();
rightFragment = (RightFragment) manager.findFragmentById(R.id.rightFragment);//当加载的是layout-land布局的fragment才不为null
if (rightFragment != null)
isTwoPage = true;
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.container, leftFragment);
transaction.commit();
// transaction.addToBackStack(null);可以将此事务添加进后退栈里面
//fragment调用activityd的方法。通过getActivity()
//acitivty调用fragment的方法。getFragmentManager().findFragmentById()
}

@Override
protected void onStart() {
super.onStart();
listView = leftFragment.getListView();
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (isTwoPage)
rightFragment.setContent(position + 1);
else {
Intent intent = new Intent(MainActivity.this, RightActivity.class);
intent.putExtra("position", position+1);
startActivity(intent);
}
}
});
}

private void initListFragment() {
leftFragment = new ListFragment();
ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1, numbers);
leftFragment.setListAdapter(adapter);

}
}


上述可以看到用ListFragment添加到左边的Framelayout布局,使用了事务添加操作。

看看竖屏模式的效果:



点击:



切换竖屏模式:



点击:



如上,请读者好好体会这个例子,可以发现fragment和activity的异同以及事务使用的方法。

---------文章写自:HyHarden---------

--------博客地址:http://blog.csdn.net/qq_25722767-----------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息