XCoreRedux框架:Android UI組件化與Redux實踐

XCoreRedux框架:Android UI組件化與Redux實踐

@author: 莫川 https://github.com/nuptboyzhb/

XCoreRedux源碼+Demo:https://github.com/nuptboyzhb/XCoreRedux

使用android studio打開該項目。

目錄結(jié)構(gòu)

  • demo

    基于xcore框架寫的一個小demo
  • xcore

    XCoreRedux核心代碼庫
  • pics

    文檔的pic資源

前言

  • Android開發(fā)當(dāng)中的Code Architecture思考

    最近看了很多前端的框架,React、Flux、Redux等,React和Redux都是前端比較流行的框架。而android方面,Google官方貌似不太Care此事,業(yè)內(nèi)也沒有公認(rèn)的優(yōu)秀Architecture。與前端類似,在Android開發(fā)中,同樣也面臨著復(fù)雜的數(shù)據(jù)state管理的問題。在理解Store、Reducer和Action的基礎(chǔ)上,最終,基于Redux+React的思想,提出了一個基于Android平臺Redux框架,我給它起名叫作:XCoreRedux。本倉庫就是XCoreRedux+UIComponent框架的實現(xiàn)。它表達(dá)的是一種思想,希望大家能夠提出更好的意見。

XCoreRedux框架介紹

與前端的Redux框架類似,XCoreRedux框架的圖示如下:

redux20160930.png

Action

Action 是把數(shù)據(jù)傳到 store 的有效載體。它是store的唯一數(shù)據(jù)來源。我們一般是通過 store.dispatch()將action傳到store中。Action一般需要兩個參數(shù):type類型和data數(shù)據(jù)。在XCoreRedux框架下,我們定義Action如下:


public class XCoreAction {

    //Action的類型
    public final String type;
    //Action攜帶的value,可為空
    public final Object value;

    public XCoreAction(String type, Object value) {
        this.type = type;
        this.value = value;
    }

    public XCoreAction(String type) {
        this(type, null);
    }

    @Override
    public boolean equals(Object object) {
       ...
    }

    @Override
    public int hashCode() {
        ...
    }
}

為了統(tǒng)一的管理Action,你可以實現(xiàn)一個ActionCreator,比如,demo中創(chuàng)建了一個聯(lián)系人業(yè)務(wù)的Creator:


/**
 * @version mochuan.zhb on 16/9/28.
 * @Author Zheng Haibo
 * @Blog github.com/nuptboyzhb
 * @Company Alibaba Group
 * @Description 聯(lián)系人 ActionCreator
 */
public class ContactsActionCreator {

    public static final String ADD_ITEM = "AddContacts";
    public static final String ADD_TITLE = "addCategory";
    public static final String DELETE_LAST = "deleteLast";
    public static final String CHECK_BOX = "contactsCheck";

    public static XCoreAction addContacts(Contacts contacts) {
        return new XCoreAction(ADD_ITEM, contacts);
    }

    public static XCoreAction addCategory(Title title) {
        return new XCoreAction(ADD_TITLE, title);
    }

    public static XCoreAction deleteLast() {
        return new XCoreAction(DELETE_LAST);
    }

    public static XCoreAction checkBoxClick(ContactsWrapper contactsWrapper) {
        return new XCoreAction(CHECK_BOX, contactsWrapper);
    }
}

Action的概念比較好理解,下面我們看一下Reducer

Reducer

reducer的字面意思就是“減速器”。Action描述了事件,Reducer是決定如何根據(jù)Action更新狀態(tài)(state),而這正是reducer要做的事情。Reducer的接口定義如下:


public interface IXCoreReducer<State> {
    State reduce(State state, XCoreAction xcoreAction);
}

就是根據(jù)輸入的Action和當(dāng)前的state,處理得到新的state。


(previousState, action) => newState

說的更直白一點,Reducer就是一些列 純函數(shù) 的集合。如Demo中的項目所示:


public class ContactsReducer implements IXCoreReducer<List<XCoreRecyclerAdapter.IDataWrapper>> {

    /**
     * 添加聯(lián)系人
     *
     * @param contactsWrappers
     * @param contacts
     * @return
     */
    private List<XCoreRecyclerAdapter.IDataWrapper> addOneContacts(List<XCoreRecyclerAdapter.IDataWrapper> contactsWrappers, Contacts contacts) {
        ...
        ...
        return wrappers;
    }

    /**
     * 添加標(biāo)題
     *
     * @param contactsWrappers
     * @param value
     * @return
     */
    private List<XCoreRecyclerAdapter.IDataWrapper> addOneTitle(List<XCoreRecyclerAdapter.IDataWrapper> contactsWrappers, Title value) {
        ...
        ...
        return wrappers;
    }

