第004天:APP在平板上的UI布局设计

        当今是移动设备发展非常迅速的时代,不仅手机已经成为了生活必需品,就连平板电脑也变 得越来越普及。平板电脑和手机最大的区别就在于屏幕的大小,一般手机屏幕的大小会在3英寸 到6英寸之间,而一般平板电脑屏幕的大小会在7英寸到10英寸之间。屏幕大小差距过大有可 能会让同样的界面在视觉效果上有较大的差异,比如一些界面在手机上看起来非常美观,但在平 板电脑上看起来就可能会有控件被过分拉长、元素之间空隙过大等情况。

        作为一名专业的Android开发人员,能够同时兼顾手机和平板的开发是我们必须做到的事情。 Android3.0版本开始引入了碎片的概念,它可以让界面在平板上更好地展示,下面我们就来一 起学习一下。

4.1碎片是什么

        碎片(Fragment)是一种可以嵌入在活动当中的UI片段,它能让程序更加合理和充分地利 用大屏幕的空间,因而在平板上应用得非常广泛。虽然碎片对你来说应该是个全新的概念,但我 相信你学习起来应该毫不费力,因为它和活动实在是太像了,同样都能包含布局,同样都有自己 的生命周期。你甚至可以将碎片理解成一个迷你型的活动,虽然这个迷你型的活动有可能和普通 的活动是一样大的。

        那么究竟要如何使用碎片才能充分地利用平板屏幕的空间呢?想象我们正在开发一个新闻 应用,其中一个界面使用RecyclerView展示了一组新闻的标题,当点击了其中一个标题时,就打 开另一个界面显示新闻的详细内容。如果是在手机中设计,我们可以将新闻标题列表放在一个活 动中,将新闻的详细内容放在另一个活动中,如图4.1所示。

可是如果在平板上也这么设计,那么新闻标题列表将会被拉长至填充满整个平板的屏幕,而 新闻的标题一般都不会太长,这样将会导致界面上有大量的空白区域,如图4.2所示。

因此,更好的设计方案是将新闻标题列表界面和新闻详细内容界面分别放在两个碎片中, 然后在同一个活动里引入这两个碎片,这样就可以将屏幕空间充分地利用起来了,如图4.3 所示。

4.2碎片的使用方式

介绍了这么多抽象的东西,也是时候学习一下碎片的具体用法了。你已经知道,碎片通常都 是在平板开发中使用的,因此我们首先要做的就是创建一个平板模拟器。创建模拟器的方法我们 在第1章已经学过了,创建完成后启动平板模拟器,效果如图4.4所示。

好了,准备工作都完成了,接着新建一个FragmentTest项目,然后开始我们的碎片探索之 旅吧。

4.2.1碎片的简单用法

这里我们准备先写一个最简单的碎片示例来练练手,在一个活动当中添加两个碎片,并让这 两个碎片平分活动空间。

新建一个左侧碎片布局left fragment.xml,代码如下所示:

这个布局非常简单,只放置了一个按钮,并让它水平居中显示。然后新建右侧碎片布局 right fragment.xml,代码如下所示:

可以看到,我们将这个布局的背景色设置成了绿色,并放置了一个TextView用于显示一段 文本。

接着新建一个LeftFragment类,并让它继承自Fragmento注意,这里可能会有两个不同包 下的Fragment供你选择,一个是系统内置的android.app.Fragment, 一个是support-v4库中的 android.support.v4.app.Fragmento这里我强烈建议你使用support-v4库中的Fragment,因为它可 以让碎片在所有Android系统版本中保持功能一致性。比如说在Fragment中嵌套使用Fragment, 这个功能是在Android 4.2系统中才开始支持的,如果你使用的是系统内置的Fragment,那么很 遗憾,4.2系统之前的设备运行你的程序就会崩溃。而使用supportv4库中的Fragment就不会出 现这个问题,只要你保证使用的是最新的support-v4库就可以了。另外,我们并不需要在 build.gradle文件中添加support-v4库的依赖,因为build.gradle文件中已经添加了 appcompat-v7 库的依赖,而这个库会将support-v4库也一起引入进来。

现在编写一下LeftFragment中的代码,如下所示:

这里仅仅是重写了 FragmentonCeateView()方法,然后在这个方法中通过Layoutlnflater inflate()方法将刚才定义的left fragment布局动态加载进来,整个方法简单明了。接着我们 用同样的方法再新建一个RightFragment,代码如下所示:

基本上代码都是相同的,相信已经没有必要再做什么解释了。接下来修改activity_main.xml 中的代码,如下所示:

可以看到,我们使用了<fragment>标签在布局中添加碎片,其中指定的大多数属性都是你 熟悉的,只不过这里还需要通过android:name属性来显式指明要添加的碎片类名,注意一定要 将类的包名也加上。

这样最简单的碎片示例就已经写好了,现在运行一下程序,效果如图4.5所示。

正如我们所期待的一样,两个碎片平分了整个活动的布局。不过这个例子实在是太简单了, 在真正的项目中很难有什么实际的作用,因此我们马上来看一看,关于碎片更加高级的使用技巧。

4.2.2动态添加碎片

在上一节当中,你已经学会了在布局文件中添加碎片的方法,不过碎片真正的强大之处在于, 它可以在程序运行时动态地添加到活动当中。根据具体情况来动态地添加碎片,你就可以将程序 界面定制得更加多样化。

