提供優質文章和福利的客戶端:Gank

前言

很久之前就在關注代碼家每日共享的學習資源,獲益匪淺。

最近比較空閒,並且想自己構建一個完整項目,來了解自己的不足,剛好Gank提供了API,於是決定一試。

TigerGank項目地址

效果展示

頁面 效果
首頁效果
網絡狀態
每日詳情

第三方庫

項目框架

首先,整體的項目框架,採用MVP框架。

關於Android框架,還是比較自由的。根據需求去選擇自己喜歡的框架就可以。

這裏我想加深一下自己對MVP框架的理解,於是選擇採用MVP框架。

關於MVC、MVP、MVVM的框架含義,可以參考此篇短文,言簡意賅。

該項目的框架基本仿照Google開源的Android框架模板

雖然存在一些差異,不過大同小異。

BaseActivity

一個好的基類,可以大大減少後續的工作量。

首先,我們來稍微屢一下全局的要求:

  • 標題欄:基本所有頁面都會有,但是有些頁面可能需要定製化,甚至可能沒有。

  • 頁面控制:此處是一大頭,包括Loading、無網、無數據、數據展示等多種頁面樣式以及展示邏輯。

綜上,我認爲我們需要爲BaseActivity設置一個layout文件,它持有ToolBar和各個狀態的頁面UI,並且可以控制頁面UI的展示時機。

在子Activity中,繼承BaseActivity後只需設置數據UI的樣式即可。

接下來我們就開始構建BaseActivity,首先來構建BaseActivity的layout:

<?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/c_ffffff"
    android:orientation="vertical">

    <!--ToolBar-->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/c_3f51b5"
        android:minHeight="?attr/actionBarSize" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <!--Loading Page-->
        <include
            android:id="@+id/view_loading"
            layout="@layout/view_loading"
            android:visibility="gone" />

        <!--無數據 Page-->
        <include
            android:id="@+id/view_no_data"
            layout="@layout/view_no_data"
            android:visibility="gone" />

        <!--無網 Page-->
        <include
            android:id="@+id/view_no_net"
            layout="@layout/view_no_net"
            android:visibility="gone" />

        <!--數據 Page-->
        <FrameLayout
            android:id="@+id/view_data"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />

    </FrameLayout>

</LinearLayout>

可以看到,它包括了ToolBar、LoadingPage、無數據Page、無網Page以及數據Page。

接下來我們在BaseActivity中對控件進行初始化,開始書寫頁面狀態的控制代碼。

此處各位看官要有意識,這些控制代碼,不應存在於BaseActivit中。

原因有很多,首先因爲Activity是我們MVP的View層,它僅僅掌控UI變化的結果,而不應該持有UI變化的邏輯判斷。其次,如果我們將來封裝BaseFragment,難道需要將這些邏輯代碼再寫一遍應用至BaseFragment中嗎?

所以這裏我決定在BaseActivity中創建成員變量PageController。每個Activity都會有一個獨立的PageController,它持有Activity,來通知Activity的UI變化,並且和Activity擁有相同的生命週期:

//頁面狀態控制器,每個頁面持有一個,使用弱引用來持有Activity
//存在於BaseActivity中,無需手動控制,在頁面銷燬時自動釋放
public class PageController {
    //Activity對象
    private WeakReference<BaseActivity> weakActivity;
    //當前頁面狀態
    private int currentState;

    public PageController(BaseActivity activity) {

        weakActivity = new WeakReference<>(activity);
    }
    //銷燬方法
    public void onDestory() {
        if (weakActivity == null) {
            return;
        }
        weakActivity.clear();
        weakActivity = null;
    }
}

接下來就開始構建具體的頁面控制邏輯。

這個邏輯因人而異,我這裏就不介紹自己構建時的思路了,源碼裏的註釋都有體現。

在完成PageController後,當你需要構建BaseFragment時,僅僅需要將fragment.getAcitivty()傳遞至PageController中,就可以使用PageController了。

之後在實際使用過程中,我也發現了一個問題:

由於我們的頁面是根據頁面狀態來顯示的,也就是說隨着狀態變化,頁面中有且只有一種類型的頁面處於顯示狀態。

這就產生了一個巨大的問題:當數據View有下拉刷新或者抽屜效果時,如果網絡超時或者無數據,數據View就會隱藏顯示對應的狀態View,數據View的隱藏代表着下拉刷新或抽屜效果的View被隱藏了,也就代表着用戶無法使用了。

這明顯是不符合用戶操作習慣的。

這個問題待解決。

網絡請求

使用okhttp來作爲本次開發的網絡請求底層。

再好的框架,想要適應一個項目,必定需要進行定製化的封裝。

在封裝之前,先回想一下一次完整的網絡請求,注意我們項目是MVP框架:

