XCoreRedux框架:Android UI組件化與Redux實(shí)踐

XCoreRedux框架:Android UI組件化與Redux實(shí)踐

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

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

使用android studio打開該項(xiàng)目。

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

  • demo

    基于xcore框架寫的一個(gè)小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的思想,提出了一個(gè)基于Android平臺(tái)Redux框架,我給它起名叫作:XCoreRedux。本倉庫就是XCoreRedux+UIComponent框架的實(shí)現(xiàn)。它表達(dá)的是一種思想,希望大家能夠提出更好的意見。

XCoreRedux框架介紹

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

redux20160930.png

Action

Action 是把數(shù)據(jù)傳到 store 的有效載體。它是store的唯一數(shù)據(jù)來源。我們一般是通過 store.dispatch()將action傳到store中。Action一般需要兩個(gè)參數(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,你可以實(shí)現(xiàn)一個(gè)ActionCreator,比如,demo中創(chuàng)建了一個(gè)聯(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

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


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

    /**
     * 刪除最后一個(gè)
     *
     * @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實(shí)現(xiàn),我們可以看出,Reducer就是一些列函數(shù)的集合,其中一個(gè)關(guān)鍵函數(shù)reduce,它按照action的不同type執(zhí)行不同的方法處理。

Store

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

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

Store的Java實(shí)現(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存儲(chǔ)的數(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();
        }
    }

    /**
     * 注冊(cè)接口;添加觀察者,當(dāng)state改變時(shí),通知觀察者
     *
     * @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中,一個(gè)Redux頁面(Fragment或者Activity) 只有一個(gè)單一的 store。當(dāng)需要拆分?jǐn)?shù)據(jù)處理邏輯時(shí),應(yīng)該使用 reducer組合,而不是創(chuàng)建多個(gè)Store。

搭配UIComponent

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

UI組件化(UIComponent)

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

xcoreredux_demo.png

紅色的區(qū)域?yàn)椤捌胀ńM件”,綠色的區(qū)域?yàn)閮煞N不同類型的“Item組件”。因此,在UIComponent里,組件分兩種:普通組件和item組件(或稱為cell組件)。

普通組件

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

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

  • 1.public int getLayoutResId()

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

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

Item組件(Cell組件)

對(duì)于前端來說,item組件和普通組件并沒有什么不同。但是對(duì)于Android或者iOS而言,item組件和普通組件是有本質(zhì)區(qū)別的。以ReyclerView為例,Item組件在同一種類型下是會(huì)復(fù)用的。在XCoreRedux框架中,定義Item組件,需要繼承自XCoreItemUIComponent,它本身并不是一個(gè)View。它只需要實(shí)現(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組件對(duì)于數(shù)據(jù)源的類型
  • public void bindView(IXCoreComponent coreComponent,
    XCoreRecyclerAdapter coreRecyclerAdapter,
    XCoreRecyclerAdapter.IDataWrapper data,
    int pos);
    數(shù)據(jù)綁定,當(dāng)Adapter調(diào)用bindViewHolder時(shí),會(huì)回調(diào)bindView方法。

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

含列表組件下的XCoreRedux框架圖

XCoreRedux20161002.png

與之前的不同之處在于,這里把整個(gè)列表封裝成一個(gè)列表組件,對(duì)外提供注冊(cè)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ā)生變化時(shí),自動(dòng)通知
     *
     * @param status
     */
    @Override
    public void onStateChanged(List<XCoreRecyclerAdapter.IDataWrapper> status) {
        mXCoreRecyclerAdapter.setDataSet(status);
        mXCoreRecyclerAdapter.notifyDataSetChanged();
    }

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

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

我們?cè)谑褂迷摻M件時(shí),只需要:

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);
        //注冊(cè)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是輕量級(jí)的otto。每一個(gè)繼承自XCoreUIBaseComponent的組件,都已經(jīng)在onCreate和onDestroy中分別進(jìn)行了注冊(cè)和反注冊(cè)。使用時(shí),只需要使用@Subscribe 注解來指定訂閱方法。因此,在任意地方都可以調(diào)用:

XCoreBus.getInstance().post(action);

小優(yōu)化

對(duì)于數(shù)據(jù)綁定方面,做了兩個(gè)優(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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容