我们还是在上一节代码的基础上继续完善,新建another_right_fi*agment.xml,代码如下所示:

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

android:orientation="vertical"

android:background="#ffff00"

android:layout_width="match_parent"

android:layout_height="match_parent">

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content" android layout_gravity=''center_horizontal" android:textSize="20sp"

android:text="This is another right fragment"

/>

</LinearLayout>

这个布局文件的代码和right_fi*agment.xml中的代码基本相同,只是将背景色改成了黄色,并 将显示的文字改了改。然后新建AnotherRightFragment作为另一个右侧碎片,代码如下所示:

public class AnotherRightFragment extends Fragment {

(aOverride

public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedlnstanceState) {

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

return view;

}

}

代码同样非常简单,在onCreateView()方法中加载了刚刚创建的another_right_firagment布 局。这样我们就准备好了另一个碎片,接下来看一下如何将它动态地添加到活动当中。修改 activity_main.xml,代码如下所示:

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

android:orientation="horizontal"

android:layout_width="match_pa rent" android:layout_height="match_parent" >

<fragment

android:id="@+id/left_f ragment"

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

android: layout_width=,,0dp"

android:layout_height="match_parent"

android:layout_weight="l" />

<FrameLayout

android:id="@+id/right_layout"

android :layout__width="Odp"

android:layout_height="match_parent” android:layout_weight="l" >

</FrameLayout>

</LinearLayout>

可以看到,现在将右侧碎片替换成了一个FrameLayout中,还记得这个布局吗?在上一章中 我们学过,这是Android中最简单的一种布局,所有的控件默认都会摆放在布局的左上角。由于 这里仅需要在布局里放入一个碎片,不需要任何定位,因此非常适合使用FrameLayout

下面我们将在代码中向FrameLayout里添加内容,从而实现动态添加碎片的功能。修改 MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

(QOverride

protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); 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.getldO) { case R.id.button:

replaceFragment(new AnotherRightFragment()); break;

default:

break;

}

}

private void replaceFragment(Fragment fragment) {

FragmentManager fragmentManager = getSupportFragmentManager();

FragmentTransaction transaction = fragmentManager.beginTransaction(): transaction.replace(R.id.right_layoutf fragment);

transaction.commit();

}

}

可以看到,首先我们给左侧碎片中的按钮注册了一个点击事件,然后调用 replaceFragmentO方法动态添加了 RightFragment这个碎片。当点击左侧碎片中的按钮时,又 会调用replaceFragmentO方 法将右侧碎片替换成 AnotherRightFragment o结 合 replaceFragmentO方法中的代码可以看出,动态添加碎片主要分为5步。

  1. 创建待添加的碎片实例。
  2. 获取 FragmentManager,在活动中可以直接通过调用 getSupportFragmentManager()方 法得到。
  3. 开启一个事务,通过调用beginTransactionO方法开启。
  4. 向容器内添加或替换碎片,一般使用replace ()方法实现,需要传入容器的id和待添加 的碎片实例。
  5. 提交事务,调用commit ()方法来完成。

这样就完成了在活动中动态添加碎片的功能,重新运行程序,可以看到和之前相同的界面, 然后点击一下按钮,效果如图4.6所示。

 

4.6动态添加碎片的效果

4.2.3在碎片中模拟返回栈

在上一小节中,我们成功实现了向活动中动态添加碎片的功能,不过你尝试一下就会发现, 通过点击按钮添加了一个碎片之后,这时按下Back键程序就会直接退出。如果这里我们想模仿 类似于返回栈的效果,按下Back键可以回到上一个碎片,该如何实现呢?

其实很简单,FragmentTransaction中提供了一个addToBackStack()方法,可以用于将一个 事务添加到返回栈中,修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private void replaceFragment(Fragment fragment) { FragmentManager fragmentManager = getSupportFragmentManagerf); Fragment!ransaction transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.rightlayout, fragment);

transaction.addToBackStack(null); transaction.commit();

}

}

这里我们在事务提交之前调用了 FragmentTransactionaddToBackStack()方法,它可以接 收一个名字用于描述返回栈的状态,一般传入null即可。现在重新运行程序,并点击按钮将 AnotherRightFragment添加到活动中,然后按下Back键,你会发现程序并没有退出,而是回到了 RightFragment界面,继续按下Back键,RightFragment界面也会消失,再次按下Back键,程序 才会退出。

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

虽然碎片都是嵌入在活动中显示的,可是实际上它们的关系并没有那么亲密。你可以看出, 碎片和活动都是各自存在于一个独立的类当中的,它们之间并没有那么明显的方式来直接进行通 信。如果想要在活动中调用碎片里的方法,或者在碎片中调用活动里的方法,应该如何实现呢?

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

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

调用FragmentManagerfindFragmentByld()方法,可以在活动中得到相应碎片的实例, 然后就能轻松地调用碎片里的方法了。

掌握了如何在活动中调用碎片里的方法,那在碎片中又该怎样调用活动里的方法呢?其实这 就更简单了,在每个碎片中都可以通过调用getActivityO方法来得到和当前碎片相关联的活 动实例,代码如下所示:

MainActivity activity = (MainActivity) getActivity();

