Two days ago to see HarmonyOS developer website issued a challenge HarmonyOS distributed fun application post, and then have an idea to make a game out, the result 3 days are stuck in the custom components, using a variety of ways to realize the function, but still did not achieve the desired effect, temporarily to take a small summary, Actually pit sometimes really deep…

First, the effect demonstration

Small application is actually quite simple, also seen before, called the number Huarong road, when you place the number in order to finish the game.

Actually belongs to the puzzle class of the small game;

Final effect:

Current implementation:

Second, the implementation process

For a moment, each square can represent a piece, and the name of the piece is also a 3 × 3 square, the number 1-9, but the last number is set separately as blank. Clicking on the pieces around the blank can make a position switch with this blank piece until all the pieces are arranged in order.

Here first said a chess piece, chess pieces have two things to be remembered, one is the coordinates of the chess is in the nine grid inside the position, the other is the name of the chess piece; So you choose to bind coordinates and names in a custom component way.

Position.java

/** * defines the position of the piece */
public class Position {
    public int sizeX; / / the total number of columns
    public int sizeY; / / the total number of rows
    public int x; / / the abscissa
    public int y; / / ordinate

    public Position(a) {}public Position(int sizeX, int sizeY) {
        this.sizeX = sizeX;
        this.sizeY = sizeY;
    }

    public Position(int sizeX, int sizeY, int x, int y) {
        this.sizeX = sizeX;
        this.sizeY = sizeY;
        this.x = x;
        this.y = y;
    }

    public Position(Position orig) {
        this(orig.sizeX, orig.sizeY, orig.x, orig.y);
    }

    /** ** moves to the next position */
    public boolean moveToNextPosition(a) {
        if (x < sizeX - 1) {
            x++;
        } else if (y < sizeY - 1) {
            x = 0;
            y++;
        } else {
            return false;
        }
        return true;
    }

    @Override
    public String toString(a) {
        return "Position{" +
                "x=" + x +
                ", y=" + y +
                '} '; }}Copy the code

CubeView.java

public class CubeView extends ComponentContainer {

    private Position mPosition;
    private int mNumber;

    private Text mTextCub;
    private int mTextSize = 20;

    public CubeView(Context context) {
        super(context);
        init();
    }

    public CubeView(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init();
    }

    private void init(a){
        Component component = LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_cube_view_item, this.false);
        mTextCub = (Text) component.findComponentById(ResourceTable.Id_tv_item);
        mTextCub.setTextSize(mTextSize, Text.TextSizeType.VP);
    }

    public void setNumber(int n) {
        mNumber = n;
        mTextCub.setText(String.valueOf(n));
    }


    public int getNumber(a) {
        return mNumber;
    }

    public Position getPosition(a) {
        return mPosition;
    }

    public void setPosition(Position position) {
        this.mPosition = position;
    }

    @Override
    public String toString(a) {
        return "CubeView{" +
                "mPosition=" + mPosition +
                ", mNumber=" + mNumber +
                '} '; }}Copy the code

cube_view_item.xml


      
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content">
    <Text
        ohos:id="$+id:tv_item"
        ohos:height="100vp"
        ohos:width="100vp"
        ohos:background_element="$graphic:cube_view_bg"
        ohos:text="1"
        ohos:text_alignment="center"
        ohos:text_color="$color:cubeViewStroke"
        ohos:text_size="20vp">
        ></Text>
</DirectionalLayout>
Copy the code

This is where the problem arises, because I’m only using setText() in my code, so some people ask me why I don’t just inherit the Text component, and write an extra layout.

The first pit

This is the first pitfall, because in the old days of writing Android custom controls, it was ok for simple components to inherit their component names instead of inheriting public classes and then using layouts to locate components inside them. This is how I originally wrote it, CubeView inherits Text directly, so you can see there’s no difference between the two.

public class CubeView extends Text {

    private Position mPosition;
    private int mNumber;


    public CubeView(Context context) {
        super(context);
        init();
    }

    public CubeView(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init();
    }

    private void init(a){}public void setNumber(int n) {
        mNumber = n;
        setText(String.valueOf(n));
    }


