打造自己的 APP「冰与火百科」(二):接口、索引页

开始「冰与火百科」开发之旅!

网络数据

先说一下我的接口是怎么来的。

存放数据

首先确定自己需要一些什么数据,在满足自己要求的情况下越简单越好。对每个详情页面,我需要一张图片和一个 html 显示描述就够了。以奶德为例,在服务器的对应目录下,就会有 Eddard_Stark.png 和 Eddard_Stark.html 这两个文件。

这一步其实是整个项目最麻烦的地方。图片还好,但收集整理描述的内容真的要非常有耐心,至今才造了十几条数据。

创建数据集合

我需要两个实体类。一个是分类,也就是到时 TabLayout 中的 Tab,另一个就是内容。对应的要生成两个 json 文件。

创建一个 java 项目,添加 Gson 依赖,建立两个要转换 json 的集合:

private static List<TabDTO> tabList = new ArrayList<>();
private static List<ContentDTO> contentList = new ArrayList<>();

再次以奶德为例,往里面添加数据:

tabList.add(new TabDTO(10100,"person","Stark","史塔克"));
String starkUrl = "person/Stark/";
contentList.add(new ContentDTO(
        "Eddard_Stark",
        "person",
        "Stark",
        starkUrl + "Eddard_Stark.png",
        "艾德·史塔克",
        "临冬城公爵、北境守护",
        starkUrl + "Eddard_Stark.html"));

生成 json 文件

完了输出为 json 文件就好了,以 content 为例:

Gson gson = new Gson();
String jsonString = gson.toJson(contentList);
File contentJsonFile = new File("../IceAndFireServer/content.json");
try {
    OutputStreamWriter osw = new OutputStreamWriter(
            new FileOutputStream(contentJsonFile), "UTF-8");
    BufferedWriter bw = new BufferedWriter(osw);
    bw.write(jsonString, 0, jsonString.length());
    bw.flush();
    bw.close();
} catch (IOException e) {
    e.printStackTrace();
}

然后将数据上传到网络就好了,json 文件所在的网络地址就是你的接口了。刚开始我上传到了 GitHub,但发现经常会发生灵异事件,导致数据无法访问或者速度超慢,后来又上传到了九牛云。

这部分内容大家看一下就好了,毕竟不是常规的做法。有兴趣的可以到这里,数据和代码都在里面了。

APP主题色

下面终于来到我们的 Android 项目了。

创建 Android 项目后,第一反应是主题色得改一改。

在官方 Material Design 的色板里面,我选用了这一套:

对应的,color 文件的主题色值修改如下:

<color name="colorPrimary">#607d8b</color>
<color name="colorPrimaryDark">#546e7a</color>
<color name="colorAccent">#40c4ff</color>

索引页

我也学着别的 APP,做一个索引页 IndexActivity。就简单展示一句「挖了蘑菇立死」,噢不对,展示一句「Valar Morghulis」就好了,像这样:

加入一点简单的动画,然后还能做一些耗时的启动操作。

DataBinding

我会比较在意代码的简洁性,在实现同样功能的情况下代码越少越好,而且排版一定要看上去舒服,缩进要少,甚至不允许代码里面有警告。

DataBinding 是一个可以增加代码简洁性的东西。这里以索引页为例,简单介绍一下它最简单的一个应用,代替 findViewByid。

配置

在对应 Module 的 build.grade 里配置:

android {
    ....
    dataBinding {
        enabled = true
    }
}

布局

在需要绑定的布局文件里,最外层增加一个 layout 标签,比如这里的 activity_index.xml :

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

    <TextView
        android:id="@+id/tv_index"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="@string/valar_morghulis"
        android:textColor="@color/color3A3A3A"
        android:textSize="36sp" />

</layout>

使用

创建一个成员变量:

private ActivityIndexBinding binding;

注意,这里的变量类型是和布局文件相关的,比如 ActivityIndexBinding 对应 activity_index。

然后将原来 setContentView 的地方修改为:

binding = DataBindingUtil.setContentView(this, R.layout.activity_index);

当我要使用布局里的 TextView 的时候,直接用 binding.tvIndex 就可以了。tvIndex 这个名字是和布局里的 id:tv_title 相对应的。

DataBinding 的一些更高级的用法这里就不赘述了,网上的教程很多,大家可以多搜索了解一下。

动画

为了让索引页的字更生动,我打算加一个渐变放大的动画效果。

xml

我这里用的是 View Animation(视图动画),动画过程是通过 xml 文件定义的。在 res/anim 文件夹下新建一个 xml 文件,代码如下:

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:shareInterpolator="true" >

    <scale
        android:duration="1300"
        android:fillAfter="true"
        android:fromXScale="0.95"
        android:fromYScale="0.95"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1"
        android:toYScale="1" />

    <alpha
        android:duration="1300"
        android:fillAfter="true"
        android:fromAlpha="0"
        android:toAlpha="1" />

</set>

这个的意思很好理解,就是用 1.3 秒的时间,控件大小从 95% 渐变到 100%,透明度从 0 渐变到10%。

使用

执行动画的代码也很简单,大家直接看吧:

Animation animation = AnimationUtils
        .loadAnimation(this, R.anim.anim_valar_morghulis);