有了活动实例之后,在碎片中调用活动里的方法就变得轻而易举了。另外当碎片中需要使用 Context对象时,也可以使用getActivityO方法,因为获取到的活动本身就是一个Context 对象。

这时不知道你心中会不会产生一个疑问:既然碎片和活动之间的通信问题已经解决了,那么 碎片和碎片之间可不可以进行通信呢?

说实在的,这个问题并没有看上去那么复杂,它的基本思路非常简单,首先在一个碎片中可 以得到与它相关联的活动,然后再通过这个活动去获取另外一个碎片的实例,这样也就实现了不 同碎片之间的通信功能,因此这里我们的答案是肯定的。

4.3碎片的生命周期

和活动一样,碎片也有自己的生命周期,并且它和活动的生命周期实在是太像了,我相信你 很快就能学会,下面我们马上就来看一下。

4.3.1 碎片的状态和回调

还记得每个活动在其生命周期内可能会有哪几种状态吗?没错,一共有运行状态、暂停状态、 停止状态和销毁状态这4种。类似地,每个碎片在其生命周期内也可能会经历这几种状态,只不 过在一些细小的地方会有部分区别。

  1. 运行状态

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

  1. 暂停状态

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

  1. 停止状态

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

  1. 销毁状态

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

结合之前的活动状态,相信你理解起来应该毫不费力吧。同样地,Fragment类中也提供了 一系列的回调方法,以覆盖碎片生命周期的每个环节。其中,活动中有的回调方法,碎片中几乎 都有,不过碎片还提供了一些附加的回调方法,那我们就重点看一下这几个回调。

碎片完整的生命周期示意图可参考图4.7,图片源自Android官网。

onCreate()

:onCreateViewO;* jonActivityCreated(^

onStartf )

当碎片被添加 到返回栈,然 后被移除/替换

onPausef)

onStop()

onDestroyViewO

onDestroy():

 

4.7碎片的生命周期

4.3.2体验碎片的生命周期

为了让你能够更加直观地体验碎片的生命周期,我们还是通过一个例子来实践一下。例子很 简单,仍然是在FragmentTest项目的基础上改动的。

修改RightFragment中的代码,如下所示:

public class RightFragment extends Fragment {

public static final String TAG = "RightFragment";

©Override

public void onAttach(Context context) { super.onAttach(context); Log.d(TAG* “onAttach”);

}

©Override

public void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); Log.d(TAG, ,,onCreateu);

}

(aOverride

public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedlnstanceState) (

Log.d(TAG, "onCreateView");

View view = inflater.inflate(R.layout.rightfragment, container, false); return view;

}

©Override

public void onActivityCreated(Bundle savedlnstanceState) { super.onActivityC reated(savedlnstanceState); Log.d(TAGr "onActivityCreated");

}

©Override

public void onStartO { super.onStartO; Log.d(TAG, ,,onStart,*);

}

©Override

public void onResume() (

super.onResumeO; Log.d(TAGf "onResume");

}

©Override

public void onPause() {

super.onPause(); Log.d(TAG, "onPause");

}

©Override

public void onStopO { super.onStopO; Log.d(TAG, ,,onStopu);

}

©Override public void onDestroyView() { super.onDestroyView(); Log.d(TAGf "onDestroyView");

}

^Override public void onDestroyO ( super.onDestroyO; Log.d(TAG, "onDestroy");

©Override

public void onDetachO { super.onDetachO; Log.d(TAG, "onDetach");

}

}

我们在RightFragment中的每一个回调方法里都加入了打印日志的代码,然后重新运行程序, 这时观察logcat中的打印信息,如图4.8所示。

MetbcweU ^RightFragment G)

com. example, fragmenttest D/RightFragment: onAttach

com. example, fragmenttest D/RightFragment: onCreate

com. fragmenttest D/RightFragment: onCreateViev

com. ex2UEple. freigmenttest D/RightFragment: onActivityCreated

com. example, fragmenttest D/RightFragment: onStart

com. exewle. fragTOnttest D/RightFragment: onResume

4.8启动程序时的打印日志

可以看到,当RightFragment第一次被加载到屏幕上时,会依次执行onAttach(), onCreate() v onCreateView() v onActivityCreated(), onStart()onResume()方法。然 后点击LeftFragment中的按钮,此时打印信息如图4.9所示。

com. exan5)le. fragmenttest D/RightFragment: onPause

com. exan^ple. fragment test D/RightFragment: onStop

com. example, fragmenttest D/RightFragment: onDestroyView

4.9 替换成AnotherRightFragment时的打印日志

由于 AnotherRightFragment 替换了 RightFragment,此时的 RightFragment 进入了停止状态, 因此onPause()v onStop()onDestroyView()方法会得到执行。当然如果在替换的时候没有 调用addToBackStack()方法,此时的RightFragment就会进入销毁状态,onDestroyOonDetachO方法就会得到执行°

接着按下Back键,RightFragment会重新回到屏幕,打印信息如图4.10所示。

fVerbose RightFragment

com. example, fragmenttest D/RightFragment: onActivityCreated

com. example, fragmenttest D/RightFragment: onStart

com. example, fragmenttest D/RightFragment: onResume

4.10返回RightFragment时的打印日志

由于 RightFragment 重新回到了运行状态,因此 onActivityCeated()onStart()onResumeO方法会得到执行。注意此时onCreateOonCreateView()方法并不会执行,因为 我们借助了 addToBackStack()方法使得RightFragment和它的视图并没有销毁。