    /**
     * 刪除最后一個
     *
     * @param contactsWrappers
     * @return
     */
    private List<XCoreRecyclerAdapter.IDataWrapper> deleteLast(List<XCoreRecyclerAdapter.IDataWrapper> contactsWrappers) {
        List<XCoreRecyclerAdapter.IDataWrapper> wrappers = new ArrayList<>(contactsWrappers);
        if (wrappers.size() > 0) {
            wrappers.remove(wrappers.size() - 1);
        }
        return wrappers;
    }

    /**
     * 設(shè)置選擇狀態(tài)
     *
     * @param contactsWrappers
     * @param value
     * @return
     */
    private List<XCoreRecyclerAdapter.IDataWrapper> changeCheckBoxStatus(List<XCoreRecyclerAdapter.IDataWrapper> contactsWrappers, ContactsWrapper value) {
        value.isChecked = !value.isChecked;
        return contactsWrappers;
    }

    @Override
    public List<XCoreRecyclerAdapter.IDataWrapper> reduce(List<XCoreRecyclerAdapter.IDataWrapper> contactsWrappers, XCoreAction xcoreAction) {
        switch (xcoreAction.type) {
            case ContactsActionCreator.ADD_ITEM:
                return addOneContacts(contactsWrappers, (Contacts) xcoreAction.value);

            case ContactsActionCreator.ADD_TITLE:
                return addOneTitle(contactsWrappers, (Title) xcoreAction.value);

            case ContactsActionCreator.DELETE_LAST:
                return deleteLast(contactsWrappers);

            case ContactsActionCreator.CHECK_BOX:
                return changeCheckBoxStatus(contactsWrappers, (ContactsWrapper) xcoreAction.value);
            ...
        }
        return contactsWrappers;
    }
}

通過上面的Reducer實現(xiàn),我們可以看出,Reducer就是一些列函數(shù)的集合,其中一個關(guān)鍵函數(shù)reduce,它按照action的不同type執(zhí)行不同的方法處理。

Store

store字面意思是存儲。在Redux框架下,Store和DataBase,File沒有關(guān)系,它可不是持久化存儲的意思。Store是負(fù)責(zé)管理數(shù)據(jù)源的狀態(tài),負(fù)責(zé)把Action和Reducer聯(lián)系到一起。Store的職責(zé)為:

  • 1.保存數(shù)據(jù)源的當(dāng)前狀態(tài)state
  • 2.對外提供dispatch方法,更新state
  • 3.通過subscribe注冊監(jiān)聽器,當(dāng)state變化時,通知觀察者
  • 4.提供getState方法,獲取當(dāng)前的state

Store的Java實現(xiàn):


public class XCoreStore<State> {
    private final IXCoreReducer<State> mIXCoreReducer;//數(shù)據(jù)處理器-reducer
    private final List<IStateChangeListener<State>> listeners = new ArrayList<>();//觀察者
    private volatile State state;//Store存儲的數(shù)據(jù)

    private XCoreStore(IXCoreReducer<State> mIXCoreReducer, State state) {
        this.mIXCoreReducer = mIXCoreReducer;
        this.state = state;
    }

    /**
     * 內(nèi)部dispatch
     *
     * @param xCoreAction
     */
    private void dispatchAction(final XCoreAction xCoreAction) throws Throwable {
        synchronized (this) {
            state = mIXCoreReducer.reduce(state, xCoreAction);
        }
        for (IStateChangeListener<State> listener : listeners) {
            listener.onStateChanged(state);
        }
    }


    /**
     * 創(chuàng)建Store
     *
     * @param reducer
     * @param initialState
     * @param <S>
     * @return
     */
    public static <S> XCoreStore<S> create(IXCoreReducer<S> reducer, S initialState) {
        return new XCoreStore<>(reducer, initialState);
    }

    public State getState() {
        return state;
    }


    public void dispatch(final XCoreAction action) {
        try {
            dispatchAction(action);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * 注冊接口;添加觀察者,當(dāng)state改變時,通知觀察者
     *
     * @param listener
     */
    public void subscribe(final IStateChangeListener<State> listener) {
        listeners.add(listener);
    }

    /**
     * 注銷
     *
     * @param listener
     */
    public void unSubscribe(final IStateChangeListener<State> listener) {
        listeners.remove(listener);
    }

    /**
     * 狀態(tài)改變的回調(diào)接口
     *
     * @param <S> 狀態(tài)
     */
    public interface IStateChangeListener<S> {
        void onStateChanged(S state);
    }

}

在Android中,一個Redux頁面(Fragment或者Activity) 只有一個單一的 store。當(dāng)需要拆分?jǐn)?shù)據(jù)處理邏輯時,應(yīng)該使用 reducer組合,而不是創(chuàng)建多個Store。

搭配UIComponent

與前端的Redux搭配React類似,XCoreRedux搭配UIComponent。

