開始「冰與火百科」開發之旅!
網路資料
先說一下我的介面是怎麼來的。
存放資料
首先確定自己需要一些什麼資料,在滿足自己要求的情況下越簡單越好。對每個詳情頁面,我需要一張圖片和一個 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 !!!