再次按下Back键退出程序,打印信息如图4.11所示。

com. example, fragmenttest D/RightFragment: onPause

com. example, fragmenttest D/RightFragment: onStop

com. example, fragmenttest D/RightFragment: onDestroyView

com. example, fragmenttest D/RightFragment: onDestroy com. example, fragmenttest D/RightFragment: onDetach

4.11退出程序时的打印日志

依次会执行 onPauseO, onStop(), onDestroyView(), onDestroy ()onDetach()方 法,最终将活动和碎片一起销毁。这样碎片完整的生命周期你也体验了一遍,是不是理解得更加 深刻了?

另外值得一提的是,在碎片中你也是可以通过onSaveInstanceState()方法来保存数据的, 因为进入停止状态的碎片有可能在系统内存不足的时候被回收。保存下来的数据在onCreateOonCreateView()or)ActivityCeated()3个方法中你都可以重新得到,它们都含有一个 Bundle类型的savedlnstanceState参数。具体的代码我就不在这里给出了,如果你忘记了该 如何编写,可以参考245小节。

4.4动态加载布局的技巧

虽然动态添加碎片的功能很强大,可以解决很多实际开发中的问题,但是它毕竟只是在一个 布局文件中进行一些添加和替换操作。如果程序能够根据设备的分辨率或屏幕大小在运行时来决 定加载哪个布局,那我们可发挥的空间就更多了。因此本节我们就来探讨一下Android中动态加 载布局的技巧。

4.4.1使用限定符

如果你经常使用平板电脑,应该会发现现在很多的平板应用都采用的是双页模式(程序会在 左侧的面板上显示一个包含子项的列表,在右侧的面板上显示内容),因为平板电脑的屏幕足够 大,完全可以同时显示下两页的内容,但手机的屏幕一次就只能显示一页的内容,因此两个页面 需要分开显示。

那么怎样才能在运行时判断程序应该是使用双页模式还是单页模式呢?这就需要借助限定 符(Qualifiers )来实现了。下面我们通过一个例子来学习一下它的用法,修改FragmentTest项目 中的activity_main.xml文件,代码如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android prientationihorizontal" android:layout_width="match_paent" android:layout_height="match_parent" >

<fragment

and roid: id=,,@+id/left_f ragment"

android: name=" com. example .fragmenttest. LeftF ragment"

android:layout_width="match_parent"

android:layout_height="match_parent"/>

</LinearLayout>

这里将多余的代码都删掉,只留下一个左侧碎片,并让它充满整个父布局。接着在res目录 下新建1 ayout-large文件夹,在这个文件夹下新建一个布局,也叫作activity_main.xml,代码如下 所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android: layout_height="inatch_parent">

<fragment

android:id="@+id/left_fragment"

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

and roid :tayout_width=,,0dp"

android:layout_height="match_parent"

android:layout_weight="1" />

<fragment

and roid: id="<a+id/rightf ragment"

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

android:layout_width="0dp"

android:layout_height="match_parent"

android:layout_weight="3" />

</LinearLayout>

可以看到,layout/activity main布局只包含了一个碎片,即单页模式,而layout-large/ activity_main布局包含了两个碎片,即双页模式。其中large就是一个限定符,那些屏幕被认为 是large的设备就会自动加载layout-large文件夹下的布局,而小屏幕的设备则还是会加载layout 文件夹下的布局。

然后将MainActivityreplaceFragment ()方法里的代码注释掉,并在平板模拟器上重新 运行程序,效果如图4.12所示。

 

4.12双页模式运行效果

再启动一个手机模拟器,并在这个模拟器上重新运行程序,效果如图4.13所示。

FragmentTest

 

4.13单页模式运行效果

这样我们就实现了在程序运行时动态加载布局的功能。

Android中一些常见的限定符可以参考下表。

屏幕特征

限定符

描 述

small

提供给小屏幕设备的资源

大小

normal

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

large

提供给大屏幕设备的资源

xlarge

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

(续)

屏幕特征

限定符

描 述

1dpi

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

mdpi

提供给中等分辨率设备的资源(120dpi160dpi)

分辨率

hdpi

提供给高分辨率设备的资源(160dpi240dpi)

xhdpi

提供给超高分辨率设备的资源(240dpi320dpi)

xxhdpi

提供给超超高分辨率设备的资源(320dpi480dpi)

方向

land

提供给横屏设备的资源

port

提供给竖屏设备的资源

4.4.2使用最小宽度限定符

在上一小节中我们使用large限定符成功解决了单页双页的判断问题,不过很快又有一个 新的问题出现large到底是指多大呢?有的时候我们希望可以更加灵活地为不同设备加载布 局,不管它们是不是被系统认定为large,这时就可以使用最小宽度限定符(Smallest-width Qualifier) 了。

最小宽度限定符允许我们对屏幕的宽度指定一个最小值(以dp为单位),然后以这个最小值 为临界点,屏幕宽度大于这个值的设备就加载一个布局,屏幕宽度小于这个值的设备就加载另一 个布局。

res目录下新建layout-sw600dp文件夹,然后在这个文件夹下新建activity main.xml布局, 代码如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android: layout_width=,,match_pa rent" android: layout_height="match_parent,,>