    public int getNumber(a) {
        return mNumber;
    }

    public Position getPosition(a) {
        return mPosition;
    }

    public void setPosition(Position position) {
        this.mPosition = position;
    }

    @Override
    public String toString(a) {
        return "CubeView{" +
                "mPosition=" + mPosition +
                ", mNumber=" + mNumber +
                '} '; }}Copy the code

But there was a problem with calling the component, because I needed to add the component of this piece to my board layout, so I needed to import this component first. [Fixed] The layout error is reported after the introduction of the component (in the original Android introduced custom components, individual components can also be directly imported) The reason for the error is that I have no layout in the outermost layer, so I cannot directly identify the individual components. However, if I add a layout, the file will not report an error, but I cannot get the components of this piece on my board.

To do this, I had to write the custom components of the pieces as layout introduction.

Here, the development of chess pieces is basically done, the following to the layout of the board. Or choose custom components;

cube_view.xml


      
<com.example.codelabs_games_hrd.CubeView
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:background_element="$graphic:cube_view_bg"
    ohos:height="100vp"
    ohos:width="100vp"
    ohos:id="$+id:title_bar_left"
    ohos:text="1"
    ohos:text_alignment="center"
    ohos:text_color="$color:cubeViewStroke"
    ohos:text_size="20vp"

    >
</com.example.codelabs_games_hrd.CubeView>
Copy the code

ability_game.xml


      
<StackLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:background_element="$color:cubeViewBg">

    <com.example.codelabs_games_hrd.BoardView
        ohos:id="$+id:board"
        ohos:height="300vp"
        ohos:width="300vp"
        ohos:layout_alignment="center"
        ohos:background_element="$color:boardViewBg">
    </com.example.codelabs_games_hrd.BoardView>

    <Text
        ohos:id="$+id:tvCheat"
        ohos:height="10vp"
        ohos:width="10vp"></Text>

    <Text
        ohos:id="$+id:mask"
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:background_element="$color:cubeViewBg"
        ohos:text="123456789"
        ohos:text_size="48vp"></Text>

</StackLayout>
Copy the code

BoardView.java

public class BoardView extends ComponentContainer implements ComponentContainer.EstimateSizeListener.ComponentContainer.ArrangeListener {
    private static final String TAG = "BoardView";
    /** ** How many pieces are in each row */
    private int mSizeX = 3;
    /** ** how many rows of pieces */
    private int mSizeY = 3;


    private int maxWidth = 0;

    private int maxHeight = 0;

    private int mChildSize;

    private Position mBlankPos;
    private CubeView[] mChildren;

    private OnFinishListener mFinishListener;

    private int xx = 0;

    private int yy = 0;

    private int lastHeight = 0;

    // A collection of sub-component indexes and their layout data
    private final Map<Integer, Layout> axis = new HashMap<>();

    // Location and size
    private static class Layout {
        int positionX = 0;
        int positionY = 0;
        int width = 0;
        int height = 0;
    }


    private void invalidateValues(a) {
        xx = 0;
        yy = 0;
        maxWidth = 0;
        maxHeight = 0;
        axis.clear();
    }

    public BoardView(Context context) {
        super(context);
    }

    public BoardView(Context context, AttrSet attrs) {
        super(context, attrs);
        setEstimateSizeListener(this);
        setArrangeListener(this);
        init();
    }

