碎片是什么
碎片(Fragment)是一种可以嵌入在活动当中的UI片段,(3.0版本开始引入)它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用得非常广泛。
碎片的简单用法
新建一个左侧碎片布局left_fragment.xml
代码如下:
<?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="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button"/>
</LinearLayout>
新键右侧碎片布局
<?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="#00ff00"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="This is right fragment"
android:textSize="20sp"/>
</LinearLayout>
接着新建一个LeftFragment类
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, null);
return view;
}
}
RightFragment类
public class RightFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
savedInstanceState) {
View view = inflater.inflate(R.layout.right_fragment, null);
return view;
}
}
修改activity_main中代码
<?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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="com.pengpeng.onenumber_04fragmentsimpleused.MainActivity">
<fragment
android:id="@+id/left_fragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:name ="com.pengpeng.onenumber_04fragmentsimpleused.fragment.LeftFragment"
android:layout_weight="1"/>
<fragment
android:id="@+id/right_fragment"
android:layout_width="0dp"
android:name ="com.pengpeng.onenumber_04fragmentsimpleused.fragment.RightFragment"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
如何动态添加碎片
新建another_right_fragment.xml
<?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="#ffff00"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="This is another right fragment"
android:textSize="20sp"/>
</LinearLayout>
新建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;
}
}
修改activity_main代码
<?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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="com.pengpeng.onenumber_04fragmentsimpleused.MainActivity">
<fragment
android:id="@+id/left_fragment"
android:name="com.pengpeng.onenumber_04fragmentsimpleused.fragment.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>
</LinearLayout>
修改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 RightFragment());
}
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();//获取FragmentManager
FragmentTransaction transaction = fragmentManager.beginTransaction();//开启事务
transaction.replace(R.id.right_layout, fragment);//向容器内添加或替换碎片
transaction.commit();//提交事务
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
replaceFragment(new AnotherRightFragment());
break;
default:
break;
}
}
}
在碎片中模拟返回栈
按下Back键就会直接退出,按下Back键返回上一个碎片如何实现
FragmentTransaction中提供了了一个addToBackStack()方法,可以用于将一个事务添加到返回栈中,修改MainActivity中的代码
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();//获取FragmentManager
FragmentTransaction transaction = fragmentManager.beginTransaction();//开启事务
transaction.replace(R.id.right_layout, fragment);//向容器内添加或替换碎片
transaction.addToBackStack(null);//接收一个名字描述返回栈的状态,一般传null即可
transaction.commit();//提交事务
}
碎片和活动之间进行通信
虽然碎片都是嵌入在活动中显示的,可是实际上他们的关系并没有那么亲密,碎片和活动都是各自存在于一个独立的类当中的,它们之间并没有那么明显的方式来直接进行通信,如果想要在活动中直接调用碎片里的方法,或者在碎片中调用活动里的方法,应该如何实现
为了方便碎片和活动之间进行通信,FragmentManager提供了一个类似于findViewById()的方法,专门用于从布局文件中获取碎片的实例,代码如下
LeftFragment leftFragment = (LeftFragment) getSupportFragmentManager().findFragmentById(R.id.left_fragment);
调用FragmentManager的findFragmentById()方法可以在活动中得到相应的碎片的实例,然后就能轻松调用碎片里的方法了
在碎片中如何调用活动里的方法呢,在每个碎片中都可以通过调用getAcitivity()方法来的到当前碎片相关联的活动实例
代码如下
MainActivity activity = (MainActivity) getActivity();
另外当碎片中需要使用Context对象时,也可以使用getActivity()方法。
碎片之间通信:
首先在一个碎片中可以得到与他关联的活动,然后在通过这个活动去获取另一个碎片的实例,这样也就实现了不同碎片之间的通信功能。
碎片的生命周期:
和活动一样碎片也有自己的生命周期,并且它和活动的生命周期差不多
碎片的状态和回调:
1:运行状态
当一个碎片是可见的,并且它所关联的活动正处于运行状态时,该碎片也处于运行状态
2:暂停状态
当一个活动进入暂停状态时,(由于另一个未占满屏幕的活动被添加到了栈顶),与他相关的可见碎片就会进入到暂停状态
3:停止状态
当一个活动进入停止状态时,与他相关联的碎片就会进入到停止状态,或者通过调用FragmentTransaction的remove(),replace()方法将碎片从活动中移除,但如果在事务提交之前调用
addToBackStack()方法,这时的碎片也会进入到停止状态,总的来说,碎片对用户来说是完全不可见的,有可能会被系统回收
4:销毁状态
碎片宗师依附于活动而存在的,因此当活动被销毁时,与他相关联的碎片就会进入销毁状态,或者通过调用FragmentTransaction的remove(),replace()方法将碎片从活动中移除,但在事务提交之前调用addToBackStack()方法,这时的碎片也会进入到销毁状态
碎片的几个回调方法:
onAttach():当碎片和活动建立关联的时候调用
onCreateView():当碎片创建视图加载布局的时候调用
onActivityCreated():确保与碎片相关联的活动一定已经创建完毕的时候调用
onDestroyView():当与碎片关联的视图被移除的时候调用
onDetach():当碎片和活动解除关联的时候调用
当碎片第一次被加载到屏幕上时,会依次执行onAttach(),onCreate(),onCreateView(),onActivityCreated(),onStart()和OnResume()方法
替换碎片时,执行onPause(),onStop(),onDestoryView()方法,此时碎片进入停止状态
当然如果在替换的时候没有调用addToBackStack()方法,此时碎片就会进入销毁状态,onDestory()和
onDetach()方法就会得到执行
在碎片中也可以用onSaveInstanceState()方法来保存数据,因为进入停止状态的碎片有可能在系统内存不足的时候被回收,保存下来的数据在onCreate(),onCreateView(),onActivityCreated()这三个方法中都可以重新得到
动态加载布局的技巧:
怎样判断程序应该是使用双页模式还是单页模式这就需要使用限定符(Qulifiers)来实现了
使用限定符
在res目录下新建layout-large文件夹,在这个文件夹下新建一个布局,也叫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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="com.pengpeng.onenumber_04fragmentsimpleused.MainActivity">
<fragment
android:id="@+id/left_fragment"
android:name="com.pengpeng.onenumber_04fragmentsimpleused.fragment.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<fragment
android:id="@+id/right_fragment"
android:name="com.pengpeng.onenumber_04fragmentsimpleused.fragment.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3/>
</LinearLayout>
原布局文件修改如下
<?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:name="com.pengpeng.onenumber_04fragmentsimpleused.fragment.LeftFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Android中一些常见的限定符
大小来作为特征
small:提供给小屏幕设备的资源
normal:提供给中等屏幕设备的资源
large:提供给大屏幕设备的资源
xlarge:提供给超大屏幕设备的资源
分辨率作为特征
ldpi:提供给低分辨率设备的资源120dpi以下
mdpi:提供给中等分辨率设备的资源120dpi-160dpi
hdpi:提供给高分辨率设备的资源160dpi-240dpi
xhdpi:提供给超高分辨率设备的资源240dpi-320dpi
xxhdpi:提供给超高分辨率设备的资源 320dpi-480dpi
方向作为特征
land:提供给横屏设备的资源
port:提供给竖屏设备的资源
使用最小宽度限定符
有的时候我们希望可以更加灵活的为不同设备加载布局,不管他们是不是被系统认定为large
这时就可以使用最小宽度限定符
在res目录下新建layout-sw600dp文件夹,然后在这个文件加下新建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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="com.pengpeng.onenumber_04fragmentsimpleused.MainActivity">
<fragment
android:id="@+id/left_fragment"
android:name="com.pengpeng.onenumber_04fragmentsimpleused.fragment.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<fragment
android:id="@+id/right_fragment"
android:name="com.pengpeng.onenumber_04fragmentsimpleused.fragment.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3/>
</LinearLayout>
这就意味着,当程序运行在屏幕宽度大于600dp的设备上时,会加载layout-sw600dp/activity_main布局
,当程序运行屏幕宽度在小于600dp的设备上时,则仍然会加载默认的layout/activity_main布局。
一个简易版的简单应用:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:recyclerview-v7:24.2.1'
testCompile 'junit:junit:4.12'
}
新建一个News类
public class News {
private String title;//新闻标题
private String content;//新闻内容
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
新建布局文件
news_content_frag.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/visibility_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="invisible">
<TextView
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp"
android:textSize="20sp"/>
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="#000"
/>
<TextView
android:id="@+id/news_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="15dp"
android:textSize="18sp"/>
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="#000"/>
</RelativeLayout>
新建一个NewsContentFragment类
public class NewsContentFragment extends Fragment {
private View view;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
savedInstanceState) {
view = inflater.inflate(R.layout.news_content_frag,container,false);
return view;
}
public void refresh(String newsTitle,String newsContent){
View visibilityLayout = view.findViewById(R.id.visibility_layout);
visibilityLayout.setVisibility(View.VISIBLE);
TextView newsTitleText = (TextView) view.findViewById(R.id.news_title);
TextView newsContentText = (TextView) view.findViewById(R.id.news_content);
newsTitleText.setText(newsTitle);//刷新新闻的标题
newsContentText.setText(newsContent);//刷新新闻的内容
}
}
如果想在单页模式中使用的话,我们还需再创建一个活动,新建一个NewsContentActivity,并修改new_content.xml
<?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="vertical"
>
<fragment
android:id="@+id/news_content_fragment"
android:name="com.pengpeng.onenumber_04fragmentbestpractice.fragment.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
public class NewsContentActivity extends AppCompatActivity {
//重点******
public static void actionStart(Context context, String newsTitle, String newsContent) {
Intent intent = new Intent(context, NewsContentActivity.class);
intent.putExtra("news_title", newsTitle);
intent.putExtra("news_content", newsContent);
context.startActivity(intent);
}@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_content);
String newsTitle = getIntent().getStringExtra("news_title");//获取传入的新闻标题
String newsContent = getIntent().getStringExtra("news_content");//获取传入的新闻内容
NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager().findFragmentById
(R.id.news_content_fragment);
newsContentFragment.refresh(newsTitle, newsContent);//刷新NewsContentFragment界面
}
创建一个用于显示新闻列表的布局,新建news_title_frag.xml
<?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="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/news_title_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
接下来新建news_item.xml作为RecyclerView的子项的布局
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"//设定当文本内容超出控件宽度时,文本的缩略方式,这里表示在尾部缩略
android:maxLine="true"//单行显示
android:paddingBottom="15dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="15dp"//表示给控件的周围加上补白,不至于让文本内容会紧靠边缘上
android:textSize="18sp"
>
</TextView>
新建一个用于显示新闻列表的碎片
NewsTitleFragment
public class NewsTitleFragment extends Fragment {
private boolean isTwoPane;//是否加载单双页模式
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
savedInstanceState) {
View view = inflater.inflate(R.layout.news_title_frag, container, false);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.news_content_layout) != null) {
isTwoPane = true;//可以找到news_content_layout布局时,为双页模式
} else {
isTwoPane = false;//找不到news_content_layout布局时,为单页模式
}
}
}
修改activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/news_title_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/news_title_fragment"
android:name="com.pengpeng.onenumber_04fragmentbestpractice.fragment.NewsTitleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
在单页模式下,只会加载一个新闻标题的碎片
双页模式:
在res目录下新建layout-sw600dp文件夹,在这个文件夹下新建一个
activity_main.xml文件,代码如下:
<?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/news_title_fragment"
android:name="com.pengpeng.onenumber_04fragmentbestpractice.fragment.NewsTitleFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<FrameLayout
android:id="@+id/news_content_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<fragment
android:id="@+id/news_content_fragment"
android:name="com.pengpeng.onenumber_04fragmentbestpractice.fragment.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</LinearLayout>
双页模式下同时引入了两个碎片并将新闻内容的碎片放在了FrameLayout下,而这个布局的id是news_content_layout因此能够找到这个id的时候就是双页模式,否则就是单页模式
NewsTitleFragment填充数据
public class NewsTitleFragment extends Fragment {
private boolean isTwoPane;//是否加载单双页模式
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
savedInstanceState) {
View view = inflater.inflate(R.layout.news_title_frag, container, false);
RecyclerView newsTitleRecyclerView = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
- newsTitleRecyclerView.setLayoutManager(layoutManager);
NewsAdapter adapter = new NewsAdapter(getNews());
newsTitleRecyclerView.setAdapter(adapter);
return view;
}
/**
* 填充数据
*
* @return
*/
private List<News> getNews() {
List<News> newsList = new ArrayList<News>();
for (int i = 0; i < = 50; i++) {
News news = new News();
news.setTitle("This is news Title" + i);
news.setContent(getRandomLengthContent("This is news content" + i + "."));
newsList.add(news);
}
return newsList;
}
private String getRandomLengthContent(String content) {
Random random = new Random();
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(content);
}
return builder.toString();
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.news_content_layout) != null) {
isTwoPane = true;//可以找到news_content_layout布局时,为双页模式
} else {
isTwoPane = false;//找不到news_content_layout布局时,为单页模式
}
}
class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private List<News> mNewsList;
class ViewHolder extends RecyclerView.ViewHolder {
TextView newsTitleText;
public ViewHolder(View itemView) {
super(itemView);
newsTitleText = (TextView) itemView.findViewById(R.id.news_title);
}
}
public NewsAdapter(List<News> newsList) {
mNewsList = newsList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
News news = mNewsList.get(holder.getAdapterPosition());
if (isTwoPane) {
//如果是双页模式,则刷新NewsContentFragment中的内容
NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager()
.findFragmentById(R.id.news_content_fragment);//activity中用getSupportFragmentManager,其他地方可以用getFragmentManagernewsContentFragment.refresh(news.getTitle(),
news.getContent());
} else {
//如果是单页模式,则直接启动NewsContentActivity
NewsContentActivity.actionStart(getActivity(), news.getTitle(), news.getContent());
}
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
News news = mNewsList.get(position);
holder.newsTitleText.setText(news.getTitle());
}
@Override
public int getItemCount() {
return mNewsList.size();
}
}
}