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

開始「冰與火百科」開發(fā)之旅!

網(wǎng)絡(luò)數(shù)據(jù)

先說一下我的接口是怎么來的。

存放數(shù)據(jù)

首先確定自己需要一些什么數(shù)據(jù),在滿足自己要求的情況下越簡單越好。對每個詳情頁面,我需要一張圖片和一個 html 顯示描述就夠了。以奶德為例,在服務(wù)器的對應(yīng)目錄下,就會有 Eddard_Stark.png 和 Eddard_Stark.html 這兩個文件。

這一步其實是整個項目最麻煩的地方。圖片還好,但收集整理描述的內(nèi)容真的要非常有耐心,至今才造了十幾條數(shù)據(jù)。

創(chuàng)建數(shù)據(jù)集合

我需要兩個實體類。一個是分類,也就是到時 TabLayout 中的 Tab,另一個就是內(nèi)容。對應(yīng)的要生成兩個 json 文件。

創(chuàng)建一個 java 項目,添加 Gson 依賴,建立兩個要轉(zhuǎn)換 json 的集合:

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

再次以奶德為例,往里面添加數(shù)據(jù):

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

然后將數(shù)據(jù)上傳到網(wǎng)絡(luò)就好了,json 文件所在的網(wǎng)絡(luò)地址就是你的接口了。剛開始我上傳到了 GitHub,但發(fā)現(xiàn)經(jīng)常會發(fā)生靈異事件,導致數(shù)據(jù)無法訪問或者速度超慢,后來又上傳到了九牛云。

這部分內(nèi)容大家看一下就好了,畢竟不是常規(guī)的做法。有興趣的可以到這里,數(shù)據(jù)和代碼都在里面了。

APP主題色

下面終于來到我們的 Android 項目了。

創(chuàng)建 Android 項目后,第一反應(yīng)是主題色得改一改。

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

對應(yīng)的,color 文件的主題色值修改如下:

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

索引頁

我也學著別的 APP,做一個索引頁 IndexActivity。就簡單展示一句「挖了蘑菇立死」,噢不對,展示一句「Valar Morghulis」就好了,像這樣:

加入一點簡單的動畫,然后還能做一些耗時的啟動操作。

DataBinding

我會比較在意代碼的簡潔性,在實現(xiàn)同樣功能的情況下代碼越少越好,而且排版一定要看上去舒服,縮進要少,甚至不允許代碼里面有警告。

DataBinding 是一個可以增加代碼簡潔性的東西。這里以索引頁為例,簡單介紹一下它最簡單的一個應(yīng)用,代替 findViewByid。

配置

在對應(yīng) 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>

使用

創(chuàng)建一個成員變量:

private ActivityIndexBinding binding;

注意,這里的變量類型是和布局文件相關(guān)的,比如 ActivityIndexBinding 對應(yīng) activity_index。

然后將原來 setContentView 的地方修改為:

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

當我要使用布局里的 TextView 的時候,直接用 binding.tvIndex 就可以了。tvIndex 這個名字是和布局里的 id:tv_title 相對應(yīng)的。

DataBinding 的一些更高級的用法這里就不贅述了,網(wǎng)上的教程很多,大家可以多搜索了解一下。

動畫

為了讓索引頁的字更生動,我打算加一個漸變放大的動畫效果。

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%。

使用

執(zhí)行動畫的代碼也很簡單,大家直接看吧:

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

為了讓用戶能看清動畫,我在里面加入了一些停頓。經(jīng)過我自己的多次試驗,最終定下的這個停頓時常,我認為長度是在能看清動畫的情況下,又不會長到讓人感到厭煩的,效果如下:

耗時操作

前面說到,在索引頁可以做一些耗時的操作。動畫的執(zhí)行總共有兩秒的時間,用戶的時間是寶貴的,要是在這兩秒里面什么都不做就太浪費了。

最耗時的操作,應(yīng)該是調(diào)接口了。

其實剛開始我是進入到首頁才調(diào)接口的,進入不同的頁面獲取不同的數(shù)據(jù)。但這樣會有一個問題,由于我沒有后臺,只有兩個假接口,所以搜索功能就無法實現(xiàn)了。

所以現(xiàn)在改為,在索引頁獲取到所有數(shù)據(jù)并保存起來,在不同分類頁面下通過篩選展示數(shù)據(jù),這樣搜索也可以實現(xiàn)了。

下面就簡單講一下目前比較流行的兩個框架 Retrofit 2 和 Realm,來完成數(shù)據(jù)的獲取和保存。

Retrofit 2

Retrofit 的厲害之處我就不多說了,網(wǎng)上的教程很多的,我只講最最簡單的用法。

配置

在 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),用來統(tǒng)一管理所有要調(diào)用的接口(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 后面的內(nèi)容就是要請求的接口,這里不用寫基礎(chǔ)域名(BaseUrl)。

初始化

需要通過 Retrofit.Builder 初始化 Retrofit,調(diào)用 baseUrl 設(shè)置基礎(chǔ)域名:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(ServerAPI.BASE_URL)
        //增加返回值為String的支持
        .addConverterFactory(ScalarsConverterFactory.create())
        //增加返回值為實體類的支持
        .addConverterFactory(GsonConverterFactory.create())
        .build();
requestServes = retrofit.create(RequestServes.class);

需要注意的是,BaseUrl 必須以斜杠「/」結(jié)尾,否則會報錯。

考慮到可能多個頁面都需要調(diào)用接口,可以把這段代碼放在 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 之前,必須先調(diào)用:

Realm.init(this);

獲取 Realm 實例有以下兩種方法:

Realm mRealm = Realm.getDefaultInstance();

這里有個小細節(jié)。如果實體類的字段發(fā)生了改變,這里是會報錯的。我的做法比較粗暴,清空數(shù)據(jù)庫后再重新獲取:

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

復雜查詢這里就不多說了。

需要注意的是,如果要對查詢的結(jié)果進行修改或刪除等操作,則必須要在 transaction 里完成,修改的結(jié)果會同步到數(shù)據(jù)庫。比如,我想對上面查詢到的第一個元素進行修改:

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

小結(jié)

就先到這吧,一個索引頁都能扯這么多。

其實我沒什么想總結(jié)的,我只想提醒一下:GOT Session 7 !!!

項目地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容