Android第一行代码读书笔记 - 第四章

====================================

====== 第四章:手机平板要兼顾 — 探究碎片 ======

====================================

4.1 碎片是什么(通过都是在平板中使用)

碎片(fragment)是一种可以嵌入在活动当中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间。碎片和活动是在太像了,同样能包含布局,同样有自己的声明周期。你甚至可以将碎片理解为一个迷你型的活动。

一个活动中引入两个碎片,相当于一个活动力分为左右两个屏幕。

新建一个FragmentTest

创建两个layout的xml文件,一个left一个right。

然后新建一个Fragment的类,继承自fragment,这时候发现有两个fragment供选择,一个是系统内置的android.app.Fragment,一个是support-v4库中的 ,强烈建议使用support-v4的Fragment,因为可以让碎片在所有Android系统版本中保持功能一致性。

另外,我们不需要在build.gradle文件中添加support-v4库的依赖,因为build.gradle文件中已经添加了appcompat-v7库的依赖,而这个库会将support-v4库也一起引入进来。

<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:layout_width="0dp"

android:layout_height="match_parent"

android:id="@+id/left_fragment"

android:layout_weight="1"

android:name="com.example.fragmenttest.LeftFragment"/>

<fragment

android:layout_width="0dp"

android:layout_height="match_parent"

android:id="@+id/right_fragment"

android:layout_weight="1"

android:name="com.example.fragmenttest.RightFragment"/>

</LinearLayout>

可以看出,我们使用了<fragment>标签在布局中添加碎片,,只不过这里需要通过android:name属性来显性指明要添加的碎片类型(注意这里一定要将类的包名也加上)

4.2.2 动态添加碎片

碎片的真正强大之处在于,它可以在程序运行事动态的添加到活动当中,根据实际情况动态的添加碎片,您就可以将程序界面定制更加多样化。

新增一个another_right_fragment.xml,

动态添加碎片主要分为5步:

1、创建待添加的碎片实例

2、获取FragmentManager,在活动中可以直接通过调用getSupportFragmentManger()方法得到。

3、开启一个事物,通过调用beginTransaction()方法开启

4、向容器内添加或替换碎片,一般使用replace()方法实现,需要传入容器的id和待添加的碎片实例

5、提交事务,调用commit()方法来完成。

4.2.3 在碎片中模拟返回栈

FragmentTransaction中提供了一个addToBackStack()方法,可以用于将一个事物添加到返回栈中。修改MainActivity中的代码。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private void replaceFragment(Fragment fragment) {

FragmentMananger fragmengManager = getSupportFragmentManager();

FragmentTransaction transaction = fragmentManager.beginTransaction();

transaction.replace(R.id.right_layout, fragment);

transaction.addToBackStack(null);

transaction.commit();

}

}

addBackToStack(null)可以接受一个名字用于描述返回栈的状态,一般传入null即可。

4.2.4 碎片和活动之间进行通信

虽然现在碎片是嵌入到活动中了,但是实际上他们的关系并没有那么亲密。你看到,碎片和活动都是各自存在于独立的类中的。

为了方便碎片和活动之间进行通讯,FragmentManager提供了一个类似于findViewById()的方法,专门用于从布局文件中获取碎片的实例。如下:

RightFragment rightFragment = (RightFragment)getFragmentManager().findFragmentById(R.id.right_fragment);

那么,如何在碎片里调用活动呢?在每个碎片中都可以通过getActivity()方法来得到和当前碎片相关联的活动实例,如下:

MainActivity activity = (MainActivity) getActivity();

getActivity()获取到的本身就是一个context对象。

那么,碎片与碎片之间如何通讯呢?碎片可以得到与它关联的活动,通过活动再获取另一个碎片的实例即可。

4.3 碎片的声明周期

和活动一样,碎片也有声明周期,而且它的声明周期与活动的生命周期实在是太像了。

4.3.1 碎片的状态和回调

活动有:运行状态、暂停状态、停止状态和销毁状态四种。

以下是碎片的声明周期:

1、运行状态:

