Android 基础核心:资源管理、布局设计与多类型 Adapter 写法总结

0 阅读7分钟

这是一个仿今日头条的 Android 项目。这个项目把新闻列表里常见的两种卡片样式都做了出来。一种是右边带一张大图的新闻,另一种是底部带三张小图的新闻。项目里还用上了 RecyclerView 和自定义的 Adapter。接下来,通过这个例子来分析 Android 的资源管理、布局设计和多类型 Adapter 的写法。

image.png

image.png

一、项目的目录

image.png

这个结构是一个比较标准的 Android 项目。res/layout 里面放了四个 XML 文件,它们分别对应主界面、标题栏、单图新闻卡片和三图新闻卡片。res/drawable里放的是图片资源,比如置顶图标和搜索框的背景。res/values里存了颜色、文字和样式。

接下来说一下常见的控件: image.png

二、主界面布局 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。很多新闻应用都喜欢用浅灰背景加上白色卡片,这样看起来层次更清楚。

image.png 根布局里面放了四个东西 : 第一个是通过 <include> 引入的标题栏。(<include layout="@layout/title_bar" />)第二个是一个水平的 LinearLayout,里面放了七个频道标签。每个标签都用了同一个样式 tvStyle,但是第一个标签“推荐”的文字颜色单独设成了红色,其他标签都是灰色。这说明“推荐”是当前选中的频道。第三个是一条很细的分割线,高度只有 1dp,颜色是浅灰色。第四个是一个 RecyclerView,它的 id 是 rv_list。这个 RecyclerView 会占满剩下的所有高度,用来展示新闻列表。

频道标签的样式 tvStyle 在 styles.xml 里应该有这样的定义:

image.png 每个标签的宽度都是 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 的边距。

image.png image.png 右边是一个搜索框。搜索框的高度是 35dp(android:layout_height="35dp"),比标题栏矮一些,所以看起来像是嵌在标题栏里面的。背景是 search_bg, 这个 search_bg(android:background="@drawable/search_bg") 是一个圆角矩形的 shape 文件。 可以设置搜索框里的提示文字是什么内容,如:“搜你想搜的”。搜索框的左边内边距是 30dp,这个空间本来是留给搜索图标的,但是这个项目里没有放图标,所以只是一个预留位置。

image.png image.png

四、单图新闻卡片 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,所以内容不会贴边。

image.png

3️⃣左边的信息区是一个垂直的 LinearLayout,里面有两个部分。上面是标题 TextView。标题的宽度固定为 280dp,这样右边才能给图片留出固定空间。标题最多显示两行,超出部分会变成省略号。标题的文字颜色是深灰色,字号是 16sp。

image.png 标题下面是一个 RelativeLayout。 这个 RelativeLayout 里放了两样东西。第一个是置顶图标 iv_top,它的宽高都是 20dp,并且通过 layout_alignParentBottom="true" 让它贴住底部。 image.png 第二个是一个水平的 LinearLayout,里面依次是作者、评论数和时间。这个水平的 LinearLayout 也贴住底部,并且通过 layout_toRightOf="@id/iv_top" 放在了置顶图标的右边。如果这条新闻不是置顶的,开发者可以把置顶图标隐藏掉,那么信息栏就会自动向左移动,不会留下空白。

image.png 右边的图片 iv_img 放在了信息区的右边。它的宽度是 match_parent,但是因为它是在 RelativeLayout 里并且指定了 layout_toRightOf="@id/ll_info",所以它实际占用的宽度是父容器减去信息区之后的剩余宽度。它的高度是 90dp,跟卡片等高,所以图片是正方形的。padding="3dp" 让图片和卡片边缘之间留了一点空隙。 image.png

五、三图新闻卡片 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,最多显示两行。

image.png 标题下面是一个水平的 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。

3️⃣图片区域下面就是作者、评论数和时间。这里用了两层 LinearLayout 嵌套。外层的 LinearLayout 是垂直的,内层是水平的。虽然两层嵌套稍微增加了布局的深度,但是结构很清楚。

image.png

image.png