<fragment

android:id="@+id/left_fragment"

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

android:layout_width="0dp"

android:layout_height="match_parent"

android:layout_weight=,,l" />

<fragment

and roid: id="(a+id/rightf ragment"

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

android:layout_width="0dp"

android:layout_height="match_pa rent" android:layout_weight=,,3n />

</LinearLayout>

这就意味着,当程序运行在屏幕宽度大于600dp的设备上时,会加载layout-sw600dp/activity_main 布局,当程序运行在屏幕宽度小于600dp的设备上时,则仍然加载默认的layout/activity_main布局。

4.5碎片的最佳实践 个简易版的新闻应用

现在你已经将关于碎片的重要知识点都掌握得差不多了,不过在灵活运用方面可能还有些欠 缺,因此下面该进入我们本章的最佳实践环节了。

前面有提到过,碎片很多时候都是在平板开发当中使用的,主要是为了解决屏幕空间不能充 分利用的问题。那是不是就表明,我们开发的程序都需要提供一个手机版和一个Pad版呢?确实 有不少公司都是这么做的,但是这样会浪费很多的人力物力。因为维护两个版本的代码成本很高, 每当增加什么新功能时,需要在两份代码里各写一遍,每当发现一个bug时,需要在两份代码里 各修改一次。因此今天我们最佳实践的内容就是,教你如何编写同时兼容手机和平板的应用程序。

还记得在本章开始的时候提到过的一个新闻应用吗?现在我们就将运用本章中所学的知识 来编写一个简易版的新闻应用,并且要求它是可以同时兼容手机和平板的。新建好一个 FragmentBestPractice项目,然后开始动手吧!

由于待会在编写新闻列表时会使用到RecyclerView,因此首先需要在app/build.gradle当中添 加依赖库,如下所示:

dependencies {

compile fileTree(dir: 'libs'f include: [jar'])

compile 'com.android.support:appcompat-v7:24.2.1'

testCompile 'junit:junit:4.12'

compile 1 com.android.support:recyclerview-v7:24.2.1'

}

接下来我们要准备好一个新闻的实体类,新建类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类的代码还是比较简单的,title字段表示新闻标题,content字段表示新闻内容。 接着新建布局文件news_content_frag.xml,用于作为新闻内容的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android: ■tayout_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="ldp"

android: background=,,#000" />

<TextView

and roid: id="(a+id/news_content"

android:layout_width="match_parent"

android:layout_height="0dp"

android:layout_weight="l"

android:padding="15dp" android:textSize="18sp" />

</LinearLayout>

<View

android:layout_width="ldp" android:layout_height="match_parent" android:layout_alignParentLeft="true" android:background="#000" />

</RelativeLayout>

新闻内容的布局主要可以分为两个部分,头部部分显示新闻标题,正文部分显示新闻内容, 中间使用一条细线分隔开。这里的细线是利用View来实现的,将View的宽或高设置为ldp,再 通过background属性给细线设置一下颜色就可以了。这里我们把细线设置成黑色。

然后再新建一个NewsContentFragment类,继承自Fragment,代码如下所示:

public class NewsContentFragment extends Fragment (

private View view;

(QOverride

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedlnstanceState) (

view = inflater.inflate(R.layout.newscontentfrag, container, false); return view;

}

public void refresh(String newsTitle, String newsContent) {

View visibilityLayout = view.findViewById(R.id.visibilitylayout); visibilityLayout.setvisibility(View.VISIBLE);

Textview newsTitleText = (Textview) view.findViewByld (R.id.newstitle); Textview newsContentText = (Textview) view.findViewById(R.id.newscontent); newsTitleText. setText (newsTitle); // 刷新新闻的标题 newsContentText. setText (newsContent); // 刷新新闻的内容

}

}

首先在onCreateView()方法里加载了我们刚刚创建的news content frag布局,这个没什么 好解释的。接下来又提供了一个refreshO方法,这个方法就是用于将新闻的标题和内容显示在 界面上的。可以看到,这里通过findViewByldO方法分别获取到新闻标题和内容的控件,然后 将方法传递进来的参数设置进去。

这样我们就把新闻内容的碎片和布局都创建好了,但是它们都是在双页模式中使用的,如果 想在单页模式中使用的话,我们还需要再仓U建一个活动。右击com.example.fragmentbestpractice New —>ActivityEmpty Activity ,新建一个 NewsContentActivity ,并将布局名指定成 news content,然后修改news content.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_pa rent" android:layout_height="matchparent">

<fragment

android:id=”@+id/news_content_fagment”

android:name="com.example.fragmentbestpractice.NewsContentFragment" android:layout_width="match_parent"

android :layout_height=,lmatch_parent"

/> ~

v/LineaLayout

这里我们充分发挥了代码的复用性,直接在布局中引入了 NewsContentFragment,这样也 就相当于把news content frag布局的内容自动加了进来。

然后修改NewsContentActivity中的代码,如下所示:

public class NewsContentActivity extends AppCompatActivity {

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

Intent intent = new Intent(context, NewsContentActivity.class);

intent.putExtra("newstitle", newsTitle);

intent.putExtra("news content", newsContent); context.startActivity(intent);

}

@0verride protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.newscontent);

String newsTitle = getlntent?) .getStringExtraf "news title"); // 获取传入的新 闻标题