UI組件化(UIComponent)

在前段的React框架下,我們常常聽說組件的概念:‘UI組件’。那么什么是UI組件呢?以下圖為例:

xcoreredux_demo.png

紅色的區(qū)域為“普通組件”,綠色的區(qū)域為兩種不同類型的“Item組件”。因此,在UIComponent里,組件分兩種:普通組件和item組件(或稱為cell組件)。

普通組件

  • 單組件,比如一個自定義的Widget,就是一樣View。比如自定義的CircleImageView等。
  • 容器組件,由ViewGroup派生出的組件。有FrameLayout、LinearLayout、RelativeLayout等。還有些常見的列表組件,比如ListView或者RecyclerView的組件等。

普通組件在XCore中是以FrameLayout的形式封裝的,編寫一個普通組件只需要實現(xiàn)如下方法:

  • 1.public int getLayoutResId()

    返回組件的布局資源Id
  • 2.public void onViewCreated(View view)

    View初始化
  • 3.實現(xiàn)XCoreStore中的IStateChangeListener接口,在onStateChanged中做數(shù)據(jù)綁定
    為了使UI組件能夠與Store進(jìn)行關(guān)聯(lián),UI組件可以實現(xiàn)IStateChangeListener接口,然后作為觀察者,觀察Store的state變化。然后在onStateChanged方法中做數(shù)據(jù)綁定。

Item組件(Cell組件)

對于前端來說,item組件和普通組件并沒有什么不同。但是對于Android或者iOS而言,item組件和普通組件是有本質(zhì)區(qū)別的。以ReyclerView為例,Item組件在同一種類型下是會復(fù)用的。在XCoreRedux框架中,定義Item組件,需要繼承自XCoreItemUIComponent,它本身并不是一個View。它只需要實現(xiàn)的方法有:

  • View onCreateView(LayoutInflater inflater,ViewGroup container);
    與Fragment的onCreateView類似,它負(fù)責(zé)創(chuàng)建item的布局View
  • void onViewCreated(View view);
    與Fragment的onViewCreated類似,在此寫View的初始化
  • public String getViewType();
    Item組件對于數(shù)據(jù)源的類型
  • public void bindView(IXCoreComponent coreComponent,
    XCoreRecyclerAdapter coreRecyclerAdapter,
    XCoreRecyclerAdapter.IDataWrapper data,
    int pos);
    數(shù)據(jù)綁定,當(dāng)Adapter調(diào)用bindViewHolder時,會回調(diào)bindView方法。

Item組件需要通過Adapter,與對應(yīng)的列表組件聯(lián)系起來。針對Android常用的RecyclerView,XCoreRedux提供了插件式的通用XCoreRecyclerAdapter。

含列表組件下的XCoreRedux框架圖

XCoreRedux20161002.png

與之前的不同之處在于,這里把整個列表封裝成一個列表組件,對外提供注冊Item,比如XCoreRecyclerViewComponent組件源碼。


public class XCoreRecyclerViewComponent extends XCoreUIBaseComponent implements XCoreStore.IStateChangeListener<List<XCoreRecyclerAdapter.IDataWrapper>> {

    private SwipeRefreshLayout mSwipeRefreshLayout;

    private RecyclerView mRecyclerView;
    private RecyclerView.LayoutManager mLayoutManager;
    private XCoreRecyclerAdapter mXCoreRecyclerAdapter;

    public XCoreRecyclerViewComponent(Context context) {
        super(context);
    }

