近段時間一直在加班,在趕一個項目,如今項目接近尾聲,那麼須要對過去一段時間工做內容進行復盤,總結下比較好的解決方案,積累一些經驗,我認爲的學習方式,是「理論—實踐—總結—分享」,這一種很好的沉澱方式。html
在以前項目中,有個需求是這樣的,要顯示書的閱讀足跡列表,具體要求是顯示最近30天閱讀狀況,佈局是用列表項佈局,而後若是有更早的書,則顯示更早的閱讀狀況,佈局是用網格佈局,如圖所示:java
要是放在以前的作法,通常都是ListView,再對特殊item樣式進行單獨處理,後來Android在5.0的時候出了一個RecyclerView組件,簡單介紹下RecyclerView,一句話:只管回收與複用View,其餘的你能夠本身去設置,有着高度的解耦,充分的擴展性。至於用法,你們能夠去官網查看文檔便可,網上也不少文章介紹如何使用,這裏很少說。想講的重點是關於裝飾者模式如何在RecyclerView中應用,以下:android
定義:Decorator模式(別名Wrapper),動態將職責附加到對象上,若要擴展功能,裝飾者提供了比繼承更具彈性的代替方案。設計模式
也就是說動態地給一個對象添加一些額外的職責,好比你能夠增長功能,相比繼承來講,有些父類的功能我是不須要的,我可能只用到某部分功能,那麼我就能夠自由組合,這樣就顯得靈活點,而不是那麼冗餘。性能優化
有幾個要點:app
UML圖:框架
咱們既然知道了裝飾者和被裝飾對象有相同的超類型,在作書的閱讀足跡這個頁面的時候,整個頁面外部是一個RecyclerView,好比這樣:ide
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <com.dracom.android.sfreader.widget.recyclerview.FeedRootRecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:drawSelectorOnTop="true"/> </android.support.v4.widget.SwipeRefreshLayout>
同時每一個Item項裏面又嵌套一個RecyclerView,但外部只有2個Item項,一個Item項表明最近30天要顯示的書的內容,一個Item項是顯示更早書的內容。其中由於涉及到RecyclerView嵌套的問題,因此須要作滑動衝突的相關處理。因此這裏用到自定義擴展的RecyclerView,具體解決滑動衝突代碼以下:工具
@Override public boolean onInterceptTouchEvent(MotionEvent e) { final int action = MotionEventCompat.getActionMasked(e); final int actionIndex = MotionEventCompat.getActionIndex(e); switch (action) { case MotionEvent.ACTION_DOWN: mScrollPointerId = MotionEventCompat.getPointerId(e, 0); mInitialTouchX = (int) (e.getX() + 0.5f); mInitialTouchY = (int) (e.getY() + 0.5f); return super.onInterceptTouchEvent(e); case MotionEventCompat.ACTION_POINTER_DOWN: mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); mInitialTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); mInitialTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); return super.onInterceptTouchEvent(e); case MotionEvent.ACTION_MOVE: { final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); if (index < 0) { return false; } final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); if (getScrollState() != SCROLL_STATE_DRAGGING) { final int dx = x - mInitialTouchX; final int dy = y - mInitialTouchY; final boolean canScrollHorizontally = getLayoutManager().canScrollHorizontally(); final boolean canScrollVertically = getLayoutManager().canScrollVertically(); boolean startScroll = false; if (canScrollHorizontally && Math.abs(dx) > mTouchSlop && (Math.abs(dx) >= Math.abs(dy) || canScrollVertically)) { startScroll = true; } if (canScrollVertically && Math.abs(dy) > mTouchSlop && (Math.abs(dy) >= Math.abs(dx) || canScrollHorizontally)) { startScroll = true; } return startScroll && super.onInterceptTouchEvent(e); } return super.onInterceptTouchEvent(e); } default: return super.onInterceptTouchEvent(e); } }
按照思路就是內部攔截法,也就是RecyclerView本身處理,默認是不攔截,若是滑動距離超過所規定距離,咱們就攔截本身處理,設置是可滾動的狀態。oop
解決完滑動衝突以後,具體看看item項中的佈局:
<?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="wrap_content" android:orientation="vertical" android:paddingBottom="4dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:paddingTop="4dp"> <LinearLayout android:id="@+id/head_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="24dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:textColor="@color/black_alpha" android:textSize="16sp" android:text="@string/zq_account_read_footprint_recent_month"/> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_marginTop="8dp" android:background="@android:drawable/divider_horizontal_bright"/> </LinearLayout> <com.dracom.android.sfreader.widget.recyclerview.BetterRecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="12dp" android:layout_marginTop="8dp" android:orientation="vertical" android:scrollbars="none"/> </LinearLayout>
能夠看到,每一個Item項目一個頭部,一個RecyclerView。而後是Adapter的適配,這裏就是經常使用的RecyclerView Adapter的方式,要繼承RecyclerView.Adapter<RecyclerView.ViewHolder>方法,同時要實現onCreateViewHolder、onBindViewHolder、getItemCount、getItemViewType方法,具體代碼以下:
public class ReadFootPrintAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final static int RECENT_MONTH = 1002; //最近三十天的Item private final static int MORE_EARLY = 1003; //更早的Item private Context mContext; private List<ReadBookColumnInfo> mColumns; private RecentReadAdapter mRecentReadAdapter; private MoreEarlyAdapter mMoreEarlyAdapter; public ReadFootPrintAdapter(Context context){ this.mContext = context; mColumns = new ArrayList<ReadBookColumnInfo>(); mRecentReadAdapter = new RecentReadAdapter(context); mMoreEarlyAdapter = new MoreEarlyAdapter(context); } public void setLoadEnable(boolean loadEnable) { mIsLoadEnable = loadEnable; } public void setColumns(List<ReadBookColumnInfo> columns) { this.mColumns = columns; notifyDataSetChanged(); } public void setRecentReadListener(OnOpenBookListener onOpenBookListener){ mRecentReadAdapter.setOnListener(onOpenBookListener); } public void setMoreEarlyListener(OnOpenBookListener onOpenBookListener){ mMoreEarlyAdapter.setOnListener(onOpenBookListener); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType == RECENT_MONTH){ View view = LayoutInflater.from(mContext).inflate(R.layout.recycler_read_footprint_recent_month,parent,false); return new ColumnViewHolder1(view); } if(viewType == MORE_EARLY){ View view = LayoutInflater.from(mContext).inflate(R.layout.recycler_read_footprint_more_early,parent,false); return new ColumnViewHolder2(view); } else{ return null; } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if(holder instanceof ColumnViewHolder){ ColumnViewHolder columnViewHolder = (ColumnViewHolder) holder; ReadBookColumnInfo readBookColumnInfo = mColumns.get(position); if(readBookColumnInfo.getReadBookInfos().size() > 0){ columnViewHolder.headLayout.setVisibility(View.VISIBLE); } else{ columnViewHolder.headLayout.setVisibility(View.GONE); } columnViewHolder.loadData(readBookColumnInfo); } } @Override public int getItemCount() { return mColumns.size(); } @Override public int getItemViewType(int position) { if (position == 0 ) return RECENT_MONTH; else return MORE_EARLY; } private abstract class ColumnViewHolder extends RecyclerView.ViewHolder { View headLayout; RecyclerView recyclerView; public ColumnViewHolder(View itemView) { super(itemView); headLayout = itemView.findViewById(R.id.head_layout); recyclerView = (RecyclerView) itemView.findViewById(R.id.recycler_view); } abstract void loadData(ReadBookColumnInfo readBookColumnInfo); } private class ColumnViewHolder1 extends ColumnViewHolder { public ColumnViewHolder1(View itemView) { super(itemView); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); recyclerView.setLayoutManager(linearLayoutManager); recyclerView.setAdapter(mRecentReadAdapter); } @Override void loadData(ReadBookColumnInfo readBookColumnInfo) { mRecentReadAdapter.setData(readBookColumnInfo.getReadBookInfos()); } } private class ColumnViewHolder2 extends ColumnViewHolder { public ColumnViewHolder2(View itemView) { super(itemView); GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext,3); gridLayoutManager.setOrientation(GridLayoutManager.VERTICAL); recyclerView.setLayoutManager(gridLayoutManager); recyclerView.setAdapter(mMoreEarlyAdapter); } @Override void loadData(ReadBookColumnInfo readBookColumnInfo) { mMoreEarlyAdapter.setData(readBookColumnInfo.getReadBookInfos()); } } }
原本到這裏,基本功能是完成了,可後來產品說要加個上下刷新,加載更多的操做。需求是隨時可變的, 咱們能不變的就是修改的心,那應該怎麼作合適呢,是再增長itemType類型,加個加載更多的item項,那樣修改的點會更多,此時想到了裝飾者模式,是否是能夠有個裝飾類對這個adapter類進行組合呢,這樣不須要修改原來的代碼,只要擴展出去,何況咱們知道都須要繼承RecyclerView.Adapter,那麼就能夠把ReadFootPrintAdapter當作一個內部成員設置進入。咱們來看下裝飾者類:
public class ReadFootPrintAdapterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final static int LOADMORE = 1001; private final static int NORMAL = 1002; private RecyclerView.Adapter internalAdapter; private View mFooterView; public ReadFootPrintAdapterWrapper(RecyclerView.Adapter adapter){ this.internalAdapter = adapter; this.mFooterView = null; } public void addFooterView(View footView) { mFooterView = footView; } public void notifyDataChanged() { internalAdapter.notifyDataSetChanged(); notifyDataSetChanged(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType == LOADMORE){ return new LoadMoreViewHolder(mFooterView); } return internalAdapter.createViewHolder(parent,viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if(holder instanceof LoadMoreViewHolder){ return; }else{ internalAdapter.onBindViewHolder(holder,position); } } @Override public int getItemCount() { int count = internalAdapter.getItemCount(); if (mFooterView != null && count != 0) count++; return count; } @Override public int getItemViewType(int position) { if(mFooterView != null && getItemCount() - 1 == position) return LOADMORE; return NORMAL; } public class LoadMoreViewHolder extends RecyclerView.ViewHolder { public LoadMoreViewHolder(View itemView) { super(itemView); } } }
這個Wrapper類就是裝飾類,裏面包含了一個RecyclerView.Adapter類型的成員,一個底部View,到時候在外部調用的時候,只須要傳遞一個RecyclerView.Adapter類型的參數進去便可,這樣就造成了組合的關係。具體使用以下:
mFooterView = LayoutInflater.from(mContext).inflate(R.layout.refresh_loadmore_layout, mRecyclerView, false); mReadFootPrintAdapterWrapper = new ReadFootPrintAdapterWrapper(mReadFootPrintAdapter); mReadFootPrintAdapterWrapper.addFooterView(mFooterView); mRecyclerView.setAdapter(mReadFootPrintAdapterWrapper);
這樣即達到需求要求,又能對原來已有的代碼不進行修改,只進行擴展,何樂而不爲。
這雖然是工做中一個應用點,但我想在開發過程當中還有不少應用點,用上設計模式。平常開發中基本都強調設計模式的重要性,或許你對23種設計模式都很熟悉,都瞭解到它們各自的定義,但是等真正應用了,卻發現沒有蹤影可尋,寫代碼也是按照之前老的思路去作,那樣就變成了知道是知道,卻不會用的尷尬局面。如何突破呢,我以爲過後覆盤和重構頗有必要,就是利用項目尾聲階段,空的時候去review下本身寫過的代碼,反思是否有更簡潔的寫法,還有能夠參考優秀代碼,它們是怎麼寫,這樣給本身找找靈感,再去結合本身已有的知識存儲,說不定就能走上理論和實踐相結合道路上。
源於對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中能夠看到技術積累的過程。
1,Android系統簡介
2,ProGuard代碼混淆
3,講講Handler+Looper+MessageQueue關係
4,Android圖片加載庫理解
5,談談Android運行時權限理解
6,EventBus初理解
7,Android 常見工具類
8,對於Fragment的一些理解
9,Android 四大組件之 " Activity "
10,Android 四大組件之" Service "
11,Android 四大組件之「 BroadcastReceiver "
12,Android 四大組件之" ContentProvider "
13,講講 Android 事件攔截機制
14,Android 動畫的理解
15,Android 生命週期和啓動模式
16,Android IPC 機制
17,View 的事件體系
18,View 的工做原理
19,理解 Window 和 WindowManager
20,Activity 啓動過程分析
21,Service 啓動過程分析
22,Android 性能優化
23,Android 消息機制
24,Android Bitmap相關
25,Android 線程和線程池
26,Android 中的 Drawable 和動畫
27,RecylerView 中的裝飾者模式
28,Android 觸摸事件機制
29,Android 事件機制應用
30,Cordova 框架的一些理解
31,有關 Android 插件化思考
32,開發人員必備技能——單元測試