用戶在Activity中發出請求—>調用Presenter層的網絡請求方法->調用Model層的網絡請求方法->傳遞給Presenter請求結果以及數據->傳遞給Activity結果以及數據

在MVP框架下,一次網絡請求基本就是這樣的了。
不過這就牽扯到框架問題了,我們先回到網絡請求中,不去考慮框架。

如何最大限度的抽象,是我們現在要着重考慮的。

並且還要考慮併發、緩存、異步同步等一系列事情。

該項目的網絡請求封裝簡直不忍直視,基本是本着能用就行的思想來進行封裝的。。。

我發現此處是我的薄弱項,在將來有空時我會好好學習的。。。。

這裏就不做過多介紹了,其實網上有很多基於okhttp封裝相當好的網絡請求框架,想使用的話也可以使用。

Activity切換動畫

Activity的切換動畫是可以定製的,大家都知道。這裏我定義了多套Activity切換動畫,並封裝爲Jump,和PageController一樣,每個Acitivty持有一個。

爲什麼要這樣做?因爲我認爲Activity的切換動畫體驗其實很重要。

有些時候,從一個Activity打開一個Activity時,是一種流程,類似的有修改密碼功能:

輸入完成舊密碼之後點擊下一步,打開輸入新密碼頁面,就屬於一種流程頁面。那麼它的動畫應該就和上個頁面很契合,給人一種頁面連貫感。

有些時候,即將打開的Activity與上個Activity毫無關聯,用戶將在這個頁面領略到風格完全不同的數據,此時Activity的切換動畫就需要給人一種打開新世界的感覺。

比如一些詳情頁、或者一些輪播圖跳轉等。

在封裝Jump的過程中,我想到了兩種方案:

  • 將Jump封裝爲單例靜態的,並在Jump中封裝一個Map集合來記錄每個Activity的打開動畫的類型,當這個Activity要關閉時,去查詢匹配對應的關閉動畫。

  • 每個Activity持有一個Jump,該Jump記錄了該Activity打開時的動畫類型,在關閉時直接去查詢匹配即可。

兩種方案理解之後,可以很明顯地發現,第2種方案更簡單一些,並且第2種方案的性能會更好一些?

所以我是選擇了第2種方案。

接着在開發過程中,遇到了一個難點,和大家分享一下:

在使用封裝的Jump跳轉時,該Jump是上個Activity持有的Jump,而不是即將打開的Activity持有的Jump。

這就導致了使用Jump去獲取動畫類型時,獲取的是上個Activity的動畫類型,而不是即將打開的動畫類型。

爲什麼不使用即將打開的Activity的Jump呢?因爲在跳轉過去的時候,也就是startActivity()時,我們是沒有即將打開的Activity的實例的。

我寫了很多代碼來進行測試,發現大部分解決方案都是需要在兩處指定動畫類型。

我認爲這是不符合編程思想的。將來如果有人接手這些代碼,那麼必定不知道要指定Activity的動畫類型,需要修改兩處代碼。

目前我是通過添加Jump中封裝的跳轉方法的參數,用來指定動畫類型,就像這樣子:

public void to(Intent intent) {}
public void to(Intent intent, Jump.JumpType type) {}
public void to(Intent intent, int requestCode) {}
public void to(Intent intent, int requestCode,Jump.JumpType type) {}

可以發現除了傳遞Intent之外,我又添加了指定動畫類型JumpType的跳轉方法。

在指定JumpType的跳轉方法中,將JumpType同時塞入到Intent當中,然後在BaseActivity中去解析JumpType並賦值給Jump,就是整體的流程。

這裏一定要注意,當指定JumpType時,我們不能將JumpType複製給當前Jump,因爲這個JumpType是下個Activity所做的動畫,而不是當前Activity所做的動畫。

具體代碼可參考:Jump

TODO

到目前爲止,還有一些功能沒有開發:

  • 搜索功能

    Gank的API中擁有搜索的API,該功能在後續會去實現。

  • 網絡請求優化

    從該項目當中,我發現自己對網絡請求的理解和Android文件存儲的方式存在很多問題,等到有空,我會將網絡請求這邊重構,包括添加File緩存、異步同步請求、文件上傳接收請求等一系列基本功能。

  • RxJava+Retrofit

    本項目當中沒有使用RxJava。在後期有空我會重新再寫一遍該項目,會使用RxJava和其他框架來實現,這算是立一個flag吧。

結語

這一週的精力除了完成本職工作外,基本上都放到了TigerGank中。

我會慢慢抽空繼續完善該項目,這周就先這樣了。

開發過程中遇到很多瓶頸,解決瓶頸的代碼肯定是不優雅的。主要還是因爲自己的水平太差。

一起加油,感興趣的朋友也可以自己開發Gank。

感謝

上述所有第三方庫

Gank API

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章