当一个碎片是可见的,并且它所关联的活动正处于运行状态时,该碎片也处于运行转台。

2、暂停状态:

当一个活动进入暂停状态(由于另一个未占满屏幕的活动被添加到了栈顶),与它相关联的可见碎片就会进入暂停状态

3、停止状态:

当一个活动进入停止状态,与它关联的碎片就会进入停止状态。或者通过调用FragmentTransaction的remove()、replace()方法将碎片从活动中移除,但如果在事物提交之前调用addToBackStack()方法,这时的碎片也会进入到停止状态。总的来说,进入停止状态的碎片对于用户来说是完全不可见的,有可能会被系统回收。

4、销毁状态:

碎片依附于活动而存在,当活动被销毁时,与他相关联的碎片就会进入到销毁状态。或者通过调用FragmentTransaction的remove()、replace()方法将碎片从活动中移除,但是在事物提交之前没有调用addBackToStack()方法,这时的碎片也会进入到销毁状态。

Fragment类中也提供了一系列的毁掉方法。以覆盖碎片生命周期的每个环节。其中,活动中有的回调方法,碎片中几乎都有,不过碎片还提供了一些附加的会调方法,重点看一下这几个会调:

onAttach():当碎片和活动建立关联的时候调用

onCreateView():为碎片创建视图(加载布局)时调用

onActivityCreate():确保与碎片相关联的活动一定已经创建完毕时调用。

onDestroyView():当与碎片关联的视图被移除的时候调用

onDetach():当碎片和活动解除关联的时候调用。

具体的碎片声明周期看书本图片:page153

添加一个碎片 —> onAttach() —> onCreate() —> onCreateView() —> onStart() —> onResume() —> 碎片已激活 —> onPause() —> onStop() —> onDestroy() —> onDetach() —> 碎片已销毁

4.3.2 体验一下碎片的声明周期

修改RightFragment中的代码

public class RightFragment extends Fragment {

public statci final String TAG = “RightFragment”;

@Override

public void onAttach(Context context) {

super.onAttach(context);

Log.d(TAG, “onAttach”);

}

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

}

@Override

public View onCreateView(LayoutInflater inflater, VIewGroup container, Bundle savedInstanceState) {

Log.d(TAG, “onCreateView”);

View view = inflater.inflater(R.layout.right_fragment, container, false);

return view;

}

@Override

public void onActivityCreated(Bundle savedInstanceState) {

super.onActivityCreate(savedInstanceState);

}

@Override

public void onStart() {

super.onStart();

}

@Overrde

public void onResume() {

super.onResume();

}

@Override

public void onPause() {

super.onPause();

}

@Override

public void onStop() {

super.onStop();

}

@Overrde

public void onDestroyView() {

super.onDestroyVew();

}

@Override

public void onDestroy() {

super.onDestroy();

}

@Override

public voide onDetach() {

super.onDetach();

}

}

顺便,在碎片中,你也可以通过onSaveInstanceState()方法来保存数据,因为在进入停止状态的时候碎片有可能在系统内存不足的时候被回收。保存下来的数据可以在onCreate() onCreateView() onActivityCreate() 这三个方法中重新得到,因为这三个方法中都有Bundle类型的savedInstanceState参数。

4.4 动态加载布局的技巧

动态添加碎片的功能毕竟只是一个布局文件中进行添加和替换操作。如果程序能够根据设备的分辨率或屏幕大小来运行时加载某个布局,那我们可发挥的空间就更多了。

4.4.1

使用平板电脑经常会发现分左右两页,那么如何才能在运行时判断程序应该是使用双页模式还是使用单页模式呢?这就需要借助限定符(Qualifiers)来实现。

创建两个activity_main.xml的文件。一个只有一个fragment占满屏幕,一个有两个fragment,分为左右屏。

其中,放入layout文件夹中的

另一个放在layout_large文件夹中,

其中,large就是一个限定符,那些屏幕被认为是large的设备就会自动加载layout_large文件夹下的布局,小屏幕的设备则会加载layout文件夹下的布局。

