vlayout principle of Android Tangram dynamic page

Keywords: Android Java Mobile Spring

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:

  1. Demand background
  2. Tangram and vlayout introduction
  3. Use of Tangram
  4. vlayout principle
  5. Tangram principle
  6. 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

Posted by X74SY on Sun, 07 Jun 2020 02:41:16 -0700