Introduction to the

Hongmeng OS development SDK for the long list of ListContainer implementation is relatively simple, can not like RecyclerView through the use of different LayoutManager to achieve complex layout, so can not quickly achieve waterfall flow effect. However, hongmeng OS also supports control Measure (onEstimateSize), layout (onArrange) and event processing. You can customize a layout in Hongmeng OS to achieve RecyclerView+LayoutManager effect, in order to achieve waterfall flow and other complex effects.

Custom layout

Hongmun OS custom layout is introduced on the official website. OnEstimateSize is mainly implemented to measure the size of the control and onArrange to achieve the layout. Here, the determination and measurement of the sub-control placement are completely handed over to LayoutManager to achieve. Sliding, at the same time we want to support with Component here. DraggedListener implementation. So our layout container is very simple, we call LayoutManager to measure the layout, and for the sliding event, we determine the sliding window, we call LayoutManager’s fill function to determine the collection of subcontainers that fill the window, and then trigger a redraw. The core code is as follows

public class SpanLayout extends ComponentContainer implements ComponentContainer.EstimateSizeListener, ComponentContainer.ArrangeListener, Component.CanAcceptScrollListener, Component.ScrolledListener, Component.TouchEventListener, Component.DraggedListener { private BaseItemProvider mProvider; public SpanLayout(Context context) { super(context); setEstimateSizeListener(this); setArrangeListener(this); setDraggedListener(DRAG_VERTICAL,this); } @Override public boolean onEstimateSize(int widthEstimatedConfig, int heightEstimatedConfig) { int width = Component.EstimateSpec.getSize(widthEstimatedConfig); int height = Component.EstimateSpec.getSize(heightEstimatedConfig); setEstimatedSize( Component.EstimateSpec.getChildSizeWithMode(width, widthEstimatedConfig, EstimateSpec.UNCONSTRAINT), Component.EstimateSpec.getChildSizeWithMode(height, heightEstimatedConfig, EstimateSpec.UNCONSTRAINT)); mLayoutManager.setEstimateSize(widthEstimatedConfig,heightEstimatedConfig); // measureChild(widthEstimatedConfig,heightEstimatedConfig); return true; } @override public Boolean onArrange(int left, int top, int width, int height) { Start from item0 until leftHeight and rightHeight are both greater than height. if(mRecycler.getAttachedScrap().isEmpty()){ mLayoutManager.fill(left,top,left+width,top+height,DIRECTION_UP); } // removeAllComponents(); // Calling removeAllComponents will always start redrawing. for(RecyclerItem item:mRecycler.getAttachedScrap()){ item.child.arrange(item.positionX+item.marginLeft,scrollY+item.positionY+item.marginTop,item.width,item.height); } return true; } @Override public void onDragStart(Component component, DragInfo dragInfo) { startY = dragInfo.startPoint.getPointYToInt(); } @Override public void onDragUpdate(Component component, DragInfo dragInfo) { int dt = dragInfo.updatePoint.getPointYToInt() - startY; int tryScrollY = dt + scrollY; startY = dragInfo.updatePoint.getPointYToInt(); mDirection = dt<0? DIRECTION_UP:DIRECTION_DOWN; mChange = mLayoutManager.fill(0, -tryScrollY,getEstimatedWidth(),-tryScrollY+getEstimatedHeight(),mDirection); if(mChange){ scrollY = tryScrollY; postLayout(); }}}Copy the code

The waterfall flow LayoutManager

The LayoutManager is used to determine the layout of the child control. The emphasis is to implement the fill function, which is used to confirm the child control within a window.

We define a Span class that records the current startLine and endLine state of a single waterfall, and in the spanNum column, we create a Span array to record the state.

For example, when scrolling up, a child control whose bottom is smaller than the bottom of the window needs to be reclaimed, and a child control whose bottom is smaller than the bottom of the window needs to be filled with children. Because the waterfall flow is multi-column and each child control has a different height, it is not easy to determine whether the first child currently displayed should be recycled or whether the last child needs to be filled to fill the window. We use a while loop + a double-ended queue to fill the window by ensuring that all spans have startlines smaller than top and endlines larger than bottom of the window. The core fill function is implemented as follows:

public synchronized boolean fill(float left,float top,float right,float bottom,int direction){ int spanWidth = mWidthSize/mSpanNum; if(mSpans == null){ mSpans = new Span[mSpanNum]; for(int i=0; i<mSpanNum; i++){ Span span = new Span(); span.index = i; mSpans[i] = span; span.left = (int) (left + i*spanWidth); } } LinkedList<RecyclerItem> attached = mRecycler.getAttachedScrap(); if(attached.isEmpty()){ mRecycler.getAllScrap().clear(); int count = mProvider.getCount(); int okSpan = 0; for (int i=0; i<count; i++){ Span span = getMinSpanWithEndLine(); RecyclerItem item = fillChild(span.left,span.endLine,i); item.span = span; If (item.positiony >=top && item.positiony <=bottom+item.height){// In display area mrecycler.additem (I,item); mRecycler.attachItemToEnd(item); }else{ mRecycler.recycle(item); } span.endLine += item.height+item.marginTop+item.marginBottom; if(span.endLine>bottom){ okSpan++; } if(okSpan>=mSpanNum){ break; } } return true; }else{ if(direction == DIRECTION_UP){ RecyclerItem last = attached.peekLast(); int count = mProvider.getCount(); If (last.index == count-1 && last.getBottom()<=bottom){return false; }else{recycle RecyclerItem first = attached. PeekFirst (); while(first ! = null && first.getBottom()<top){ mRecycler.recycle(first); //recycle will remove first.span. StartLine += first.getvspace (); first = attached.peekFirst(); } Span minEndLineSpan = getMinSpanWithEndLine(); int index = last.index+1; While (index<count && minendLinespan.endline <=bottom){// Need to fill RecyclerItem; if(mRecycler.getAllScrap().size()>index){ item = mRecycler.getAllScrap().get(index); mRecycler.recoverToEnd(item); }else{ item = fillChild(minEndLineSpan.left,minEndLineSpan.endLine,index); item.span = minEndLineSpan; mRecycler.attachItemToEnd(item); mRecycler.addItem(index,item); } item.span.endLine += item.getVSpace(); minEndLineSpan = getMinSpanWithEndLine(); index++; } return true; } }else if(direction == DIRECTION_DOWN){ RecyclerItem first = attached.peekFirst(); int count = mProvider.getCount(); If (first.index == 0 && first.getTop()>=top){return false; }else{// recycle RecyclerItem last = attached. PeekLast (); while(last ! = null && last.getTop()>bottom){ mRecycler.recycle(last); //recycle will remove last.span. EndLine -= last.getvspace (); last = attached.peekFirst(); } Span maxStartLineSpan = getMaxSpanWithStartLine(); int index = first.index-1; While (index > = 0 && maxStartLineSpan. The startLine > = top) {/ / need filling RecyclerItem item = mRecycler. GetAllScrap () get (index); if(item ! = null){ mRecycler.recoverToStart(item); item.span.startLine -= item.getVSpace(); }else{// not exist} maxStartLineSpan = getMaxSpanWithStartLine(); index--; } return true; } } } return true; }Copy the code

The Item to recycle

For long lists, there must be something like RecyclerView. Item collection and undo are triggered in the Fill function of LayoutManager via Reycler.

Simple use of mAttacthedScrap to save the Item displayed on the current window and mCacheScrap to save the recycled controls. The design here is the simplification of RecyclerView recycling mechanism.

The difference is that a RecycleItem class, referring to the concept of three trees in Flutter, is defined to record the upper-left corner coordinates and width and height values of each Item. Only items displayed on a window are bound to components. Because RecycleItem is very lightweight in the case of unbound components, the loss of memory can be basically ignored. We use mAllScrap to store all RecycleItem objects in order for reuse. When restoring an Item existing in mAllScrap, its coordinates and width and height are determined.

The core code for Recycler is as follows:

public class Recycler { public static final int DIRECTION_UP = 0; public static final int DIRECTION_DOWN = 2; private ArrayList<RecyclerItem> mAllScrap = new ArrayList<>(); private LinkedList<RecyclerItem> mAttachedScrap = new LinkedList<>(); private LinkedList<Component> mCacheScrap = new LinkedList<Component>(); private BaseItemProvider mProvider; private SpanLayout mSpanLayout; private int direction = 0; public Recycler(SpanLayout layout, BaseItemProvider provider) { this.mSpanLayout = layout; this.mProvider = provider; } public ArrayList<RecyclerItem> getAllScrap() { return mAllScrap; } public LinkedList<RecyclerItem> getAttachedScrap() { return mAttachedScrap; } public void cacheItem(int index, RecyclerItem item) { mAllScrap.add(index, item); } public void attachComponent(RecyclerItem item) { mAttachedScrap.add(item); } public Component getView(int index, ComponentContainer container) { Component cache = mCacheScrap.poll(); return mProvider.getComponent(index, cache, container); } public void addItem(int index,RecyclerItem item) { mAllScrap.add(index,item); } public void attachItemToEnd(RecyclerItem item) { mAttachedScrap.add(item); } public void attachItemToStart(RecyclerItem item) { mAttachedScrap.add(0,item); } public void recycle(RecyclerItem item) { mSpanLayout.removeComponent(item.child); mAttachedScrap.remove(item); mCacheScrap.push(item.child); item.child = null; } public void recoverToEnd(RecyclerItem item) { Component child = mProvider.getComponent(item.index, mCacheScrap.poll(), mSpanLayout); child.estimateSize( Component.EstimateSpec.getSizeWithMode(item.width, Component.EstimateSpec.PRECISE), Component.EstimateSpec.getSizeWithMode(item.height, Component.EstimateSpec.PRECISE) ); item.child = child; mAttachedScrap.add(item); mSpanLayout.addComponent(child); } public void recoverToStart(RecyclerItem item) { Component child = mProvider.getComponent(item.index, mCacheScrap.poll(), mSpanLayout); child.estimateSize( Component.EstimateSpec.getSizeWithMode(item.width, Component.EstimateSpec.PRECISE), Component.EstimateSpec.getSizeWithMode(item.height, Component.EstimateSpec.PRECISE) ); item.child = child; mAttachedScrap.add(0,item); mSpanLayout.addComponent(child); }}Copy the code

conclusion

Hongmeng OS development SDK in the basic ability has provided a comprehensive, can be used to achieve some complex effects. SpanLayout+LayoutManager+Recycler is basically a complete complex list. Other layout effects can also be achieved by implementing different LayoutManagers.

Complete code in my code cloud project, under the com.profound.notes.com ponent package, passing please help a star. Gitee.com/profound-la…

The original link: developer.huawei.com/consumer/cn…

Original author: Zjwujlei