Some simple games can be implemented with custom controls, such as jigsaw puzzles. First, the effect picture:





Normal mode




Exchange pattern

1. General idea of the game

The basic idea of the game: cut a large picture into several small pictures, and then disarrange the order of the small pictures, arrange them neatly in a ViewGroup, and assemble the picture into the original large picture by clicking the small pictures to swap positions.

2. Technical points

1, inherit the ViewGroup custom control and the use of onLayout method. 2. Cut a large picture into smaller ones. 3. Picture compression. 4. Property animation. 5. Use of DialogFragment.

3. Technical point analysis

3.1 inherit ViewGroup to achieve custom View

When implementing a custom View, you need to decide whether to inherit the View or ViewGroup. Inherit View: A custom control that inherits a View can be called a paint control and needs to use paint, Canvas and other classes to draw. For example: www.jianshu.com/p/ac33e61a1… Inherit ViewGroup: A custom control that inherits a ViewGroup can be called a composite control. A custom control that combines multiple controls, such as this jigsaw puzzle game, is composed of multiple ImageViews and a ViewGroup.

The very important onLayout method

Inheriting ViewGroup, the onLayout method must be implemented. This method is very important and is the key method for controlling the placement of child controls in the parent container.

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  }Copy the code

View has four corresponding methods: getLeft(), getTop(), getRight(), and getBottom(). To get the values left, top, right, and bottom. These values represent the distance between the bounds of the child view and the bounds of the parent container.





The meaning of four methods

Pictures from: blog.csdn.net/u013872857/…

As can be seen from the picture:

left = view.getLeft(); top = view.getTop(); Right = view.getLeft()+view width botto = view.getTop()+ View heightCopy the code

If a View gets its left, top, right, and bottom parameters, we can use the View layout method to determine the position of the View in the parent container. These parameters are used in a ViewGroup:

  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    view.layout(left,top,right, bottom);
  }Copy the code

The jigsaw puzzle calculates the left, top, right, and bottom of each ImageView. The reason for this calculation is that the ViewGroup passes AddView and the child views are displayed in the upper left corner by default. The subview can be displayed in different positions according to the left, top, right, and bottom by calculation.

private void initBitmapsWidth() { int line = 0; int left = 0; int top = 0; int right = 0; int bottom = 0; for (int i = 0; i < mImagePieces.size(); i++) { /// ... Omit some code if (I! = 0 && i % mCount == 0) { line++; } if (i % mCount == 0) { left = i % mCount * mItemWidth; } else { left = i % mCount * mItemWidth + (i % mCount) * mMargin; } top = mItemWidth * line + line * mMargin; right = left + mItemWidth; bottom = top + mItemWidth; imageView.setRight(right); imageView.setLeft(left); imageView.setBottom(bottom); imageView.setTop(top); imageView.setId(i); imageView.setOnClickListener(this); mImagePieces.get(i).setImageView(imageView); addView(imageView); }}Copy the code

Each ImageView is displayed in the onLayout method: here the imageView.layout() method displays the image in a different location in the parent container.

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 0; i < getChildCount(); i++) { if (getChildAt(i) instanceof ImageView) { ImageView imageView = (ImageView) getChildAt(i); imageView.layout(imageView.getLeft(), imageView.getTop(), imageView.getRight(), imageView.getBottom()); } else {layout for the animation layer if (getChildAt(I) instanceof RelativeLayout) {RelativeLayout = (RelativeLayout) getChildAt(i); relativeLayout.layout(0, 0, mViewWidth, mViewWidth); }}}}Copy the code

3.3 Cut a large picture into several smaller pictures

Instead of finding lots of pictures, the jigsaw puzzle uses one large picture to cut into many smaller pictures. This also makes sense, as the difficulty level increases, the number of images displayed in each row increases, which can be very troublesome if each small image is a separate image. Image cutting method:

@param bitmap @param count @return */ public static List<ImagePiece> splitImage(Context context, Bitmap bitmap, int count, String gameMode) { List<ImagePiece> imagePieces = new ArrayList<>(); int width = bitmap.getWidth(); int height = bitmap.getHeight(); int picWidth = Math.min(width, height) / count; for (int i = 0; i < count; i++) { for (int j = 0; j < count; j++) { ImagePiece imagePiece = new ImagePiece(); imagePiece.setIndex(j + i * count); CreateBitmap xy int x = j * picWidth; int y = i * picWidth; if (gameMode.equals(PuzzleLayout.GAME_MODE_NORMAL)) { if (i == count - 1 && j == count - 1) { imagePiece.setType(ImagePiece.TYPE_EMPTY); Bitmap emptyBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.empty); imagePiece.setBitmap(emptyBitmap); } else { imagePiece.setBitmap(Bitmap.createBitmap(bitmap, x, y, picWidth, picWidth)); } } else { imagePiece.setBitmap(Bitmap.createBitmap(bitmap, x, y, picWidth, picWidth)); } imagePieces.add(imagePiece); } } return imagePieces; }Copy the code