Android中常见的限定符如下所示:(参照Page159)

屏幕特征 限定符 描述

大小 small 提供给小屏幕设备的资源

normal 提供给中等屏幕设备的资源

large 提供给大屏幕设备的资源

xlarge 提供给超大屏幕设备的资源

分辨率 ldpi 提供给低分辨率设备的资源(120dpi以下)

mdpi 提供给中等分辨率设备的资源(120dpi ~ 160dpi)

hdpi 提供给高分辨率设备的资源(160dpi ~ 240dpi)

xhdpi 提供给超高分辨率设备的资源(240dpi ~ 320dpi)

xxhdpi 提供给超超高分辨率设备的资源(320dpi ~ 480dpi)

方向 land 提供给横屏设备的资源

port 提供给竖屏设备的资源

4.4.2 使用最小宽度限定符

新的问题出现了,large到底是指多大呢?

最小宽度限定符允许我们对屏幕的宽度指定一个最小值(以dp为单位),然后以这个最小值为临界点。

在res目录下新建layout-sw600dp,然后在里面新建activity_main.xml布局,这意味着,当程序运行在屏幕宽度大于600dp的设备上时,会加载layout-sw600dp/activity_main布局。

4.5 碎片的最佳实践 —> 一个简易的新闻应用

常见一个新的应用FragmentBestPractice。

1、在app/build.gradle中添加recyclerView的依赖库

2、新增一个News类,作为model

