This series of articles mainly introduces the open source Tangram Experience and principle of using framework, because Tangram bottom layer is based on vlayout The series will be introduced according to the following outline:
- Demand background
- Tangram and vlayout introduction
- Use of Tangram
- vlayout principle
- Tangram principle
- Tangram secondary packaging
This article will explain the underlying implementation of Tangram, vlayout.
Based on the latest source code of vlayout
vlayout
stay Tangram and vlayout introduction As mentioned in this article,
vlayout customizes a VirtualLayoutManager, which inherits from LinearLayoutManager and introduces LayoutHelper Virtual layout manager manages a series of layouthelpers, and gives the specific layout ability to LayoutHelper. Each LayoutHelper provides a layout method. The framework provides several common layout types, including grid layout, linear layout, waterfall flow layout, suspended layout, suction side layout, etc. In this way, the ability of mixed layout is realized, and the extension of external is supported, new LayoutHelper is registered, and special layout mode is realized.
Quoted from Apple core - the foundation of Tangram - vlayout (Android)
That's the general idea,
In VLayoutActivity,
//VLayoutActivity.java void onCreate(Bundle savedInstanceState) { if (FLOAT_LAYOUT) { //Create layout mode layoutHelper. FloatLayoutHelper is a floating and draggable layout, such as the floating window function of wechat FloatLayoutHelper layoutHelper = new FloatLayoutHelper(); //Set the initial position to the lower right corner layoutHelper.setAlignType(FixLayoutHelper.BOTTOM_RIGHT); //Set the offset. When the position is the lower right corner, it is marginRight and marginBottom respectively layoutHelper.setDefaultLocation(100, 400); //Set width and height LayoutParams layoutParams = new LayoutParams(150, 150); //Create sub adapter, add to adapter collection adapters.add(new SubAdapter(this, layoutHelper, 1, layoutParams)); } }
Go to SubAdapter,
//inherit DelegateAdapter.Adapter class SubAdapter extends DelegateAdapter.Adapter<MainViewHolder> { private LayoutHelper mLayoutHelper; public SubAdapter(Context context, LayoutHelper layoutHelper, int count, LayoutParams layoutParams) { this.mContext = context; this.mLayoutHelper = layoutHelper; this.mCount = count; this.mLayoutParams = layoutParams; } @Override public LayoutHelper onCreateLayoutHelper() { //Return the layout from LayoutHelper return mLayoutHelper; } //Create ViewHolder public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new MainViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false)); } //Bind ViewHolder protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) { ((TextView) holder.itemView.findViewById(R.id.title)).setText(Integer.toString(offsetTotal)); } }
stay delegateAdapter.setAdapters(adapters), take out the layout method specified by the adapter, and perform transparent transmission,
//DelegateAdapter.java public void setAdapters(List<Adapter> adapters) { List<LayoutHelper> helpers = new LinkedList<>(); for (Adapter adapter : adapters) { LayoutHelper helper = adapter.onCreateLayoutHelper(); helpers.add(helper); } super.setLayoutHelpers(helpers); }
Come to VirtualLayoutManager,
//VirtualLayoutManager.java void setLayoutHelpers(@Nullable List<LayoutHelper> helpers) { //Set the jurisdiction of LayoutHelper for each layout method start and end //Assuming that the first module is ColumnLayoutHelper with 3 elements, the jurisdiction is [0,2] //The second module is OnePlusNLayoutHelper, with 4 elements, the jurisdiction is [3,6] if (helpers != null) { int start = 0; Iterator<LayoutHelper> it1 = helpers.iterator(); while (it1.hasNext()) { LayoutHelper helper = it1.next(); if (helper.getItemCount() > 0) { helper.setRange(start, start + helper.getItemCount() - 1); } else { helper.setRange(-1, -1); } start += helper.getItemCount(); } } //Internal assignment and sorting, RangeLayoutHelperFinder can find the corresponding LayoutHelper according to the location this.mHelperFinder.setLayouts(helpers); requestLayout(); }
After the LayoutHelper has been assigned a value, the layout will be carried out. Here, we will not go into the drawing process of View's measurement layout VirtualLayoutManager.onLayoutChildren ,
//VirtualLayoutManager.java void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { //Pre layout, that is, calling the beforeLayout of each layouthelper runPreLayout(recycler, state); super.onLayoutChildren(recycler, state); } //ExposeLinearLayoutManagerEx.java void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { fill(recycler, mLayoutState, state, false); } int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { layoutChunk(recycler, state, layoutState, layoutChunkResultCache); } //VirtualLayoutManager.java void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, com.alibaba.android.vlayout.layout.LayoutChunkResult result) { //RangeLayoutHelperFinder finds the corresponding layout method according to the location LayoutHelper final int position = layoutState.mCurrentPosition; LayoutHelper layoutHelper = mHelperFinder == null ? null : mHelperFinder.getLayoutHelper(position); layoutHelper.doLayout(recycler, state, mTempLayoutStateWrapper, result, this); } //BaseLayoutHelper.java void doLayout(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutStateWrapper layoutState, LayoutChunkResult result, LayoutManagerHelper helper) { //Trigger each specific LayoutHelper for measurement and layout layoutViews(recycler, state, layoutState, result, helper); }
The specific measurement and layout implementation of layoutViews, we give two more typical layout analysis, ColumnLayoutHelper and FloatLayoutHelper.
Example ColumnLayoutHelper column layout
Set the proportion, the first column and the fourth column account for 33, the middle two columns do not specify the proportion, then divide the remaining space equally,
layoutHelper.setWeights(new float[]{33f, Float.NaN, Float.NaN, 33f});
The effect is as follows,
Look at the layoutViews method,
//ColumnLayoutHelper.java void layoutViews(RecyclerView.Recycler recycler, RecyclerView.State state, VirtualLayoutManager.LayoutStateWrapper layoutState, LayoutChunkResult result, LayoutManagerHelper helper) { final int count = getAllChildren(mViews, recycler, layoutState, result, helper); //1. Calculate the margin of each child //2. Use the total width and percentage to allocate the width and height for the child. The child without the percentage is stored in the mEqViews first for (int i = 0; i < count; i++) { View view = mViews[i]; VirtualLayoutManager.LayoutParams params = (VirtualLayoutManager.LayoutParams) view.getLayoutParams(); int heightSpec = helper.getChildMeasureSpec( helper.getContentHeight() - helper.getPaddingTop() - helper.getPaddingBottom(), uniformHeight > 0 ? uniformHeight : params.height, true); if (mWeights != null && i < mWeights.length && !Float.isNaN(mWeights[i]) && mWeights[i] >= 0) { //Calculate width by percentage int resizeWidth = (int) (mWeights[i] * 1.0f / 100 * availableWidth + 0.5f); //Calculate height based on width and scale if (!Float.isNaN(params.mAspectRatio)) { int specialHeight = (int) (resizeWidth / params.mAspectRatio + 0.5f); heightSpec = View.MeasureSpec .makeMeasureSpec(specialHeight, View.MeasureSpec.EXACTLY); } helper.measureChildWithMargins(view, View.MeasureSpec.makeMeasureSpec(resizeWidth, View.MeasureSpec.EXACTLY), heightSpec); //Record used width usedWidth += resizeWidth; //Record minimum height minHeight = Math.min(minHeight, view.getMeasuredHeight()); } else { mEqViews[eqSize++] = view; } } }
3. Divide the remaining width equally among child ren without setting percentage,
//ColumnLayoutHelper.java for (int i = 0; i < eqSize; i++) { View view = mEqViews[i]; VirtualLayoutManager.LayoutParams params = (VirtualLayoutManager.LayoutParams) view.getLayoutParams(); int heightSpec; int resizeWidth = (int) ((availableWidth - usedWidth) * 1.0f / eqSize + 0.5f); //Calculate height based on width and scale if (!Float.isNaN(params.mAspectRatio)) { int specialHeight = (int) (resizeWidth / params.mAspectRatio + 0.5f); heightSpec = View.MeasureSpec .makeMeasureSpec(specialHeight, View.MeasureSpec.EXACTLY); } else { heightSpec = helper.getChildMeasureSpec( helper.getContentHeight() - helper.getPaddingTop() - helper.getPaddingBottom(), uniformHeight > 0 ? uniformHeight : params.height, true); } helper.measureChildWithMargins(view, View.MeasureSpec.makeMeasureSpec(resizeWidth, View.MeasureSpec.EXACTLY), heightSpec); //Record minimum height minHeight = Math.min(minHeight, view.getMeasuredHeight()); }
4. Uniform height for all child ren, minimum height
//ColumnLayoutHelper.java for (int i = 0; i < count; i++) { View view = mViews[i]; if (view.getMeasuredHeight() != minHeight) { helper.measureChildWithMargins(view, View.MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(minHeight, View.MeasureSpec.EXACTLY)); } }
5. The measurement is completed, the layout is carried out, and finally handed over to RecyclerView.LayoutManager Processing, i.e. layoutDecorated,
//ColumnLayoutHelper.java for (int i = 0; i < count; i++) { View view = mViews[i]; int top = mTempArea.top, bottom = mTempArea.bottom; int right = left + orientationHelper.getDecoratedMeasurementInOther(view); layoutChildWithMargin(view, left, top, right, bottom, helper); left = right; }
For example, FloatLayoutHelper floats a draggable layout
The layout code of FloatLayoutHelper doesn't need to be looked at. It's probably to calculate the specific location according to the location and offset. We focus on the implementation of his touch event,
//FloatLayoutHelper.java View.OnTouchListener touchDragListener = new View.OnTouchListener() { boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: isDrag = false; //Press to have the parent view RecyclerView not intercept the event (v.getParent()).requestDisallowInterceptTouchEvent(true); lastPosX = (int) event.getX(); lastPosY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: if (Math.abs(event.getX() - lastPosX) > mTouchSlop || Math.abs(event.getY() - lastPosY) > mTouchSlop) { isDrag = true; } if (isDrag) { //... //Constantly update coordinates to achieve mobile effect v.setTranslationX(curTranslateX); v.setTranslationY(curTranslateY); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //Lift or cancel, play the suction animation, that is, automatically spring back to both sides doPullOverAnimation(v); //Let the parent view RecyclerView resume blocking events (v.getParent()).requestDisallowInterceptTouchEvent(false); break; } } }
The effect is as follows,
RecyclerView reuse and Cantor function
The final use of RecyclerView is to manage the DelegateAdapter of the sub adapter set. In general, we can't guarantee that the ViewTypes among the sub adapters don't conflict, so we only analyze the case where hasconsististitemtype = false. For the specific reasons, see FAQ (the problem of component reuse),
//DelegateAdapter.java @Override public int getItemViewType(int position) { Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position); //viewType of child adapter as subItemType int subItemType = p.second.getItemViewType(position - p.first.mStartPosition); //The location of LayoutHelper as the index int index = p.first.mIndex; //Cantor operation to a number return (int) Cantor.getCantor(subItemType, index); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //Cantor inverse operation, turning a number back to subItemType and index Cantor.reverseCantor(viewType, cantorReverse); int index = (int)cantorReverse[1]; int subItemType = (int)cantorReverse[0]; //Find the specific sub adapter according to index Adapter adapter = findAdapterByIndex(index); //Sub adapter to create specific view return adapter.onCreateViewHolder(parent, subItemType); }
It's a bit obscure here. I drew a picture. I need to elaborate~
In this way, we can use the reuse mechanism of RecyclerView to help us manage the reuse of views,
About cantor functions:
Set idx1,type1;idx2,type2,
When idx1! = idx2 or type1! = type2,
viewType1 = cantor(idx1,type1)
When viewType2 = cantor(idx2,type2)
Meet viewtype1! = viewtype2
At the same time, it supports inverse operation:
viewType1 => idx1,type1
viewType2 => idx2,type2
You can see if you are interested Small scenes of using mathematics in vlayout.
Reference article
- Apple core - the foundation of Tangram - vlayout (Android)
- Apple kernel Pairing Function -- a small scene of using mathematics in vlayout
- Blog Park - based on scenario analysis RecyclerView's recycling mechanism principle
- Simple book RecyclerView understanding layout and recycling