这是一个仿今日头条的 Android 项目。这个项目把新闻列表里常见的两种卡片样式都做了出来。一种是右边带一张大图的新闻,另一种是底部带三张小图的新闻。项目里还用上了 RecyclerView 和自定义的 Adapter。接下来,通过这个例子来分析 Android 的资源管理、布局设计和多类型 Adapter 的写法。
一、项目的目录
这个结构是一个比较标准的 Android 项目。res/layout 里面放了四个 XML 文件,它们分别对应主界面、标题栏、单图新闻卡片和三图新闻卡片。res/drawable里放的是图片资源,比如置顶图标和搜索框的背景。res/values里存了颜色、文字和样式。
接下来说一下常见的控件:
二、主界面布局 activity_main.xml
1️⃣我把这个布局文件的内容贴出来,然后一行一行解释。
<?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="@color/light_gray_color"
android:orientation="vertical">
<include layout="@layout/title_bar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@android:color/white"
android:orientation="horizontal">
<TextView
style="@style/tvStyle"
android:text="推荐"
android:textColor="@android:color/holo_red_dark" />
<TextView
style="@style/tvStyle"
android:text="抗疫"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="小视频"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="北京"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="视频"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="热点"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="娱乐"
android:textColor="@color/gray_color" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eeeeee" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
2️⃣根布局是一个垂直方向的 LinearLayout。背景色是浅灰色,这个颜色在 colors.xml 里定义为 light_gray_color。很多新闻应用都喜欢用浅灰背景加上白色卡片,这样看起来层次更清楚。
根布局里面放了四个东西 : 第一个是通过
<include> 引入的标题栏。(<include layout="@layout/title_bar" />)第二个是一个水平的 LinearLayout,里面放了七个频道标签。每个标签都用了同一个样式 tvStyle,但是第一个标签“推荐”的文字颜色单独设成了红色,其他标签都是灰色。这说明“推荐”是当前选中的频道。第三个是一条很细的分割线,高度只有 1dp,颜色是浅灰色。第四个是一个 RecyclerView,它的 id 是 rv_list。这个 RecyclerView 会占满剩下的所有高度,用来展示新闻列表。
频道标签的样式 tvStyle 在 styles.xml 里应该有这样的定义:
每个标签的宽度都是 0dp,然后权重都是 1,所以七个标签会平均分配父容器的宽度。
三、标题栏布局 title_bar.xml
1️⃣标题栏的代码是这样的:
<?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="50dp"
android:background="#d33d3c"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="仿今日头条"
android:textColor="@android:color/white"
android:textSize="22sp" />
<EditText
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="15dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="15dp"
android:background="@drawable/search_bg"
android:gravity="center_vertical"
android:textColor="@android:color/black"
android:hint="搜你想搜的"
android:textColorHint="@color/gray_color"
android:textSize="14sp"
android:paddingLeft="30dp" />
</LinearLayout>
2️⃣标题栏的背景色是 #d33d3c,这是一种偏暗的红色,跟今日头条的品牌色比较接近。标题文字是白色的,字号 22sp,左边留了 10dp 的边距。
右边是一个搜索框。搜索框的高度是 35dp(
android:layout_height="35dp"),比标题栏矮一些,所以看起来像是嵌在标题栏里面的。背景是 search_bg,
这个 search_bg(android:background="@drawable/search_bg") 是一个圆角矩形的 shape 文件。
可以设置搜索框里的提示文字是什么内容,如:“搜你想搜的”。搜索框的左边内边距是 30dp,这个空间本来是留给搜索图标的,但是这个项目里没有放图标,所以只是一个预留位置。
四、单图新闻卡片 list_item_one.xml
1️⃣这个文件定义的是最常见的新闻样式:左边是标题和作者信息,右边是一张图片。
<?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="90dp"
android:layout_marginBottom="8dp"
android:background="@android:color/white"
android:padding="8dp">
<LinearLayout
android:id="@+id/ll_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:textColor="#3c3c3c"
android:textSize="16sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_top"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentBottom="true"
android:src="@drawable/top" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/iv_top"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
style="@style/tvInfo" />
<TextView
android:id="@+id/tv_comment"
style="@style/tvInfo" />
<TextView
android:id="@+id/tv_time"
style="@style/tvInfo" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
<ImageView
android:id="@+id/iv_img"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_toRightOf="@id/ll_info"
android:padding="3dp" />
</RelativeLayout>
2️⃣整个卡片的高度是 90dp 。每个卡片之间通过layout_marginBottom="8dp" 隔开,所以看起来就像一张一张独立的卡片。内边距是 8dp,所以内容不会贴边。
3️⃣左边的信息区是一个垂直的 LinearLayout,里面有两个部分。上面是标题 TextView。标题的宽度固定为 280dp,这样右边才能给图片留出固定空间。标题最多显示两行,超出部分会变成省略号。标题的文字颜色是深灰色,字号是 16sp。
标题下面是一个
RelativeLayout。
这个 RelativeLayout 里放了两样东西。第一个是置顶图标 iv_top,它的宽高都是 20dp,并且通过 layout_alignParentBottom="true" 让它贴住底部。
第二个是一个水平的
LinearLayout,里面依次是作者、评论数和时间。这个水平的 LinearLayout 也贴住底部,并且通过 layout_toRightOf="@id/iv_top" 放在了置顶图标的右边。如果这条新闻不是置顶的,开发者可以把置顶图标隐藏掉,那么信息栏就会自动向左移动,不会留下空白。
右边的图片
iv_img 放在了信息区的右边。它的宽度是 match_parent,但是因为它是在 RelativeLayout 里并且指定了 layout_toRightOf="@id/ll_info",所以它实际占用的宽度是父容器减去信息区之后的剩余宽度。它的高度是 90dp,跟卡片等高,所以图片是正方形的。padding="3dp" 让图片和卡片边缘之间留了一点空隙。
五、三图新闻卡片 list_item_two.xml
1️⃣这个布局用在图集类的新闻上,比如一组多图新闻。
<?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="wrap_content"
android:layout_marginBottom="8dp"
android:background="@android:color/white">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:padding="8dp"
android:textColor="#3c3c3c"
android:textSize="16sp" />
<LinearLayout
android:id="@+id/ll_img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv_title"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_img1"
style="@style/ivImg"/>
<ImageView
android:id="@+id/iv_img2"
style="@style/ivImg"/>
<ImageView
android:id="@+id/iv_img3"
style="@style/ivImg"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/ll_img"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
style="@style/tvInfo" />
<TextView
android:id="@+id/tv_comment"
style="@style/tvInfo" />
<TextView
android:id="@+id/tv_time"
style="@style/tvInfo" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
2️⃣这个卡片的根布局高度是 wrap_content,因为图片区域的高度不是固定的。标题放在最上面,宽度占满父容器,内边距是 8dp,最多显示两行。
标题下面是一个水平的
LinearLayout,里面放了三个 ImageView。这三个 ImageView 都使用了同一个样式 ivImg:
<style name="ivImg">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">90dp</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_toRightOf">@id/ll_info</item>
</style>
三个 ImageView 的宽度都是 0dp,权重都是 1,所以它们会等宽地排成一行。高度是 90dp。