Fragment
Fragment 表示应用界面中可重复使用的一部分。Fragment 定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。Fragment 不能独立存在,而是必须由 Activity 或另一个 Fragment 托管。Fragment 的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构。
模块化
Fragment 允许您将界面划分为离散的区块,从而将模块化和可重用性引入 Activity 的界面。Activity 是围绕应用的界面放置全局元素(如抽屉式导航栏)的理想位置。相反,Fragment 更适合定义和管理单个屏幕或部分屏幕的界面。
假设有一个响应各种屏幕尺寸的应用。在较大的屏幕上,该应用应显示一个静态抽屉式导航栏和一个采用网格布局的列表。在较小的屏幕上,该应用应显示一个底部导航栏和一个采用线性布局的列表。在 Activity 中管理所有这些变化因素可能会很麻烦。将导航元素与内容分离可使此过程更易于管理。然后,Activity 负责显示正确的导航界面,而 Fragment 采用适当的布局显示列表。
同一屏幕的采用不同屏幕尺寸的两个版本。在左侧,大屏幕包含一个由 Activity 控制的抽屉式导航栏和一个由 Fragment 控制的网格列表。在右侧,小屏幕包含一个由 Activity 控制的底部导航栏和一个由 Fragment 控制的线性列表。
将界面划分为 Fragment 可让您更轻松地在运行时修改 Activity 的外观。当 Activity 处于 STARTED 生命周期状态或更高的状态时,可以添加、替换或移除 Fragment。您可以将这些更改的记录保留在由 Activity 管理的返回堆栈中,从而允许撤消这些更改。
您可以在同一 Activity 或多个 Activity 中使用同一 Fragment 类的多个实例,甚至可以将其用作另一个 Fragment 的子级。考虑到这一点,您应只为 Fragment 提供管理它自己的界面所需的逻辑。您应避免让一个 Fragment 依赖于另一个 Fragment 或从一个 Fragment 操控另一个 Fragment。
Fragment的生命周期
从官方拿(偷)过来的图
Fragment也有生命周期,这货咋还和Activity有点像呢,先看看生命周期回调方法的调用时机和意义吧
- onAttach(Context context):在Fragment和Activity关联上的时候调用,且仅调用一次。在该回调中我们可以将context转化为Activity保存下来,从而避免后期频繁调用getAtivity()获取Activity的局面,避免了在某些情况下getAtivity()为空的异常(Activity和Fragment分离的情况下)。同时也可以在该回调中将传入的Arguments提取并解析,在这里强烈推荐通过setArguments给Fragment传参数,因为在应用被系统回收时Fragment不会保存相关属性,具体之后会讲解。
- onCreate:在最初创建Fragment的时候会调用,和Activity的onCreate类似。
- View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState):在准备绘制Fragment界面时调用,返回值为Fragment要绘制布局的根视图,当然也可以返回null。注意使用inflater构建View时一定要将attachToRoot指明false,因为Fragment会自动将视图添加到container中,attachToRoot为true会重复添加报错。onCreateView并不是一定会被调用,当添加的是没有界面的Fragment就不会调用,比如调用FragmentTransaction的 add(Fragment fragment, String tag)方法。
- onActivityCreated :在Activity的onCreated执行完时会调用。
- onStart() :Fragment对用户可见的时候调用,前提是Activity已经started。
- onResume():Fragment和用户之前可交互时会调用,前提是Activity已经resumed。
- onPause():Fragment和用户之前不可交互时会调用。
- onStop():Fragment不可见时会调用。
- onDestroyView():在移除Fragment相关视图层级时调用。
- onDestroy():最终清除Fragment状态时会调用。
- onDetach():Fragment和Activity解除关联时调用。
不难看出,Fragment和Activity的生命周期有着很大的关联,前面也说到,Fragment主要用于模块化,所以,Fragment只能依赖于Activity而存在,如果Activity被销毁,那么在Activity销毁之前,Fragment会先解绑Activity并销毁。Activity和Fragment是一对多的关系,一个Activity中可以存在多个Fragment,而一个Fragment实例只能在同一个时间对应一个Activity
创建Fragment
片段表示活动中用户界面的模块化部分。片段具有自己的生命周期,接收自己的输入事件,您可以在包含活动运行时添加或删除片段。
创建一个片段
class ExampleFragment extends Fragment {
public ExampleFragment() {
//片段对应的布局
super(R.layout.example_fragment);
}
}
向活动中添加Fragment
首先,被添加Fragment的Activity必须是FragmentActivity或其子类,比如说AppCompatActivity,然后在xml布局文件中添加一个Fragment容器(你创建了一个Fragment总得有地方装吧),总的来说无论静态声明Fragment或者动态添加Fragment,在Activity的xml布局文件中总得有一个Fragment容器(下面会说到)
通过xml添加Fragment
<!-- res/layout/example_activity.xml -->
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.ExampleFragment" />
<!--如若使用静态添加Fragment,则必须在容器中使用android:name属性声明该Fragment,值为想要添加Fragment的全类名-->
在Activity创建时,该容器会自动去实例化name属性声明的Fragment,并将其放入容器在布局中的位置。
在代码中添加
public class ExampleActivity extends AppCompatActivity {
public ExampleActivity() {
super(R.layout.example_activity);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用FragmentManager添加Fragment到R.id.fragment_container_view对应的Fragment容器中
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, ExampleFragment.class, null)
.commit();
}
}
Fragment 管理器
FragmentManager 类负责对应用的 Fragment 执行一些操作,如添加、移除或替换它们,以及将它们添加到返回堆栈。
你在在FragmentActivity或其子类中调用getSupportFragmentManager()
FragmentManager manager = getSupportFragmentManager();
FragmentManager对象可以用开始一个Fragment的管理事务
FragmentTransaction transaction = manager.beginTransaction();
FragmentTransaction对象可以实现对fragment的增加,删除,替换,隐藏等操作
//使用事务将一个fragment实例替换到R.id.fragment指定的Fragment容器中
transaction.replace(R.id.fragment,new Fragment());
//使用事务将一个fragment添加到R.id.fragment指定的Fragment的容器中
transaction.add(R.id.fragment,new Fragment());
//从事务中移除一个fragment所指向的实例对象
transaction.remove(fragment);
//隐藏事务中现存在的fragment实例,fragment将会被隐藏
transaction.hide(fragment);
//显示被隐藏的fragment实例,fragment将会被显示
transaction.show(fragment);
里面有一个特别一点的方法:
/**
官方解释:当您移除或替换一个片段并向返回栈添加事务时,系统会停止(而非销毁)移除的片段。 如果用户执行回退操作进行片段恢复,该片段将重新启动。 如果您不向返回栈添加事务,则系统会在您移除或替换片段时将其销毁。
说实话,我第一次没咋看懂QAQ
*/
transaction.addToBackStack("fragment");
人性化一点,图片样例显示:
在进行fragment事务创建提交的过程中,不添加此方法 ,如下图:
添加了此方法后,如下图:
非常好懂吧!下面是常用操作代码。
public void replaceFragment(Fragment fragment){
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragment,fragment);
//不加或加就是上面两张图的区别 为了名字不重复(tag不重复,一般都是传入null值),
transaction.addToBackStack(null)
transaction.commit();
}
差不多了,来写个样例吧。
如果是在手机上:
平板上的显示效果:(图片)
平板上的操作:
可以看到,对于不同大小尺寸的移动端设备,操作逻辑和页面布局的区别是非常大的,对于这种只需要某块区域更新页面显示的,肯定不是用Activity跳转的形式,这时候Fragment的作用就非常明显了。
下面先来创建自己的第一个Fragment Android项目吧。
首先先引进一个知识点,为了适配不同屏幕大小的android手机或android平板,App需要利用限定符来为不同的屏幕设定不同的布局文件。
android中一些常用的限定符如下所示:
| 屏幕特性 | 限定符 | 描述 |
|---|---|---|
| 屏幕尺寸 | small | 小屏幕 |
| normal | 基准屏幕 | |
| large | 大屏幕 | |
| xlarge | 超大屏幕 | |
| 屏幕密度 | ldpi | <=120dpi |
| mdpi | <= 160dpi | |
| hdpi | <= 240dpi | |
| xhdpi | <= 320dpi | |
| xxhdpi | <= 480dpi | |
| xxhdpi | <= 640dpi(只用来存放icon) | |
| nodpi | 与屏幕密度无关的资源.系统不会针对屏幕密度对其中资源进行压缩或者拉伸 | |
| tvdpi | 介于mdpi与hdpi之间,特定针对213dpi,专门为电视准备的,手机应用开发不需要关心这个密度值. | |
| 屏幕方向 | land | 横向 |
| port | 纵向 | |
| 屏幕宽高比 | long | 比标准屏幕宽高比明显的高或者宽的这样屏幕 |
| notlong | 和标准屏幕配置一样的屏幕宽高比 |
//android 系统会对移动设备的尺寸大小来调用res资源目录下不同文件夹下的布局,如果没有创建其他限定符尺寸的文件夹,就使用默认的(layout)
res/layout/my_layout.xml // 正常屏幕尺寸的布局 ("default")
res/layout-large/my_layout.xml // 大屏幕布局
res/layout-xlarge/my_layout.xml // 超大屏幕的布局
res/layout-xlarge-land/my_layout.xml // 横屏超大屏幕布局
res/drawable-mdpi/graphic.png // 中等密度位图
res/drawable-hdpi/graphic.png // 高密度位图
res/drawable-xhdpi/graphic.png // 超高密度位图
res/drawable-xxhdpi/graphic.png // 超高密度位图(比上面那个还要高密度)
res/mipmap-mdpi/my_icon.png // 中等密度的图标
res/mipmap-hdpi/my_icon.png // 高密度的图标
res/mipmap-xhdpi/my_icon.png // 超高密度的图标
res/mipmap-xxhdpi/my_icon.png // ......
res/mipmap-xxxhdpi/my_icon.png // ......
开始创建吧
1.先在res文件夹下创建一个layout-xlarge文件夹
2.创建一个MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
3.将创建MainActivity时自动创建的activity_main.xml的布局(在res/layout文件夹下)复制一份到我们上面创建的layout-xlarge文件夹下。
3.开始编辑这两个文件夹下的布局,layout文件夹下的布局是用于手机的,layout-xlarge文件夹下的布局是用于平板的。
对于手机布局文件,操作逻辑为一个列表显示,点击列表转跳到一个Activity然后显示数据。
<?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"
android:gravity="center"
tools:context=".MainActivity">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/title_list"
/>
</LinearLayout>
而对于平板布局文件,则为左侧列表显示,右侧用一个Fragment显示,点击左侧列表后右侧Fragment更新数据。
<?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:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="@+id/title_list"
/>
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:id="@+id/fragment"
/>
</LinearLayout>
没有用,是因为要动态添加,如果将上面的改成,则需要在中白编写name属性,标志该fragment用于是与哪个Fragment进行绑定。
4.然后创建手机端点击列表转跳的WebActivity
web_layout.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"
android:padding="5dp"
tools:context=".WebActivity">
<!--用WebView展示和加载MainAcitvity传输过来的url网址-->
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/web_view"
/>
</LinearLayout>
WebActivity.java
import android.os.Bundle;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;
public class WebActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web);
initView();
String url = getIntent().getStringExtra("url");
//加载网址并在webView中显示
webView.loadUrl(url);
}
private void initView() {
webView = (WebView) findViewById(R.id.web_view);
}
}
5.再创建平板端右侧显示的Fragment
web_fragment.xml对应的布局(fragment是可以拥有自己的布局的)
<?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:padding="5dp"
tools:context=".fragment.WebFragment">
<!--用WebView展示和加载MainAcitvity传输过来的url网址-->
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/web_view"
/>
</LinearLayout>
WebFragment.java
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import androidx.fragment.app.Fragment;
import com.pj.fragment.R;
public class WebFragment extends Fragment {
private WebView webView;
private String url;
public WebFragment(String url) {
this.url = url;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_web, container, false);
initView(view);
//加载网址并在webView中显示
webView.loadUrl(url);
return view;
}
private void initView(View view) {
webView = (WebView) view.findViewById(R.id.web_view);
}
}
6.对于两个layout文件夹下都有的ListView,为它创建一个专属的适配器
item.xml ( layout )
<?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="80dp"
android:minHeight="80dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
>
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/list_item_icon"
/>
</LinearLayout>
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:id="@+id/list_item_content"
android:textSize="20sp"
/>
</LinearLayout>
ListAdapter.java
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.pj.fragment.R;
import com.pj.fragment.bean.WebListBean;
import java.util.List;
public class ListItemAdapter extends BaseAdapter {
List<WebListBean> data;
private ImageView listItemIcon;
private TextView listItemContent;
public ListItemAdapter(List<WebListBean> data) {
this.data = data;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int i) {
return data.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
@SuppressLint("ViewHolder") View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, null);
initView(v);
listItemIcon.setImageResource(data.get(i).getImage());
listItemContent.setText(data.get(i).getName());
return v;
}
private void initView(View v) {
listItemIcon = (ImageView) v.findViewById(R.id.list_item_icon);
listItemContent = (TextView) v.findViewById(R.id.list_item_content);
}
}
7.创建用于存放数据的JavaBean类
public class WebListBean {
//列表名称
private String name;
//对应加载的网址
private String url;
//列表上的图标资源id
private int image;
public WebListBean() {
}
public WebListBean(String name, String url, int image) {
this.name = name;
this.url = url;
this.image = image;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getImage() {
return image;
}
public void setImage(int image) {
this.image = image;
}
}
8.汇总吧(在MainActivity中汇总)
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ListView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import com.pj.fragment.adapter.ListItemAdapter;
import com.pj.fragment.bean.WebListBean;
import com.pj.fragment.fragment.WebFragment;
import com.pj.fragment.util.ScreenUtil;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ListView titleList;
private FrameLayout fragment;
private boolean isPad = false;
private List<WebListBean> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
titleList.setAdapter(new ListItemAdapter(data));
titleList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (isPad){
//如果为平板,则更新右侧的fragment,将网址的url用构造方法传输过去
replaceFragment(new WebFragment(data.get(i).getUrl()));
}else {
//如果是手机,则将url用Intent传参带到WebActivity
startActivity(new Intent(MainActivity.this,WebActivity.class).putExtra("url",data.get(i).getUrl()));
}
}
});
}
private void initView() {
isPad = isPad(MainActivity.this);
titleList = (ListView) findViewById(R.id.title_list);
if (isPad) {
fragment = (FrameLayout) findViewById(R.id.fragment);
}
data = new ArrayList<>();
data.add(new WebListBean("苹果","https://www.apple.com.cn",R.drawable.apple));
data.add(new WebListBean("华为","https://www.huawei.com/cn/",R.drawable.huawei));
data.add(new WebListBean("小米","https://m.mi.com/",R.drawable.mi));
data.add(new WebListBean("oppo","https://www.oppo.com/cn/",R.drawable.oppo));
data.add(new WebListBean("vivo","http://www.vivo.com.cn/",R.drawable.vivo));
}
//用FragmentManager更换Fragment显示
public void replaceFragment(Fragment fragment){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment,fragment).commit();
}
//用于判断是否为平板
public static boolean isPad(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
}
撒花!
项目gitee地址:gitee.com/thor19/frag…
本人蒟蒻,有错误多多提醒包含。