String newsContent = getlntent().getStringExtra( "news content"); // 获取传入 的新闻内容

NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.newscontentf ragment); newsContentFragment.ref resh(newsTitle, newsContent); //刷新 NewsContentFragment 界面

}

}

可以看到,在onCreateO方法中我们通过Intent获取到了传入的新闻标题和新闻内容,然 后调用 FragmentManager findFragmentById()方法得到了 NewsContentFragment 的实例, 接着调用它的refreshO方法,并将新闻的标题和内容传入,就可以把这些数据显示出来了。注 意这里我们还提供了一个actionStartO方法,还记得它的作用吗?如果忘记的话就再去阅读 一遍2.6.3小节卩巴。

接下来还需要再创建一个用于显示新闻列表的布局,新建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="inatch_parent,,>

<android.support.v7.widget.RecyclerView

android: id="(a+id/news_title_recycler_view"

android:layout_width="matchparent" android:layout_height="match_pa rent" /> ~ ~

</LinearLayout>

这个布局的代码就非常简单了,里面只有一个用于显示新闻列表的RecyclerViewo既然要用 到RecyclerView,那么就必定少不了子项的布局。新建news item.xml作为RecyclerView子项的 布局,代码如下所示:

<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:singleLine="true"

android:ellipsize="end"

android:textSize="18sp"

android:paddingLeft="10dp"

android:paddingRight="10dp"

android:paddingTop="15dp" android:paddingBottom="15dp" />

子项的布局也非常简单,只有一个Text Vie Wo仔细观察TextView,你会发现其中有几个属 性是我们之前没有学过的。android:padding表示给控件的周围加上补白,这样不至于让文本 内容会紧靠在边缘上。android: singleLine设置为true表示让这个TextView只能单行显示。 android:ellipsize用于设定当文本内容超出控件宽度时,文本的缩略方式,这里指定成end 表示在尾部进行缩略。

既然新闻列表和子项的布局都已经创建好了,那么接下来我们就需要一个用于展示新闻列表 的地方。这里新建NewsTitleFragment作为展示新闻列表的碎片,代码如下所示:

public class NewsTitleFragment extends Fragment {

private boolean isTwoPane;

(QOverride

public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedlnstanceState) {

View view = inflater.inflate(R.layout.newstitlefrag, container, false); return view;

}

(aOverride

public void onActivityCreated(Bundle savedlnstanceState) (

super,onActivityCreated(savedlnstanceState);

if (getActivity().findViewByld(R.id.newscontentlayout) != null) { isTwoPane = true; //可以找到news content layout布局时,为双页模式

} else {

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

} " "

}

}

可以看到,NewsTitleFragment中并没有多少代码,在onCreateView()方法中加载了 news_titleag布局,这个没什么好说的。我们注意看一下onActivityCreated()方法,这个方 法通过在活动中能否找到一个idnews content layoutView来判断当前是双页模式还是单 页模式,因此我们需要让这个idnews content layoutView只在双页模式中才会出现。

那么怎样才能实现这个功能呢?其实并不复杂,只需要借助我们刚刚学过的限定符就可以 了。首先修改activity main.xml中的代码,如下所示:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android: id=,,@+id/news_tifle_layout" android:layout_width="match_parent" android:layout_height=,,match_parent" >

<fragment

and roid: id="(a+id/news_title_f ragment"

android: name=" com. example. f ragmentbestpractice. NewsTitleFragment"

android:layout_width="match_parent"

android: layout_height=,,match_parent"

</FrameLayout>

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

然后新建layoutsw600dp文件夹,在这个文件夹下再新建一个activity_main.xml文件,代码 如下所示:

<LinearLayout xmlns:android=uhttp://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" >

<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="l" />

<FrameLayout

android: id="(a+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=,,match_parent" />

</FrameLayout>

</LinearLayout>

可以看出,在双页模式下我们同时引入了两个碎片,并将新闻内容的碎片放在了一个Frame- Layout布局下,而这个布局的id正是news_content_layout0因此,能够找到这个id的时候就是 双页模式,否则就是单面模式。

现在我们已经将绝大部分的工作都完成了,但还剩下至关重要的一点,就是在NewsTitle- Fragment中通过RecyclerView将新闻列表展示出来。我们在NewsTitleFragment中新建一个 内部类NewsAdapter来作为RecyclerView的适配器,如下所示:

public class NewsTitleFragment 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_tit'le);

}

}

public NewsAdapter(List<News> newsList) { mNewsList = newsList;

}

@0verride

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = Layoutlnflater.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()

.findFragmentByld(R.id.news_content_f ragment); newsContentFragment.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.getTitleO);

}

©Override

public int getItemCount() { return mNewsList.size();

}

}

}

RecyclerView的用法你已经相当熟练了,因此这个适配器的代码对你来说应该没有什么难 度吧?需要注意的是,之前我们都是将适配器写成一个独立的类,其实也是可以写成内部类的, 这里写成内部类的好处就是可以直接访问NewsTitleFragment的变量,比如isTwoPane0

观察一下onCeateViewHolder()方法中注册的点击事件,首先获取到了点击项的News实 例,然后通过isTwoPane变量来判断当前是单页还是双页模式,如果是单页模式,就启动一个 新的活动去显示新闻内容,如果是双页模式,就更新新闻内容碎片里的数据。