animation.setAnimationListener(new AnimationListener() {

    @Override
    public void onAnimationStart(Animation animation) {

    }

    @Override
    public void onAnimationEnd(Animation animation) {
        SystemClock.sleep(500);
        animationComplete = true;
        goMainPage();
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }

});
SystemClock.sleep(200);
binding.tvIndex.startAnimation(animation);

为了让用户能看清动画,我在里面加入了一些停顿。经过我自己的多次试验,最终定下的这个停顿时常,我认为长度是在能看清动画的情况下,又不会长到让人感到厌烦的,效果如下:

耗时操作

前面说到,在索引页可以做一些耗时的操作。动画的执行总共有两秒的时间,用户的时间是宝贵的,要是在这两秒里面什么都不做就太浪费了。

最耗时的操作,应该是调接口了。

其实刚开始我是进入到首页才调接口的,进入不同的页面获取不同的数据。但这样会有一个问题,由于我没有后台,只有两个假接口,所以搜索功能就无法实现了。

所以现在改为,在索引页获取到所有数据并保存起来,在不同分类页面下通过筛选展示数据,这样搜索也可以实现了。

下面就简单讲一下目前比较流行的两个框架 Retrofit 2 和 Realm,来完成数据的获取和保存。

Retrofit 2

Retrofit 的厉害之处我就不多说了,网上的教程很多的,我只讲最最简单的用法。

配置

在 Module 的 build.grade 里添加依赖:

compile "com.squareup.retrofit2:retrofit:${RETROFIT_VERSION}"
compile "com.squareup.retrofit2:converter-scalars:${RETROFIT_VERSION}"
compile "com.squareup.retrofit2:converter-gson:${RETROFIT_VERSION}"

目前最新版是 2.3.0,大家可以自行替换。

这里面 converter-scalars 是添加 String 类型的返回,converter-gson 是添加 Gson 的支持(返回实体类)。

接口定义

新建一个接口文件(interface),用来统一管理所有要调用的接口(url),我暂时只有两个接口,再留一个通用的 Get 请求备用:

public interface RequestServes {

    @GET("{url}")
    Call<String> get(@Path("url") String url);

    @GET("tab.json")
    Call<List<TabDTO>> getTab();

    @GET("content.json")
    Call<List<ContentDTO>> getContent();

}

注解 @GET 后面的内容就是要请求的接口,这里不用写基础域名(BaseUrl)。

初始化

需要通过 Retrofit.Builder 初始化 Retrofit,调用 baseUrl 设置基础域名:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(ServerAPI.BASE_URL)
        //增加返回值为String的支持
        .addConverterFactory(ScalarsConverterFactory.create())
        //增加返回值为实体类的支持
        .addConverterFactory(GsonConverterFactory.create())
        .build();
requestServes = retrofit.create(RequestServes.class);

需要注意的是,BaseUrl 必须以斜杠「/」结尾,否则会报错。

考虑到可能多个页面都需要调用接口,可以把这段代码放在 BaseActivity 里。

使用

用起来超简单:

Call<List<TabDTO>> call = requestServes.getTab();
call.enqueue(new Callback<List<TabDTO>>() {
    @Override
    public void onResponse(@NonNull Call<List<TabDTO>> call,
                           @NonNull retrofit2.Response<List<TabDTO>> response) {
        List<TabDTO> tabList = response.body();
        //...
        goMainPage();
    }

    @Override
    public void onFailure(@NonNull Call<List<TabDTO>> call, @NonNull Throwable t) {
        netError();
    }
});

请求失败的时候,我会弹吐司提醒,并且让页面可点击重试。

Realm

Realm 是 SQLite 的替代者,它更快速、更易用。下面看看 Realm 的简单使用。

配置

修改 Project 下的 build.gradle :

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath "io.realm:realm-gradle-plugin:3.5.0"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

目前最新版是 3.5.0。然后再到 Module 的 build.gradle,添加:

apply plugin: 'realm-android'

配置完毕

初始化

在使用 Realm 之前,必须先调用:

Realm.init(this);

获取 Realm 实例有以下两种方法:

Realm mRealm = Realm.getDefaultInstance();

这里有个小细节。如果实体类的字段发生了改变,这里是会报错的。我的做法比较粗暴,清空数据库后再重新获取:

try {
    mRealm = Realm.getDefaultInstance();
} catch (RuntimeException e) {
    Realm.deleteRealm(Realm.getDefaultConfiguration());
    mRealm = Realm.getDefaultInstance();
}

保存

让需要保存下来的实体类继承 RealmObject,然后就可以使用以下代码保存了:

mRealm.beginTransaction();
mRealm.copyToRealm(list);
mRealm.commitTransaction();

查询

查询也很简单,就一句代码的事:

List<Data> list = mRealm.where(Data.class).findAll();

复杂查询这里就不多说了。

需要注意的是,如果要对查询的结果进行修改或删除等操作,则必须要在 transaction 里完成,修改的结果会同步到数据库。比如,我想对上面查询到的第一个元素进行修改:

Data data = list.get(0)
mRealm.beginTransaction()
mRealm.copyFromRealm(data)
data.num = 666
mRealm.commitTransaction()

小结

就先到这吧,一个索引页都能扯这么多。

其实我没什么想总结的,我只想提醒一下:GOT Session 7 !!!

项目地址

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

推荐阅读更多精彩内容