Android | Tangram動態頁面之路(四)vlayout原理

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本系列文章主要介紹天貓團隊開源的"},{"type":"link","attrs":{"href":"https://github.com/alibaba/Tangram-Android.git","title":""},"content":[{"type":"text","text":"Tangram"}]},{"type":"text","text":"框架的使用心得和原理,由於"},{"type":"codeinline","content":[{"type":"text","text":"Tangram"}]},{"type":"text","text":"底層基於"},{"type":"link","attrs":{"href":"https://github.com/alibaba/vlayout.git","title":""},"content":[{"type":"text","text":"vlayout"}]},{"type":"text","text":",所以也會簡單講解,該系列將按以下大綱進行介紹:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/e6c67e3f493e378edf74312e1","title":""},"content":[{"type":"text","text":"需求背景"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"2","normalizeStart":"2"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/de3f045f7390b848fa70f0c82","title":""},"content":[{"type":"text","text":"Tangram和vlayout介紹"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"3","normalizeStart":"3"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/b6b7c82140c3b6c804586e26a","title":""},"content":[{"type":"text","text":"Tangram的使用"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"4","normalizeStart":"4"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"vlayout原理"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"5","normalizeStart":"5"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"Tangram原理"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"6","normalizeStart":"6"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"Tangram二次封裝"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文將對"},{"type":"codeinline","content":[{"type":"text","text":"Tangram"}]},{"type":"text","text":"的底層實現"},{"type":"codeinline","content":[{"type":"text","text":"vlayout"}]},{"type":"text","text":"進行講解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/alibaba/vlayout/commit/f394eff9beb5b13872d4637fe148e767f2f0320d","title":""},"content":[{"type":"text","text":"基於vlayout最新源碼"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"vlayout"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"link","attrs":{"href":"https://juejin.im/post/5eba9113f265da7bd442576f","title":""},"content":[{"type":"text","text":"Tangram和vlayout介紹"}]},{"type":"text","text":"這篇文章提到過,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"vlayout自定義了一個VirtualLayoutManager,它繼承自 LinearLayoutManager;引入了 LayoutHelper 的概念,它負責具體的佈局邏輯;VirtualLayoutManager管理了一系列LayoutHelper,將具體的佈局能力交給LayoutHelper來完成,每一種LayoutHelper提供一種佈局方式,框架內置提供了幾種常用的佈局類型,包括:網格佈局、線性佈局、瀑布流佈局、懸浮佈局、吸邊佈局等。這樣實現了混合佈局的能力,並且支持擴展外部,註冊新的LayoutHelper,實現特殊的佈局方式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"引用自"},{"type":"link","attrs":{"href":"http://pingguohe.net/2017/02/28/vlayout-design.html","title":""},"content":[{"type":"text","text":"蘋果核 - Tangram 的基礎 —— vlayout(Android)"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大致意思是這樣,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/eb/ebb5d155c23898b27b0bec8dc747abb4.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"codeinline","content":[{"type":"text","text":"VLayoutActivity"}]},{"type":"text","text":"中,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//VLayoutActivity.java\nvoid onCreate(Bundle savedInstanceState) {\n if (FLOAT_LAYOUT) {\n //創建佈局方式layoutHelper,FloatLayoutHelper是浮動可拖拽佈局,比如微信現在的浮窗功能\n FloatLayoutHelper layoutHelper = new FloatLayoutHelper();\n //設置初始位置爲右下角\n layoutHelper.setAlignType(FixLayoutHelper.BOTTOM_RIGHT);\n //設置偏移量,位置是右下角時,分別是marginRight和marginBottom\n layoutHelper.setDefaultLocation(100, 400);\n //設置寬高\n LayoutParams layoutParams = new LayoutParams(150, 150);\n //創建子適配器,添加進適配器集合\n adapters.add(new SubAdapter(this, layoutHelper, 1, layoutParams));\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來到子適配器"},{"type":"codeinline","content":[{"type":"text","text":"SubAdapter"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//繼承DelegateAdapter.Adapter\nclass SubAdapter extends DelegateAdapter.Adapter {\n private LayoutHelper mLayoutHelper;\n \n public SubAdapter(Context context, LayoutHelper layoutHelper, int count, LayoutParams layoutParams) {\n this.mContext = context;\n this.mLayoutHelper = layoutHelper;\n this.mCount = count;\n this.mLayoutParams = layoutParams;\n }\n\n @Override\n public LayoutHelper onCreateLayoutHelper() {\n //把傳進來的佈局方式LayoutHelper返回\n return mLayoutHelper;\n }\n\n //創建ViewHolder\n public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n return new MainViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false));\n }\n\n //綁定ViewHolder\n protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) {\n ((TextView) holder.itemView.findViewById(R.id.title)).setText(Integer.toString(offsetTotal));\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"codeinline","content":[{"type":"text","text":"delegateAdapter.setAdapters(adapters)"}]},{"type":"text","text":"時,取出適配器指定的佈局方式,進行透傳,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//DelegateAdapter.java\npublic void setAdapters(List adapters) {\n List helpers = new LinkedList<>();\n for (Adapter adapter : adapters) {\n LayoutHelper helper = adapter.onCreateLayoutHelper();\n helpers.add(helper);\n }\n super.setLayoutHelpers(helpers);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來到"},{"type":"codeinline","content":[{"type":"text","text":"VirtualLayoutManager"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//VirtualLayoutManager.java\nvoid setLayoutHelpers(@Nullable List helpers) {\n //設置每個佈局方式LayoutHelper的管轄範圍start和end\n //假設第1個模塊是ColumnLayoutHelper,有3個元素,則管轄範圍是[0,2]\n //第2個模塊是OnePlusNLayoutHelper,有4個元素,則管轄範圍是[3,6]\n if (helpers != null) {\n int start = 0;\n Iterator it1 = helpers.iterator();\n while (it1.hasNext()) {\n LayoutHelper helper = it1.next();\n if (helper.getItemCount() > 0) {\n helper.setRange(start, start + helper.getItemCount() - 1);\n } else {\n helper.setRange(-1, -1);\n }\n start += helper.getItemCount();\n }\n }\n //內部進行賦值和排序,RangeLayoutHelperFinder可以根據位置查找對應的LayoutHelper\n this.mHelperFinder.setLayouts(helpers);\n requestLayout();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"LayoutHelper"}]},{"type":"text","text":"被賦值好後,進行佈局,這裏暫不深究"},{"type":"codeinline","content":[{"type":"text","text":"View"}]},{"type":"text","text":"的測量佈局繪製流程,來到"},{"type":"codeinline","content":[{"type":"text","text":"VirtualLayoutManager.onLayoutChildren"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//VirtualLayoutManager.java\nvoid onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n //預佈局,也就是調用每個ayoutHelper的beforeLayout\n runPreLayout(recycler, state);\n super.onLayoutChildren(recycler, state);\n}\n\n//ExposeLinearLayoutManagerEx.java\nvoid onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n fill(recycler, mLayoutState, state, false);\n}\n\nint fill(RecyclerView.Recycler recycler, LayoutState layoutState,\n RecyclerView.State state, boolean stopOnFocusable) {\n layoutChunk(recycler, state, layoutState, layoutChunkResultCache);\n}\n\n//VirtualLayoutManager.java\nvoid layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, \n LayoutState layoutState, \n com.alibaba.android.vlayout.layout.LayoutChunkResult result) {\n //RangeLayoutHelperFinder根據位置查找對應的的佈局方式LayoutHelper\n final int position = layoutState.mCurrentPosition;\n LayoutHelper layoutHelper = mHelperFinder == null ? null : mHelperFinder.getLayoutHelper(position);\n layoutHelper.doLayout(recycler, state, mTempLayoutStateWrapper, result, this);\n}\n\n//BaseLayoutHelper.java\nvoid doLayout(RecyclerView.Recycler recycler, RecyclerView.State state, \n LayoutStateWrapper layoutState, LayoutChunkResult result, \n LayoutManagerHelper helper) {\n //觸發每個具體的LayoutHelper進行測量和佈局\n layoutViews(recycler, state, layoutState, result, helper);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體的測量和佈局的實現"},{"type":"codeinline","content":[{"type":"text","text":"layoutViews"}]},{"type":"text","text":",我們舉兩個比較典型的佈局方式分析,"},{"type":"codeinline","content":[{"type":"text","text":"ColumnLayoutHelper"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"FloatLayoutHelper"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"舉例ColumnLayoutHelper列布局"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設置比重,第一列和第四列佔比33,中間兩列不指定比重,則平分剩餘空間,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"layoutHelper.setWeights(new float[]{33f, Float.NaN, Float.NaN, 33f});"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"效果如下,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ef/ef022f21608b5a5719ea4808f771de7a.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來看"},{"type":"codeinline","content":[{"type":"text","text":"layoutViews"}]},{"type":"text","text":"方法,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//ColumnLayoutHelper.java\nvoid layoutViews(RecyclerView.Recycler recycler, RecyclerView.State state, \n VirtualLayoutManager.LayoutStateWrapper layoutState, \n LayoutChunkResult result, LayoutManagerHelper helper) {\n final int count = getAllChildren(mViews, recycler, layoutState, result, helper);\n //1. 計算每個child的margin\n \n //2. 用總寬度和百分比爲child分配寬高,沒有設置百分比的child先存儲進mEqViews\n for (int i = 0; i < count; i++) {\n View view = mViews[i];\n VirtualLayoutManager.LayoutParams params = (VirtualLayoutManager.LayoutParams) view.getLayoutParams();\n int heightSpec = helper.getChildMeasureSpec(\n helper.getContentHeight() - helper.getPaddingTop() - helper.getPaddingBottom(),\n uniformHeight > 0 ? uniformHeight : params.height, true);\n if (mWeights != null && i < mWeights.length && !Float.isNaN(mWeights[i]) && mWeights[i] >= 0) {\n //根據百分比計算寬度\n int resizeWidth = (int) (mWeights[i] * 1.0f / 100 * availableWidth + 0.5f);\n //根據寬度和比例計算高度\n if (!Float.isNaN(params.mAspectRatio)) {\n int specialHeight = (int) (resizeWidth / params.mAspectRatio + 0.5f);\n heightSpec = View.MeasureSpec\n .makeMeasureSpec(specialHeight, View.MeasureSpec.EXACTLY);\n }\n helper.measureChildWithMargins(view, View.MeasureSpec.makeMeasureSpec(resizeWidth, View.MeasureSpec.EXACTLY), heightSpec);\n //記錄已使用寬度\n usedWidth += resizeWidth;\n //記錄最小高度\n minHeight = Math.min(minHeight, view.getMeasuredHeight());\n } else {\n mEqViews[eqSize++] = view;\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.將剩餘寬度平分給沒有設置百分比的child,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//ColumnLayoutHelper.java\nfor (int i = 0; i < eqSize; i++) {\n View view = mEqViews[i];\n VirtualLayoutManager.LayoutParams params = (VirtualLayoutManager.LayoutParams) view.getLayoutParams();\n int heightSpec;\n int resizeWidth = (int) ((availableWidth - usedWidth) * 1.0f / eqSize + 0.5f);\n //根據寬度和比例計算高度\n if (!Float.isNaN(params.mAspectRatio)) {\n int specialHeight = (int) (resizeWidth / params.mAspectRatio + 0.5f);\n heightSpec = View.MeasureSpec\n .makeMeasureSpec(specialHeight, View.MeasureSpec.EXACTLY);\n } else {\n heightSpec = helper.getChildMeasureSpec(\n helper.getContentHeight() - helper.getPaddingTop() - helper.getPaddingBottom(),\n uniformHeight > 0 ? uniformHeight : params.height, true);\n }\n helper.measureChildWithMargins(view, View.MeasureSpec.makeMeasureSpec(resizeWidth, View.MeasureSpec.EXACTLY),\n heightSpec);\n //記錄最小高度\n minHeight = Math.min(minHeight, view.getMeasuredHeight());\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.爲所有child統一高度,爲最小高度"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//ColumnLayoutHelper.java\nfor (int i = 0; i < count; i++) {\n View view = mViews[i];\n if (view.getMeasuredHeight() != minHeight) {\n helper.measureChildWithMargins(view, View.MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(), View.MeasureSpec.EXACTLY),\n View.MeasureSpec.makeMeasureSpec(minHeight, View.MeasureSpec.EXACTLY));\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5.測量完成,進行佈局,最終交給"},{"type":"codeinline","content":[{"type":"text","text":"RecyclerView.LayoutManager"}]},{"type":"text","text":"進行處理,即"},{"type":"codeinline","content":[{"type":"text","text":"layoutDecorated"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//ColumnLayoutHelper.java\nfor (int i = 0; i < count; i++) {\n View view = mViews[i];\n int top = mTempArea.top, bottom = mTempArea.bottom;\n int right = left + orientationHelper.getDecoratedMeasurementInOther(view);\n layoutChildWithMargin(view, left, top, right, bottom, helper);\n left = right;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"舉例FloatLayoutHelper浮動可拖拽佈局"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"FloatLayoutHelper"}]},{"type":"text","text":"的佈局代碼就不看了,大概就是根據位置和偏移量計算具體位置,我們重點關注下他的觸摸事件實現,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//FloatLayoutHelper.java\nView.OnTouchListener touchDragListener = new View.OnTouchListener() {\n boolean onTouch(View v, MotionEvent event) {\n int action = event.getAction();\n switch (action) {\n case MotionEvent.ACTION_DOWN:\n isDrag = false;\n //按下,讓父view RecyclerView不要攔截事件\n (v.getParent()).requestDisallowInterceptTouchEvent(true);\n lastPosX = (int) event.getX();\n lastPosY = (int) event.getY();\n break;\n case MotionEvent.ACTION_MOVE:\n if (Math.abs(event.getX() - lastPosX) > mTouchSlop\n || Math.abs(event.getY() - lastPosY) > mTouchSlop) {\n isDrag = true;\n }\n if (isDrag) {\n //...\n //不斷更新座標,實現移動效果\n v.setTranslationX(curTranslateX);\n v.setTranslationY(curTranslateY);\n }\n break;\n case MotionEvent.ACTION_UP:\n case MotionEvent.ACTION_CANCEL:\n //擡起或取消,播放吸邊動畫,即自動彈回兩側\n doPullOverAnimation(v);\n //讓父view RecyclerView恢復攔截事件\n (v.getParent()).requestDisallowInterceptTouchEvent(false);\n break;\n }\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"效果如下,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9e/9e3fb8f0e71378cfab5f53711671340c.gif","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"RecyclerView複用和Cantor函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"RecyclerView"}]},{"type":"text","text":"最終使用的是管理子適配器集合的"},{"type":"codeinline","content":[{"type":"text","text":"DelegateAdapter"}]},{"type":"text","text":",通常情況下,我們是沒法保證各個子適配器間的"},{"type":"codeinline","content":[{"type":"text","text":"viewType"}]},{"type":"text","text":"能不衝突的,所以這裏只分析"},{"type":"codeinline","content":[{"type":"text","text":"hasConsistItemType=false"}]},{"type":"text","text":"的情況,具體原因見"},{"type":"link","attrs":{"href":"http://pingguohe.net/2017/08/31/vlayout-faq.html","title":""},"content":[{"type":"text","text":"FAQ"}]},{"type":"text","text":"(組件複用的問題),"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//DelegateAdapter.java\n\n@Override\npublic int getItemViewType(int position) {\n Pair p = findAdapterByPosition(position);\n //子適配器的viewType作爲subItemType\n int subItemType = p.second.getItemViewType(position - p.first.mStartPosition);\n //佈局方式LayoutHelper的所在位置作爲index\n int index = p.first.mIndex;\n //Cantor運算轉成一個數\n return (int) Cantor.getCantor(subItemType, index);\n}\n\n@Override\npublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n //Cantor逆運算,把一個數轉回subItemType和index\n Cantor.reverseCantor(viewType, cantorReverse);\n int index = (int)cantorReverse[1];\n int subItemType = (int)cantorReverse[0];\n //根據index找到具體的子適配器\n Adapter adapter = findAdapterByIndex(index);\n //由子適配器來創建具體的view\n return adapter.onCreateViewHolder(parent, subItemType);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這邊有點晦澀,畫了張圖,需要細品~"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e4/e480979e1e0bfedcb3565ea2c69ca270.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣,自然就可以利用"},{"type":"codeinline","content":[{"type":"text","text":"RecyclerView"}]},{"type":"text","text":"自帶的複用機制幫我們管理view的複用了,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於cantor函數:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設idx1,type1;idx2,type2,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當 idx1 != idx2 或 type1 != type2,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"viewType1 = cantor(idx1,type1) "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"viewType2 = cantor(idx2,type2) 時"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"滿足 viewType1 != viewType2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時支持逆運算:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"viewType1 => idx1,type1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"viewType2 => idx2,type2"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"感興趣的話可以看"},{"type":"link","attrs":{"href":"http://pingguohe.net/2017/05/03/the-beauty-of-math-in-vlayout.html","title":""},"content":[{"type":"text","text":"vlayout中使用數學的小場景"}]},{"type":"text","text":"。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"參考文章"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://pingguohe.net/2017/02/28/vlayout-design.html","title":""},"content":[{"type":"text","text":"蘋果核 - Tangram 的基礎 —— vlayout(Android)"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://pingguohe.net/2017/05/03/the-beauty-of-math-in-vlayout.html","title":""},"content":[{"type":"text","text":"蘋果核 - Pairing Function —— vlayout 中使用數學的小場景"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.cnblogs.com/dasusu/p/7746946.html","title":""},"content":[{"type":"text","text":"博客園-基於場景解析RecyclerView的回收複用機制原理"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.jianshu.com/p/697ce543b1c1","title":""},"content":[{"type":"text","text":"簡書-RecyclerView理解-佈局與回收複用"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a5/a59a19a54e6436e666d4a9b74fff8c29.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章