现在还剩最后一步收尾工作,就是向RecyclerView中填充数据了。修改NewsTitleFragment 中的代码,如下所示:

public class NewsTitleFragment extends Fragment {

@0verride

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedlnstanceState) {

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

RecyclerView newsTitleRecyclerView = (RecyclerView) view.findViewByld (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> getNev/s () {

List<News> newsList = new ArrayList<>(); for (int i = 1; i <= 50; i++) {

News news = new News();

news.setTitle("This is news title " + i); news. setContent (getRandomLengthContent ("This is news content " + i + u));

newsList add(news);

return newsList;

private String getRandomLengthContent(String content) { Random random = new Random();

int length = random.nextlnt(20) + 1; StringBuilder builder = new StringBuilder();

for (int i = 0; i < length; i++) { builder.append(content);

}

return builder.toString();

可以看到,onCreateView()方法中添加了 RecyclerView标准的使用方法,在碎片中使用 RecyclerView和在活动中使用几乎是一模一样的,相信没有什么需要解释的。另外,这里调用 了 getNewsO方法来初始化50条模拟新闻数据,同样使用了一个getRandomLengthContent () 方法来随机生成新闻内容的长度,以保证每条新闻的内容差距比较大,相信你对这个方法肯定不 会陌生了。

这样我们所有的编写工作就已经完成了,赶快来运行一下吧!首先在手机模拟器上运行,效 果如图4.14所示。

可以看到许多条新闻的标题,然后点击第一条新闻,会启动一个新的活动来显示新闻的内容, 效果如图4.15所示。

FragmentBestPractice

Thts is news title 1

接下来将程序在平板模拟器上运行,同样点击第一条新闻,效果如图4.16所示。

FragmentBe&tPractice

Ibis i^e 1

Thts  2

T羚& m title 3

ik洛註卷斜& istic 4

This is news 牴陡 5

1hi5 is i6

This is网海痍絶了

th據條ne間您mte 1

Thh * 窘玲覆》1 Ibis s ? f * 怂 赠荷& ccr tem > fhs 盅,辦*& tomem

1 Vifs 営&彩仲t 3他用f i•授舟s mnF ” ?檢普拎蔓斜容簿泌¥ Tbts snevjs

< on rent 1 This s 七◎华 1 Th挎偽 fw 翔务 ' Th 缗鼠 news content 1 Th^ w

阳洲& gmf”甘* 丛找登。以点斜f寿T打危丢电鮮鴻%〈矽藻5%籍fgf勺粉H" Vsjs z 1 this  nfas % $ ts 用^㈤广 cnrferjt 1

Th議德择伊精(Hie 8

This is 務灘幡 tnh 9

This is

Thk

Thif« ies 食机徐編

4.16双页模式的新闻标题和内容界面

怎么样?同样的一份代码,在手机和平板上运行却分别是两种完全不同的效果,说明我们程 序的兼容性已经相当不错了。通过这个例子,我相信你对碎片的理解一定又加深了很多,现在就 让我们一起来总结一下吧。

4.6小结与点评

你应该可以感觉到,上一节中我们开发的新闻应用,代码复杂度还是有点高的,比起只需要 兼容一个终端的应用,我们要考虑的东西多了很多。不过在开发的过程中多付出一些,在以后的 代码维护中就可以轻松很多。因此,有时候提前的付出还是很值得的。

我们再来回顾一下本章所学的内容吧,首先你了解了碎片的基本概念以及使用场景,接着通 过几个实例掌握了碎片的常见用法,随后又学习了碎片生命周期的相关内容以及动态加载布局的 技巧,最后在本章的最佳实践部分将前面所学的内容综合运用了一遍,相信你已经将碎片相关知 识点都牢记在心,并可以较为熟练地应用了。

本章其实是具有一个里程碑式的纪念意义的,因为到这里为止,我们已经基AndroidUI 相关的重要知识点都讲完了。后面在很长一段时间内都不会再系统性地介绍UI方面的知识,而 是将结合前面所学的UI知识来更好地讲解相应章节的内容。那么我们下一章将要学习什么呢? 还记得在第1章里介绍过的Android四大组件吧?目前我们只掌握了活动这一个组件,那么下一 章就来学习广播接收器吧。跟上脚步,准备继续前进!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/64252.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android开发——类微信界面设计

功能要求 开发一个类微信的主界面框架&#xff0c;UI布局为上中下布局&#xff0c;包含4个tab界面&#xff0c;当点击选择底部部件的时候进行页面切换 开发技术 layout xml、控件、监听、fragment 页面设计 微信的界面布局分为上中下三个部分。 &#xff08;1&#xff09…

类微信界面设计1

1. 设计目标 实现一个类似微信的底部tab栏切换 2. 功能说明 一个类微信的app&#xff0c;现阶段完成功能为点击对应按钮能够切换到对应界面 3. 代码解析 1).头部 使用LinearLayout布局。 <?xml version"1.0" encoding"utf-8"?> <LinearLayo…

马原强化考点

马原核心考点 考点1 非重点 马克思主义的内涵和构成&#xff08;马克思主义是什么&#xff09; 内涵&#xff1a; 1.创立者 &#xff1a;马恩创立&#xff0c;后继者发展 2.内容 &#xff1a;自然&#xff0c;社会&#xff0c;人类思维&#xff08;唯物史观不包括自然&#xff…

[哲学部分]马克思主义基本原理概论思维导图

2020/3/3 更新 之前链接关了补一个 链接&#xff1a;https://pan.baidu.com/s/1tsmAkdRG7jE1eMz34Ea4qQ 提取码&#xff1a;7y2j 2019/10/23 更新 由于最近比较忙 没时间一一回复大家的评论和邮件&#xff0c;我把文件放到了百度云&#xff0c;大家自取 谢谢大家支持 链接&…

马原,期末复习部分知识点,思维导图

原链接&#xff1a;https://gitmind.cn/app/doc/39a4c3a6e1a3d7892c79030d028cadbf 感谢观看&#xff01;

马原复习思维导图-前三章

一天一个奇迹系列&#xff0c;这个思维导图做的时候很爽&#xff0c;然而并没有什么用 ……马原这种东西看不懂就是看不懂&#xff0c;要应付考试还是要专心听课555 绪论部分 第一章 第二章 第三章

《马克思主义基本原理》复习重点

什么是马克思主义&#xff1f; 1、马克思主义是马克思和恩格斯共同创立并为后继者所不断发展的科学理论体系。 2、马克思理论是关于科学、社会、人类思维发展一般规律的学说&#xff0c;是关于社会主义必将代替资本主义&#xff0c;最终实现共产主义的学说 3、马克思主义是关…

《马克思主义基本原理》复习整理

马原是我第一门认真学习的政治类课程&#xff0c;当然要记录一下。听说马原期末很难&#xff0c;所以认真看了下书和提纲&#xff0c;学习马原之后&#xff0c;对事物的本质有了更深的理解&#xff0c;理论知识也没有想象中那么晦涩难懂&#xff0c;顺便整理出以下的资料&#…

考研马原 知识点 做题技巧 同类比较 重要会议 1800易错题 思维导图整理

本文的思维导图将考研马原进行了整理并标记出重点内容&#xff0c;同时对于同类事件&#xff0c;按时间顺序的时间都进行了整理&#xff0c;而且对于1800中的易错题目等重点内容也有整理 思维导图源文件已经发布在我的资源当中&#xff0c;有需要的可以去 我的主页 了解更多学…

GPT对SaaS领域有什么影响?

GPT火了&#xff0c;Chat GPT真的火了。 突然之间&#xff0c;所有人都在讨论AI&#xff0c;最初的访客是程序员、工程师、AI从业者&#xff0c;从早高峰写字楼电梯里讨论声&#xff0c;到村里大爷们的饭后谈资&#xff0c;路过的狗子都要和它讨论两句GPT的程度。 革命的前夜…

计算机英语ppt演讲稿,英语的ppt演讲稿

英语的ppt演讲稿 to make a better city, planners aimed at creating a city in which the insalubrious environment and social structure would be defeated by a reordering of physical and social arrangements, so that all the citizens could attain the benefits of…

网络安全复习总结

目录 Ch1 网络安全基础1.1 网络安全的总的目标1.2 防范技术(主流网络安全技术)1.3 网络安全技术支撑1.4 专业网络安全技术1.5 信息安全保障体系组成(PDRR)1.6 网络体系结构的深入理解、各层加密的作用1.7 帧、IP报文、TCP报文、UDP报文格式TCP首部三次握手半连接队列OSI七层网络…

软件安全期末总结

写在前面 所用教材&#xff1a;彭国军等人编著的第一版 博客地址&#xff1a;https://blog.csdn.net/zss192 说明&#xff1a;博客为根据老师所画重点有针对性的总结&#xff0c;供个人复习使用&#xff0c;仅供参考 第一章 软件安全概述 1.软件安全包括三个方面&#xff…

基于安全运营中心的工作总结

0x0 背景 日光之下&#xff0c;并无新事。又是一年新旧交替&#xff0c;去年年底的时候Freebuf的小编在关于安全工作总结这块还做了一个专题讨论&#xff0c;关于安全工作的总结复盘与规划估计也是许许多多安全从业者比较为难回答的问题了&#xff0c;最近大家也比较热门的讨论…

业务安全漏洞总结

目录 前言 一、登录认证模块 暴力破解 登录绕过 会话固定漏洞 Session会话注销 Cookie仿冒 二、越权 三、数量金额修改 四、前端js限制绕过 五、请求重放 六、短信/邮箱验证码漏洞 短信重放 验证码校验绕过 验证码重用/手机号修改 验证码回显 短信/邮箱验证码…

【湃哒星说安全】攻防演练中数据库信息收集方法记录

0x00 背景 在攻防演练或红队评估项目中&#xff0c;项目成果往往依赖红队队员综合渗透技能和优良的自动化工具。信息收集贯穿整个项目生命周期&#xff0c;如果攻方通过获取互联网侧应用服务器权限&#xff0c;并以此为跳板突破目标单位互联网侧防线&#xff0c;进而对应用服务…

日常安全运营工作的一些思考

0x0 安全运营的背景 安全运营是以企业安全能力成熟度为基础&#xff0c;运用适当的安全技术和管理手段整合人、技术、流程&#xff0c;持续降低企业安全风险的综合能力。在最近几年安全运营这个岗位出现之前&#xff0c;大部分的企事业单位都是由IT运维部门的少数几个工程师承担…