    private void init(a) {
        mChildSize = mSizeX * mSizeY - 1;
        mChildren = new CubeView[mChildSize];
        Position p = new Position(mSizeX, mSizeY);
        for (int i = 0; i < mChildSize; i++) {
        // Add pieces
            CubeView view = (CubeView) LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_cube_view, this.false);
            view.setPosition(new Position(p));
            view.setClickedListener(component -> moveChildToBlank(view));
            addComponent(view);
            p.moveToNextPosition();
            mChildren[i] = view;
        }
        // The last blank piece
        mBlankPos = new Position(mSizeX, mSizeY, mSizeX - 1, mSizeY - 1);
    }



    public void setData(List<Integer> data) {
        for (int i = 0; i < mChildSize; i++) { CubeView view = (CubeView) getComponentAt(i); view.setNumber(data.get(i)); }}// Measure the monitoring method
    @Override
    public boolean onEstimateSize(int widthEstimatedConfig, int heightEstimatedConfig) {
        invalidateValues();
        // Measure the size of the child component
        measureChildren( widthEstimatedConfig,  heightEstimatedConfig);
       // Associate the index of the child component with its layout data
        for (int idx = 0; idx < getChildCount(); idx++) {
            CubeView childView = (CubeView) getComponentAt(idx);
            addChild(childView, idx, EstimateSpec.getSize(widthEstimatedConfig));
        }
        // Measure the size of itself
        setEstimatedSize( widthEstimatedConfig,  heightEstimatedConfig);


        return true;
    }

    private void measureChildren(int widthEstimatedConfig, int heightEstimatedConfig) {
        for (int idx = 0; idx < getChildCount(); idx++) {
            CubeView childView = (CubeView) getComponentAt(idx);
            if(childView ! =null) {
                LayoutConfig lc = childView.getLayoutConfig();
                int childWidthMeasureSpec;
                int childHeightMeasureSpec;
                if (lc.width == LayoutConfig.MATCH_CONTENT) {
                    childWidthMeasureSpec = EstimateSpec.getSizeWithMode(lc.width, EstimateSpec.NOT_EXCEED);
                } else if (lc.width == LayoutConfig.MATCH_PARENT) {
                    int parentWidth = EstimateSpec.getSize(widthEstimatedConfig);
                    int childWidth = parentWidth - childView.getMarginLeft() - childView.getMarginRight();
                    childWidthMeasureSpec = EstimateSpec.getSizeWithMode(childWidth, EstimateSpec.PRECISE);
                } else {
                    childWidthMeasureSpec = EstimateSpec.getSizeWithMode(lc.width, EstimateSpec.PRECISE);
                }

                if (lc.height == LayoutConfig.MATCH_CONTENT) {
                    childHeightMeasureSpec = EstimateSpec.getSizeWithMode(lc.height, EstimateSpec.NOT_EXCEED);
                } else if (lc.height == LayoutConfig.MATCH_PARENT) {
                    int parentHeight = EstimateSpec.getSize(heightEstimatedConfig);
                    int childHeight = parentHeight - childView.getMarginTop() - childView.getMarginBottom();
                    childHeightMeasureSpec = EstimateSpec.getSizeWithMode(childHeight, EstimateSpec.PRECISE);
                } else{ childHeightMeasureSpec = EstimateSpec.getSizeWithMode(lc.height, EstimateSpec.PRECISE); } childView.estimateSize(childWidthMeasureSpec, childHeightMeasureSpec); }}}private void measureSelf(int widthEstimatedConfig, int heightEstimatedConfig) {
        int widthSpce = EstimateSpec.getMode(widthEstimatedConfig);
        int heightSpce = EstimateSpec.getMode(heightEstimatedConfig);
        int widthConfig = 0;
        switch (widthSpce) {
            case EstimateSpec.UNCONSTRAINT:
            case EstimateSpec.PRECISE:
                int width = EstimateSpec.getSize(widthEstimatedConfig);
                widthConfig = EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE);
                break;
            case EstimateSpec.NOT_EXCEED:
                widthConfig = EstimateSpec.getSizeWithMode(maxWidth, EstimateSpec.PRECISE);
                break;
            default:
                break;
        }

        int heightConfig = 0;
        switch (heightSpce) {
            case EstimateSpec.UNCONSTRAINT:
            case EstimateSpec.PRECISE:
                int height = EstimateSpec.getSize(heightEstimatedConfig);
                heightConfig = EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE);
                break;
            case EstimateSpec.NOT_EXCEED:
                heightConfig = EstimateSpec.getSizeWithMode(maxHeight, EstimateSpec.PRECISE);
                break;
            default:
                break;
        }
        setEstimatedSize(widthConfig, heightConfig);
    }



    // The position and size of each piece
    @Override
    public boolean onArrange(int l, int t, int r, int b) {

        for (int idx = 0; idx < getChildCount(); idx++) {
            Component childView = getComponentAt(idx);
            Layout layout = axis.get(idx);
            if(layout ! =null) { childView.arrange(layout.positionX, layout.positionY, layout.width, layout.height); }}return true;
    }


    private void addChild(CubeView component, int id, int layoutWidth) {
        Layout layout = new Layout();
        layout.positionX = xx + component.getMarginLeft();
        layout.positionY = yy + component.getMarginTop();
        layout.width = component.getEstimatedWidth();
        layout.height = component.getEstimatedHeight();
        if ((xx + layout.width) > layoutWidth) {
            xx = 0;
            yy += lastHeight;
            lastHeight = 0;
            layout.positionX = xx + component.getMarginLeft();
            layout.positionY = yy + component.getMarginTop();
        }
        axis.put(id, layout);
        lastHeight = Math.max(lastHeight, layout.height + component.getMarginBottom());
        xx += layout.width + component.getMarginRight();
        maxWidth = Math.max(maxWidth, layout.positionX + layout.width + component.getMarginRight());
        maxHeight = Math.max(maxHeight, layout.positionY + layout.height + component.getMarginBottom());
    }
    
    // Click the piece to switch position
    public void moveChildToBlank(@org.jetbrains.annotations.NotNull CubeView child) {
        Position childPos = child.getPosition();
        Position dstPos = mBlankPos;
        if (childPos.x == dstPos.x && Math.abs(childPos.y - dstPos.y) == 1 ||
                childPos.y == dstPos.y && Math.abs(childPos.x - dstPos.x) == 1) {
            child.setPosition(dstPos);
            // There is no way to physically pan components in component
            //setTranslationX() and setTranslationY() are not available
            child.setTranslationX(dstPos.x * xx);
            child.setTranslationY(dstPos.y * yy);

            mBlankPos = childPos;
            mStepCounter.add();
        }
        checkPosition();
    }

    /** * Check that all the boxes are in the correct position */
    private void checkPosition(a) {
        if(mBlankPos.x ! = mSizeX -1|| mBlankPos.y ! = mSizeY -1) {
            return;
        }

        for (CubeView child : mChildren) {
            int num = child.getNumber();
            int x = child.getPosition().x;
            int y = child.getPosition().y;
            if (y * mSizeX + x + 1! = num) {return; }}if(mFinishListener ! =null) {
            mFinishListener.onFinished(mStepCounter.step);
        }
        for (CubeView child : mChildren) {
            child.setClickable(false); }}public void setOnFinishedListener(OnFinishListener l) {
        mFinishListener = l;
    }

    public interface OnFinishListener {
        void onFinished(int step);
    }

    public int getSizeX(a) {
        return mSizeX;
    }

    public int getSizeY(a) {
        return mSizeY;
    }

    /** * step count */
    class StepCounter {
        private int step = 0;

        void add(a) {
            step++;
        }

        void clear(a) {
            step = 0; }}private StepCounter mStepCounter = new StepCounter();

}

Copy the code

The custom layout of the board is complete. The layout of the board is a little more complicated, because you need to calculate the size of each piece based on the size of the board, and you need to bind the pieces, especially the last piece that needs to be blank.

Then click on the chess piece for the translation of the chess piece, the translation of its position for exchange.

The second pit

[Img-pVMupb0C-1634810943992] (C:\Users\HHCH\AppData\ Typora\ Typora-user-images \ I) [imG-PVMupb0C-1634810943992] (C:\Users\HHCH\AppData\Roaming\ Typora-user-images \ I mage-20211021175237912.png)]

There is no translation method under component public component in API, setTranslationX()/setTranslationY(), there is no way to translate the physical position of component, so you can see the effect shown at the start. After clicking it, it switched with the blank position, but there was no way to assign the physical position again. This problem bothered me for two days.

I haven’t solved that yet, so let’s try to see if we can use the TouchEvent event to do a swipe instead of a click event to do a swipe.

The final structure of the project is as follows:

conclusion

Behind will continue to improve, so that the whole function can be used normally, trample pit or trample, there will always be a harvest…..