The bitmap. createBitmap method is used to divide an image into multiple pieces. Create a javaBean to hold the bitmap and index (index holds the subscript of the image and is used to check if the puzzle is complete). In the normal mode of the jigsaw puzzle (normal mode renderings), there is a blank drawing. Replace it with a transparent.9 drawing.

3.4 Image compression

In order to prevent the image is too large to cause OOM, here uses the method of compressed image:

<p> ** @param scale Scale (1 to 10, 2 to 1/2 of the length and width, 3 to 1/3 of the width) Public synchronized static Bitmap readBitmap(Context Context, int res, int scale) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; options.inSampleSize = scale; options.inPurgeable = true; options.inInputShareable = true; options.inPreferredConfig = Bitmap.Config.RGB_565; return BitmapFactory.decodeResource(context.getResources(), res, options); } catch (Exception e) { return null; }}Copy the code

3.5 Attribute Animation

3.5.1 Concept of animation layer

QQ list bubble drag effect, use a similar concept:





Bubble drag effect

If the bubble is in a ViewGroup, you can’t drag it out of the ViewGroup, because the bubble is a child View of the ViewGroup, and it can’t be displayed outside of the ViewGroup, let alone the entire screen. So the concept of an animation layer might be used here: when a bubble is clicked, hide the clicked bubble and add a transparent full-screen ViewGroup over the entire layout, then add a similar bubble at the original bubble position, and then slide over the ViewGroup. (Note: this is the result of my YY, maybe QQ is not realized in this way)

/** * Construct animation layer for click animation * why animation layer? Make sure the animation executes across the view. */ private void setUpAnimLayout() { if (mAnimLayout == null) { mAnimLayout = new RelativeLayout(getContext()); } if (! isAddAnimatorLayout) { isAddAnimatorLayout = true; addView(mAnimLayout); }}Copy the code

One problem with this is that addView(mAnimLayout) is called; This code later shows that the animation layer is not displayed. This problem may require a look at the source code, but I have found a temporary solution (I don’t know why) : after addView, I need to reset the width and height of the child view. www.cnblogs.com/renjiemei12… Code to resolve this problem:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mViewWidth, mViewWidth); for (int i = 0; i < getChildCount(); i++) { if (getChildAt(i) instanceof RelativeLayout) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); }}}Copy the code
3.5.2 Realize small graph sliding or switching

When jigsaw puzzle, no matter it is common mode or exchange mode, it is nothing more than the exchange effect of two pictures. When a picture is clicked, hide the clicked picture and record it, then generate an animation layer, generate pictures of the same size and position on the animation layer, and then realize the effect of picture exchange on the animation layer. After the animation is completed, you can hide the animation layer in onAnimationEnd, remove the ImageView in the animation layer, and record the exchange of some attributes of the two ImageViews, such as the exchange of bitmap and index. Note: The effect looks like the two ImageViews have swapped positions, but it’s really just the bitmaps that have swapped each other. A blank image in normal mode is a recorded image by default.

