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框架的圖示如下:
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組件呢?以下圖為例:
紅色的區(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框架圖
與之前的不同之處在于,這里把整個列表封裝成一個列表組件,對外提供注冊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.