public class News {

private String title; // 标题

private String content; // 内容

public String getTitle() {

return title;

}

public String getContent() {

return content;

}

public void setTitle(String title) {

this.title = title;

}

public void setContent(String content) {

this.content = content;

}

3、新建布局,用于新闻内容的布局 news_content_frag.xml

<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:layout_gravity=“center”

android:padding=“10dp”

android:textSize=“20sp” />

<View

android:layout_width=“match_parent”

android:layout_height=“1dp”

android:background=“#000” />

<TextView

android:id=“@+id/new_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>

4、新建一个NewsContentFragment类,继承自Fragment,代码如下

public class NewsContentFragment extends Fragment {

private VIew view;

@Override

public View onCreateVIew(LayoutInflater infalter, ViewGroup container, Bundle savedInstanceState) {

View view = inflater.inflater(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 newsTitltText = (TextView) view.findViewById(R.id.news_title);

TextView newsContentText = (TextView) view.findViewById(R.id.new_content);

newsTitleText.setText(newsTitle); // 刷新新闻的标题

newsContentText.setText(newsContent); // 刷新新闻的内容

}

}

5、上面我们创建的碎片和布局都是在双页模式下使用的,现在需要再创建一个活动,右键com.example.fragmentbestpractice包 —> New —> Activity —> Empty Activity,新建一个NewsContentActivity,并将布局名指定为news_content(新增一个news_content.xml文件):

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:orientation=“vertical”

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<fragment

android:id=“@+id/news_content_fragment”

android:name=“com.example.fragmentbestpractice.NewsContentFragment”

android:layout_width=“match_parent”

android:layotu_height=“match_parent” />

</LinearLayout>

6、修改NewsContentFragment的代码

public class NewsContentFragment extends AppCompataActivity {

public static void actionStart(Context context, String newsTitle, String newsContent) {

Intent intend = 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().getStingExtra(“news_content”); // 获取传入的新闻内容

NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmengManager().findFragmentById(R.id.news_content_fragnent);

newsContentFragment.refresh(newsTitle, newsContent); // 刷新NewsContent-Fragment界面

}

}

在onCreate方法中,我们通过Intent获取到了传入的新闻标题和新闻内容

然后通过FragmentManager的findFragmentById()方法得到了NewsContentFragment的实例。接着调用它的refresh()方法。

actionStart()方法,作用忘记了。。。回看一下2.6.3小节。

7、还需要创建一个用于显示新闻列表的布局,新建news_title_frag.xml

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:orientation=“vertical”

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<android.support.v7.widget.RecyclerView

android:id=“@+id/news_title_recycler_view”

android:layout_width=“match_parent”

android:layout_height=“match_parent” />

</LinearLayout>

8、新建news_item.xml作为RecyclerView子项的布局

<TextView xmlns:android=“http://schemas.android.com/apk/res/android

android:id=“@+id/new_title”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:singleLine=“true”

android:ellipsize=“end”

android:textSize=“18sp”

android:paddingLeft=“10dp”

android:paddingRight=“10dp”

android:paddingTop=“15dp”

android:paddingBottom=“15dp” />

android:padding的意思表示给控件的周围加上补白,这样不至于让文本内容靠在边缘上

android:singleLine设置为true表示让TextView只能单行显示

Android:ellipsize用于设定当文本内容超出控件宽度时,文本的缩略方式,这里的end表示在尾部进行缩略。

9、用于展示新闻列表的地方。新建NewsTitleFragment作为展示新闻列表的碎片。

public class NewsTitleFragment extends Fragment {

private boolean isTwoPane;

@Override

public View onCreateView(LayoutInflater inflater, VIewGroup container, Bundle savedInstanceState) {

View view = inflater.inflater(R.id.news_title_frag, container, false);

return view;

}

@Override

public void onActivityCreate(Bundle savedInstanceState) {

super.onActivityCreate(savedInstanceState);

if (getActivity().findViewById(R.id.news_content_layout) != null ) {

isTwoPane = true; // 可以找到news_content_layout布局时,为双页模式

} else {

isTwoPane = false; // 找不到news_content_layout布局时,为单页模式

}

}

}

getActivity()方法用户在fragment中获取关联的activity

10、修改activity_main.xml文件

<FramLayout 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.example.fragmentbestparctice.NewsTitleFragment”

android:layout_width=“match_parent”

android:layout_height=“match_parent” />

</FrameLayout>

上面代码中,在单页模式下,只会加载一个新闻标题的碎片。

11、然后新建layout-sw600dp文件夹,在这个文件及中新建一个activity_main.xml文件,代码如下

<LinearLayout xmlns:android=“http://schemas.android.com/apl/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.example.fragmentbestpractice.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.example.fragmentbestpractice.NewsContentFragment”

android:layout_width=“match_parent”

android:layout_height=“math_parent” />

</FrameLayout>

</LinearLayout>

可以看到,我们在双页模式下引入了两个碎片,并将新闻内容碎片放在FrameLayout布局下,而这个布局的id正式news_content_layout,因此,能够找到这个id的时候就是双页模式。

12、在NewsTitleFragment中通过RecyclerView将新闻列表展示出来,我们在NewsTitleFragment中新建一个NewsAdapter来作为RecyclerView的适配器。

public class NewsTitltFragment extends Fragment {

private boolean isTwoPane;

class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {

private List<News> mNewsList;

class ViewHolder extends RecyclerVIew.ViewHolder {

TextView newsTitleText;

public ViewHolder(View view) {

super(view);

newsTitleText = (TextView) view.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()).inflater(R.layout.news_item, parent, false);

final ViewHolder holder = new ViewHolder(view);

view.setOnClickLinster(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);

newsContentFragment.refresh(news.getTitle(), news.getContent());

} else {

// 如果是单页模式,则直接启动NewsContentActivity

NewsContentActivity.actionStart(getActigity(), 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();

}

}

需要注意的是,之前我们是将适配器写成一个独立的类,其实也可以写成内部类的。这里写成内部类的好处就是可以直接访问NewsTitleFragment的变量,比如isTwoPane变量;

13、最后的工作,向RecyclerView中填充数据。修改NewsTitleFragment中的代码

@Override

public View onCreateView (LayoutInflater inflater, VIewGroup container, 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;

}

private List<News> getNews() {

List<News> newsList = new ArrayList<>();

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 StrignBuilder();

for (int i = 0; i < length; i++ ) {

builder.append(content);

}

return builder.toString();

}

总结:到本章为止,已经学习完了AndroidUI相关的重要知识点。但是只是涉及了Android四大组件的第一个组件活动。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容