/** * private imageView addAnimationImageView(imageView imageView) { ImageView getImage = new ImageView(getContext()); RelativeLayout.LayoutParams firstParams = new RelativeLayout.LayoutParams(mItemWidth, mItemWidth); firstParams.leftMargin = imageView.getLeft() - mPadding; firstParams.topMargin = imageView.getTop() - mPadding; Bitmap firstBitmap = mImagePieces.get(imageView.getId()).getBitmap(); getImage.setImageBitmap(firstBitmap); getImage.setLayoutParams(firstParams); mAnimLayout.addView(getImage); return getImage; Private void exChangeView() {// Add animation layer setUpAnimLayout(); // Add animation layer setUpAnimLayout(); ImageView first = addAnimationImageView(mFirst); ImageView Second = addAnimationImageView(mSecond); ObjectAnimator secondXAnimator = ObjectAnimator.ofFloat(second, "TranslationX", 0f, -(mSecond.getLeft() - mFirst.getLeft())); ObjectAnimator secondYAnimator = ObjectAnimator.ofFloat(second, "TranslationY", 0f, -(mSecond.getTop() - mFirst.getTop())); ObjectAnimator firstXAnimator = ObjectAnimator.ofFloat(first, "TranslationX", 0f, mSecond.getLeft() - mFirst.getLeft());  ObjectAnimator firstYAnimator = ObjectAnimator.ofFloat(first, "TranslationY", 0f, mSecond.getTop() - mFirst.getTop()); AnimatorSet secondAnimator = new AnimatorSet(); secondAnimator.play(secondXAnimator).with(secondYAnimator).with(firstXAnimator).with(firstYAnimator); secondAnimator.setDuration(300); secondAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { ImagePiece firstPiece = mImagePieces.get(mFirst.getId()); ImagePiece secondPiece = mImagePieces.get(mSecond.getId()); int firstType = firstPiece.getType(); int secondType = secondPiece.getType(); Bitmap firstBitmap = mImagePieces.get(mFirst.getId()).getBitmap(); Bitmap secondBitmap = mImagePieces.get(mSecond.getId()).getBitmap(); int fristIndex = firstPiece.getIndex(); int secondeIndex = secondPiece.getIndex(); if (mFirst ! = null) { mFirst.setColorFilter(null); mFirst.setVisibility(VISIBLE); mFirst.setImageBitmap(secondBitmap); firstPiece.setBitmap(secondBitmap); firstPiece.setIndex(secondeIndex); } if (mSecond ! = null) { mSecond.setVisibility(VISIBLE); mSecond.setImageBitmap(firstBitmap); secondPiece.setBitmap(firstBitmap); secondPiece.setIndex(fristIndex); } if (mGameMode.equals(GAME_MODE_NORMAL)) { firstPiece.setType(secondType); secondPiece.setType(firstType); } mAnimLayout.removeAllViews(); mAnimLayout.setVisibility(GONE); mFirst = null; mSecond = null; isAnimation = false; invalidate(); If (checkSuccess()) {toast.maketext (getContext(), "success!" , Toast.LENGTH_SHORT).show(); if (mSuccessListener ! = null) { mSuccessListener.success(); } } } @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); isAnimation = true; mAnimLayout.setVisibility(VISIBLE); mFirst.setVisibility(INVISIBLE); mSecond.setVisibility(INVISIBLE); }}); secondAnimator.start(); }Copy the code

3.6 Using DialogFragments

3.6.1 Basic Concepts

DialogFragment was introduced in Android 3.0. Fragment is a special Fragment used to display a modal dialog box on top of the content of an Activity. Typical use: display warning boxes, input boxes, confirmation boxes, etc.

3.6.2 Benefits of use

Using DialogFragment to manage dialog boxes, you can better manage the declaration period when rotating the screen and pressing the back button. It has basically the same declaration period as the Fragment. Dialogfragments also allow developers to reuse dialogs as embedded components, similar to fragments (which display different effects on large and small screens). Above will show these benefits by example ~ the above text from blog: blog.csdn.net/lmj62356579… The basic usage is in the above blog, not detailed usage.

DialogFragment is used for image selection and game success.





Pictures to choose

3.7 Some public apis

Provide some common ways to change the game’s mode, difficulty, graphics, etc. Each change should reset some of the necessary parameters.

Public void reset() {mItemWidth = (mViewWidth - mPadding * 2 - mMargin * (mCount - 1))/mCount; if (mImagePieces ! = null) { mImagePieces.clear(); } isAddAnimatorLayout = false; mBitmap = null; removeAllViews(); initBitmaps(); initBitmapsWidth(); } public Boolean addCount() {mCount++; if (mCount > 7) { mCount--; return false; } reset(); return true; } public void changeRes(int res) {this.res = res; reset(); } /** * reduceCount() {mCount--; if (mCount < 3) { mCount++; return false; } reset(); return true; }Copy the code

3.8 other

Some recommend a site: attribute animation: blog.csdn.net/lmj62356579… www.jianshu.com/p/ecba05115… www.jianshu.com/p/2412d00a0… DialogFragment: blog.csdn.net/lmj62356579… ViewGroup: blog.csdn.net/lmj62356579…

Source code address: github.com/AxeChen/Puz…

Original is not easy, if the big guy is interested in this article, but also hope to praise, to encourage!