    public XCoreRecyclerViewComponent(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public XCoreRecyclerViewComponent(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public final int getLayoutResId() {
        return R.layout.xcore_recyclerview_component;
    }

    @Override
    public void onViewCreated(View view) {
        //初始化View
        mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.xcore_refresh_layout);
        mSwipeRefreshLayout.setEnabled(false);
        mRecyclerView = (RecyclerView) findViewById(R.id.xcore_rv);
        //初始化RecyclerView
        mLayoutManager = new LinearLayoutManager(getContext());
        mRecyclerView.setLayoutManager(mLayoutManager);
        mXCoreRecyclerAdapter = new XCoreRecyclerAdapter(this);
        mRecyclerView.setAdapter(mXCoreRecyclerAdapter);
    }

    public SwipeRefreshLayout getSwipeRefreshLayout() {
        return mSwipeRefreshLayout;
    }

    public RecyclerView getRecyclerView() {
        return mRecyclerView;
    }

    public RecyclerView.LayoutManager getLayoutManager() {
        return mLayoutManager;
    }

    public XCoreRecyclerAdapter getXCoreRecyclerAdapter() {
        return mXCoreRecyclerAdapter;
    }

    /**
     * 當(dāng)狀態(tài)發(fā)生變化時,自動通知
     *
     * @param status
     */
    @Override
    public void onStateChanged(List<XCoreRecyclerAdapter.IDataWrapper> status) {
        mXCoreRecyclerAdapter.setDataSet(status);
        mXCoreRecyclerAdapter.notifyDataSetChanged();
    }

    /**
     * 對外提供item組件的注冊
     *
     * @param xCoreItemUIComponent
     * @return
     */
    public XCoreRecyclerViewComponent registerItemComponent(XCoreItemUIComponent xCoreItemUIComponent) {
        mXCoreRecyclerAdapter.registerItemUIComponent(xCoreItemUIComponent);
        return this;
    }

    public void setRefreshEnable(boolean enable) {
        mSwipeRefreshLayout.setEnabled(enable);
    }
}

我們在使用該組件時,只需要:

1.在XML中添加組件


<?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:orientation="vertical">
    <!-- 頭部組件-->
    <com.example.haibozheng.myapplication.components.container.HeaderComponent
        android:id="@+id/recycler_view_header_component"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <!-- 列表組件-->
    <com.github.nuptboyzhb.xcore.components.impl.XCoreRecyclerViewComponent
        android:id="@+id/recycler_view_component"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

2.初始化

        
        ...
        //創(chuàng)建數(shù)據(jù)源的store
        mContactsListXCoreStore = XCoreStore.create(new ContactsReducer(), new ArrayList<XCoreRecyclerAdapter.IDataWrapper>());

        //創(chuàng)建RecyclerView的UI組件
        mXCoreRecyclerViewComponent = (XCoreRecyclerViewComponent) findViewById(R.id.recycler_view_component);
        //注冊item組件模板
        mXCoreRecyclerViewComponent.registerItemComponent(new TextItemComponent())
                .registerItemComponent(new ImageItemComponent());

        //創(chuàng)建頭部組件
        mHeaderComponent = (HeaderComponent) findViewById(R.id.recycler_view_header_component);

        //添加觀察者
        mContactsListXCoreStore.subscribe(mXCoreRecyclerViewComponent);
        mContactsListXCoreStore.subscribe(mHeaderComponent);
        ...

組件之間通信

Item組件與列表組件及普通組件之間的通信。在本Demo中使用的EventBus是輕量級的otto。每一個繼承自XCoreUIBaseComponent的組件,都已經(jīng)在onCreate和onDestroy中分別進(jìn)行了注冊和反注冊。使用時,只需要使用@Subscribe 注解來指定訂閱方法。因此,在任意地方都可以調(diào)用:

XCoreBus.getInstance().post(action);

小優(yōu)化

對于數(shù)據(jù)綁定方面,做了兩個優(yōu)化:
1.把數(shù)據(jù)通過Wrapper包裝
2.使用UIBinderHelper做流式綁定,比如:


public class ImageItemComponent extends XCoreItemUIComponent implements View.OnClickListener {

    private UIBinderHelperImpl mUIBinderHelperImpl;

    ...

    @Override
    public void bindView(IXCoreComponent coreComponent,
                         XCoreRecyclerAdapter coreRecyclerAdapter,
                         XCoreRecyclerAdapter.IDataWrapper data,
                         int pos) {
        mContactsWrapper = (ContactsWrapper) data;
        mUIBinderHelperImpl.from(R.id.item_content_tv).setText(mContactsWrapper.bindContentText())
                .from(R.id.item_pic_iv).setImageUrl(mContactsWrapper.getAvatarUrl())
                .from(R.id.item_title_tv).setText(mContactsWrapper.bindItemTitle())
                .from(R.id.checkbox).setButtonDrawable(mContactsWrapper.isChecked ? R.mipmap.checkbox_checked : R.mipmap.checkbox_normal)
                .setOnClickListener(this);
    }

    ...
}

后續(xù)

  • 1.異步
  • 2.Middleware中間件
  • 3.與Rx結(jié)合

參考文獻(xiàn)

License

Copyright 2016 Zheng Haibo

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,746評論 2 370

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,598評論 25 707
  • 學(xué)習(xí)必備要點: 首先弄明白,Redux在使用React開發(fā)應(yīng)用時,起到什么作用——狀態(tài)集中管理 弄清楚Redux是...
    賀賀v5閱讀 8,924評論 10 58
  • afinalAfinal是一個android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,461評論 2 45
  • 這本書講的主題是:如何利用規(guī)律和趨勢,放大個人努力。 第一章,談到了網(wǎng)絡(luò)、人工智能對于學(xué)習(xí)方式、思考方式和競爭力的...
    責(zé)任與貢獻(xiàn)閱讀 292評論 1 2
  • 1.小D看完了《佳期如夢》后感慨道“嫁人當(dāng)嫁阮正東。” 小Y則是每看完一本匪大的書,都感慨,“要是能嫁給XXX(書...
    元小缺閱讀 303評論 0 1