English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Detalles de implementación del juego de rompecabezas de belleza en Android

Veamos primero el efecto:

La imagen se corta en muchas porciones, al hacer clic se ensambla en una imagen completa; de esta manera, también es fácil diseñar los niveles3 3;4 4;5 5;6 6; sigue así

Añadimos una animación de cambio, el efecto es bastante bueno, en realidad, el juego es personalizar un controlador, ahora comenzamos nuestra aventura de personalización.

Diseño del juego

Primero analicemos cómo diseñar este juego:

1, necesitamos un contenedor que pueda contener estos bloques de imágenes, para facilitar, preparamos para usar RelativeLayout con addRule

2, cada bloque de imagen, preparamos para usar ImageView

3, intercambio de clic, preparamos para usar TranslationAnimation tradicional

Con un diseño preliminar, siento que este juego es muy fácil~

Implementación del diseño del juego

Primero, preparamos la implementación de poder cortar una imagen en n*n porciones, colocadas en la posición especificada; solo necesitamos configurar el número n, y luego, según el ancho o alto más pequeño del diseño, dividirlo por n y restar algunos márgenes para obtener el ancho y alto de ImageView~~

Método constructor
/** 
  * Configura la cantidad de elementos n*n;por defecto es3 
  */ 
 private int mColumn = 3; 
 /** 
  * Ancho del layout 
  */ 
 private int mWidth; 
 /** 
  * Alineación de layout 
  */ 
 private int mPadding; 
 /** 
  * Almacena todos los elementos 
  */ 
 private ImageView[] mGamePintuItems; 
 /** 
  * El ancho del elemento 
  */ 
 private int mItemWidth; 
 /** 
  * El margen horizontal y vertical del elemento 
  */ 
 private int mMargin = 3; 
 /** 
  * La imagen del puzzle 
  */ 
 private Bitmap mBitmap; 
 /** 
  * Almacena el bean de la imagen después de cortar 
  */ 
 private List<ImagePiece> mItemBitmaps; 
 private boolean once; 
 public GamePintuLayout(Context context) { 
  this(context, null); 
 } 
 public GamePintuLayout(Context context, AttributeSet attrs) { 
  this(context, attrs, 0); 
 } 
 /**
  * El constructor se utiliza para inicializar
  * @param context the context
  * @param attrs the attrs
  * @param defStyle the def style
  * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
  */
 public GamePintuLayout(Context context, AttributeSet attrs, int defStyle) { 
  super(context, attrs, defStyle); 
 //Convertir el valor de margen configurado a dp
  mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 
    mMargin, getResources().getDisplayMetrics()); 
  // Configurar el relleno interno de Layout, lados iguales, configurar el valor más pequeño de los cuatro rellenos internos 
  mPadding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), 
    getPaddingBottom()); 
 }

En el método constructor, convertimos el valor de margen configurado a dp; obtenemos el valor de relleno del diseño; como es un cuadrado completo, tomamos el valor más pequeño de los cuatro lados de relleno; en cuanto al margen, como el espacio horizontal y vertical entre los elementos, puedes extraerlo como atributo personalizado si te gusta~~

onMeasure
/**
  * Se utiliza para configurar el ancho y alto del View personalizado
  * @param widthMeasureSpec the width measure spec
  * @param heightMeasureSpec la especificación de medida de altura
  * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
  */
@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  // Obtener el lado del diseño del juego 
  mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth()); 
  if (!once) { 
   initBitmap(); 
   initItem(); 
  } 
  once = true; 
  setMeasuredDimension(mWidth, mWidth); 
 }

En onMeasure, lo principal es obtener el ancho del diseño, luego preparar la imagen y inicializar nuestro Item, establecer el ancho y la altura del Item

initBitmap naturalmente es preparar la imagen:

/**
 * Inicializar bitmap
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */
private void initBitmap() { 
  if (mBitmap == null) 
   mBitmap = BitmapFactory.decodeResource(getResources(), 
     R.drawable.aa); 
  mItemBitmaps = ImageSplitter.split(mBitmap, mColumn); 
 //Ordenar las imágenes
  Collections.sort(mItemBitmaps, new Comparator<ImagePiece>(){ 
   @Override 
   public int compare(ImagePiece lhs, ImagePiece rhs){ 
   //Usamos random para comparar el tamaño aleatoriamente
    return Math.random() > 0.5 ? 1 : -1; 
   } 
  }); 
 }

Aquí, si no se ha configurado mBitmap, preparamos una imagen de respaldo y luego llamamos a ImageSplitter.split para cortar la imagen en n * n Devuelve una lista de ImagePiece. Después de cortar, necesitamos desordenar el orden, por lo que llamamos al método sort, en cuanto al comparador, usamos random para comparar el tamaño aleatoriamente, de esta manera completamos nuestra operación de desorden, ¿¡Qué te parece!?~

/**
 * Descripción: Clase de cortado de imágenes
 * Datos:2016/9/11-19:53
 * Blog: www.qiuchengjia.cn
 * Autor: qiu
 */
public class ImageSplitter { 
 /** 
  * Cortar la imagen en , pieza *piece 
  * @param bitmap 
  * @param piece 
  * @return 
  */ 
 public static List<ImagePiece> split(Bitmap bitmap, int piece){ 
  List<ImagePiece> pieces = new ArrayList<ImagePiece>(piece * piece); 
  int ancho = bitmap.getWidth(); 
  int altura = bitmap.getHeight(); 
  Log.e("TAG", "Ancho de bitmap = " + ancho + " , altura = " + height); 
  int pieceWidth = Math.min(width, height) / piece; 
  for (int i = 0; i < piece; i++{ 
   for (int j = 0; j < piece; j++{ 
    ImagePiece imagePiece = new ImagePiece(); 
    imagePiece.index = j + i * piece; 
    int xValue = j * pieceWidth; 
    int yValue = i * pieceWidth; 
    imagePiece.bitmap = Bitmap.createBitmap(bitmap, xValue, yValue, 
      pieceWidth, pieceWidth); 
    pieces.add(imagePiece); 
   } 
  } 
  return pieces; 
 } 
}
/**
 * Descripción: bean de imagen
 * Datos:2016/9/11-19:54
 * Blog: www.qiuchengjia.cn
 * Autor: qiu
 */
public class ImagePiece 
{ 
 public int index = 0; 
 public Bitmap bitmap = null; 
}

Siempre se habla de un proceso de cortar y guardar imágenes basado en el ancho, la altura y n~~

La imagen guardada por ImagePiece y el índice, por cierto, estos dos tipos fueron descubiertos por mí accidentalmente en línea~~

La imagen está lista aquí, ahora veamos que el Item ya ha configurado el ancho y la altura, es decir, initItems

/**
 * inicializar cada item
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */
private void initItem() { 
  // obtener el ancho del Item 
  int childWidth = (mWidth - mPadding * 2 - mMargin * 
  (mColumn - 1)) / mColumn; 
  mItemWidth = childWidth; 
  mGamePintuItems = new ImageView[mColumn * mColumn]; 
  // colocar Item 
  for (int i = 0; i < mGamePintuItems.length; i++) { 
   ImageView item = new ImageView(getContext()); 
   item.setOnClickListener(this); 
   item.setImageBitmap(mItemBitmaps.get(i).bitmap); 
   mGamePintuItems[i] = item; 
   item.setId(i + 1); 
    + "_" + mItemBitmaps.get(i).index); 
   
    new LayoutParams(mItemWidth, 
     mItemWidth); 
   // establecer el margen horizontal, no la última columna 
   if ((i + 1) % mColumn != 0) { 
    lp.rightMargin = mMargin; 
   } 
   // si no es la primera columna 
    
    lp.addRule(RelativeLayout.RIGHT_OF,// 
      mGamePintuItems[i - 1].getId()); 
   } 
   // si no es la primera fila,//establecer el margen vertical, no la última fila 
   if ((i + 1) > mColumn) { 
     
    lp.addRule(RelativeLayout.BELOW,// 
      mGamePintuItems[i - mColumn].getId()); 
   } 
   addView(item, lp); 
  } 
 }

Se puede ver la cálculo del ancho de nuestro Item: childWidth = (mWidth - mPadding 2 - mMargin (mColumn - 1) ) / mColumn; el ancho del contenedor, restando su margen interno, restando el espacio entre Item, luego dividiendo por el número de Item por fila se obtiene el ancho del Item~~

A continuación, es el ciclo de generación de Item, según su posición se configura la Rule, revise los comentarios con atención~~

Tenga en cuenta dos puntos:

1y hemos configurado el setOnClickListener para Item, por supuesto, porque nuestro juego es hacer clic en Item, ¿no es así?

2y también hemos configurado el Tag para Item: item.setTag(i + "_" + mItemBitmaps.get(i).index);

el tag contiene el índice, es decir, la posición correcta; además de i, i puede ayudarnos a encontrar la imagen actual del elemento en mItemBitmaps: (mItemBitmaps.get(i).bitmap))

hasta aquí, el código del diseño de nuestro juego se ha terminado~~~

luego declaramos en el archivo de diseño:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/
 android:layout_width="fill_parent"
 android:layout_height="fill_parent" >
 <game.qiu.com.beautygame.GamePintuLayout
  android:id="@"+id/id_gameview"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:layout_centerInParent="true"
  5dp" >
 </game.qiu.com.beautygame.GamePintuLayout>
</RelativeLayout>

en el Activity, recuerda configurar este diseño~~

el efecto actual es:

el efecto de cambio del juego

el cambio inicial

¿Te acuerdas de que le dimos a los elementos el escucha de onClick?~~

ahora necesitamos implementar, al pulsar dos elementos, sus imágenes pueden intercambiarse~

entonces, necesitamos dos variables miembro para almacenar estos dos elementos y luego intercambiarlos

/**
 * registra el ImageView del primer clic
 */
private ImageView mFirst; 
/**
 * registra el ImageView del segundo clic
 */
private ImageView mSecond; 
/**
 * evento de clic
 * @param view la vista
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */ 
@Override 
public void onClick(View v) { 
 /** 
  * si los dos clics son en el mismo 
  */ 
 if (mFirst == v) { 
  mFirst.setColorFilter(null); 
  mFirst = null; 
  return; 
 } 
 //pulsar el primer elemento 
 if (mFirst == null) { 
  mFirst = (ImageView) v; 
  mFirst.setColorFilter(Color.parseColor("#"),55FF0000")); 
 } else//Hacer clic en el segundo Item 
 { 
  mSecond = (ImageView) v; 
  exchangeView(); 
 } 
}

Al hacer clic en el primer elemento, configuramos el efecto de selección mediante setColorFilter, al hacer clic en otro, prepararemos exchangeView para intercambiar imágenes, por supuesto, este método aún no lo hemos escrito, dejémoslo por ahora~

Si se hace clic dos veces en el mismo, se elimina el efecto de selección, simplemente lo tratamos como si nada hubiera pasado

A continuación, implementamos exchangeView:

/**
 * Intercambiar las imágenes de dos Item 
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */
 private void exchangeView() { 
  mFirst.setColorFilter(null); 
  String firstTag = (String) mFirst.getTag(); 
  String secondTag = (String) mSecond.getTag(); 
  //Obtener la posición de índice en la lista 
  String[] firstImageIndex = firstTag.split("_"); 
  String[] secondImageIndex = secondTag.split("_"); 
  mFirst.setImageBitmap(mItemBitmaps.get(Integer 
    .parseInt(secondImageIndex[0])).bitmap); 
  mSecond.setImageBitmap(mItemBitmaps.get(Integer 
    .parseInt(firstImageIndex[0])).bitmap); 
  mFirst.setTag(secondTag); 
  mSecond.setTag(firstTag); 
  mFirst = mSecond = null; 
 }

Deberías recordar que habíamos mencionado setTag antes, si lo olvidaste, regresa a verlo, aún estamos hablando de esto~

A través de getTag, obtener la posición de índice en la lista, luego obtener el bitmap para configurar el intercambio, finalmente intercambiar tag;

Hasta aquí, hemos terminado de escribir el efecto de intercambio, nuestro juego puede terminar~~

El efecto es así:

Puedes ver que ya podemos jugar, en cuanto a por qué no se usa una imagen de paisaje fresca, es porque realmente no se puede ver qué parte es qué, o la chica es más intuitiva~

Seguramente todos estarán quejándose, ¡caray, ¿dónde está el cambio de animación? ¡Claro, debería ser el intercambio de dos volando para cambiar de posición, esto es qué?

También, para el programa, debemos tener ambiciones, ahora vamos a agregar el efecto de cambio de animación~~

Cambio de animación sin interrupción

Primero hablemos de cómo agregar, estoy preparando TranslationAnimation, y luego las dos posiciones superior e izquierda de los dos Item también se obtienen del contenedor;

Pero, para entender, realmente, el Item solo ha cambiado setImage, la posición del Item no ha cambiado;

Ahora necesitamos efectos de movimiento de animación, por ejemplo, A se mueve a B, no hay problema, después de que se complete el movimiento, el Item debe regresar, pero la imagen no ha cambiado, aún necesitamos establecer manualmente setImage;

Así, se ha producido un fenómeno, el efecto de cambio de animación está, pero al final, aún hay un parpadeo, es causado por el cambio de imagen;

Para evitar este fenómeno, lograr un efecto de cambio perfecto, aquí introducimos una capa de animación, dedicada a los efectos de animación, algo similar a la capa de Photoshop, veamos cómo lo hacemos a continuación;

/** 
 * Indicador de ejecución de animación 
 */ 
private boolean isAniming; 
/** 
 * Capa de animación 
 */ 
private RelativeLayout mAnimLayout; 
/**
 * Intercambiar las imágenes de dos Item
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */
private void exchangeView(){ 
  mFirst.setColorFilter(null); 
  setUpAnimLayout(); 
  // Agregar FirstView 
  ImageView first = new ImageView(getContext()); 
  first.setImageBitmap(mItemBitmaps 
    .get(getImageIndexByTag((String) mFirst.getTag())).bitmap); 
  LayoutParams lp = new LayoutParams(mItemWidth, mItemWidth); 
  lp.leftMargin = mFirst.getLeft() - mPadding; 
  lp.topMargin = mFirst.getTop() - mPadding; 
  first.setLayoutParams(lp); 
  mAnimLayout.addView(first); 
  // Agregar SecondView 
  ImageView second = new ImageView(getContext()); 
  second.setImageBitmap(mItemBitmaps 
    .get(getImageIndexByTag((String) mSecond.getTag())).bitmap); 
  LayoutParams lp2 = new LayoutParams(mItemWidth, mItemWidth); 
  lp2.leftMargin = mSecond.getLeft() - mPadding; 
  lp2.topMargin = mSecond.getTop() - mPadding; 
  second.setLayoutParams(lp2); 
  mAnimLayout.addView(second); 
  // Configurar animación 
  TranslateAnimation anim = new TranslateAnimation(0, mSecond.getLeft() 
    - mFirst.getLeft(), 0, mSecond.getTop() - mFirst.getTop()); 
  anim.setDuration(300); 
  anim.setFillAfter(true); 
  first.startAnimation(anim); 
  TranslateAnimation animSecond = new TranslateAnimation(0, 
    mFirst.getLeft() - mSecond.getLeft(), 0, mFirst.getTop() 
      - mSecond.getTop()); 
  animSecond.setDuration(300); 
  animSecond.setFillAfter(true); 
  second.startAnimation(animSecond); 
  // Añadir escucha de animación 
  anim.setAnimationListener(new AnimationListener(){ 
   @Override 
   public void onAnimationStart(Animation animation){ 
    isAniming = true; 
    mFirst.setVisibility(INVISIBLE); 
    mSecond.setVisibility(INVISIBLE); 
   } 
   @Override 
   public void onAnimationRepeat(Animation animation){ 
   } 
   @Override 
   public void onAnimationEnd(Animation animation){ 
    String firstTag = (String) mFirst.getTag(); 
    String secondTag = (String) mSecond.getTag(); 
    String[] firstParams = firstTag.split("_"); 
    String[] secondParams = secondTag.split("_"); 
    mFirst.setImageBitmap(mItemBitmaps.get(Integer 
      .parseInt(secondParams[0])).bitmap); 
    mSecond.setImageBitmap(mItemBitmaps.get(Integer 
      .parseInt(firstParams[0])).bitmap); 
    mFirst.setTag(secondTag); 
    mSecond.setTag(firstTag); 
    mFirst.setVisibility(VISIBLE); 
    mSecond.setVisibility(VISIBLE); 
    mFirst = mSecond = null; 
    mAnimLayout.removeAllViews(); 
        //checkSuccess(); 
    isAniming = false; 
   } 
  }); 
 } 
 /** 
  * Create animation layer 
  */ 
 private void setUpAnimLayout(){ 
  if (mAnimLayout == null){ 
   mAnimLayout = new RelativeLayout(getContext()); 
   addView(mAnimLayout); 
  } 
 } 
 private int getImageIndexByTag(String tag){ 
  String[] split = tag.split("_"); 
  return Integer.parseInt(split[0]); 
 }

When starting the exchange, we create an animation layer, then add two identical Items on this layer, hide the original Item, and then carry out the animation switch freely, setFillAfter to true~

After the animation is completed, we have quietly exchanged the image of the Item and directly displayed it. Thus, the switch is perfect:

The general process:

    1A, B hidden

    2A copy animation moves to B's position; B copy moves to A's position

    3A sets the image to B, removes the B copy, and A is displayed, thus perfectly fitting, making the user feel that B has moved over

    4As above, B

Now our effect:

Now the effect is satisfactory~~ In order to prevent users from clicking repeatedly, add a sentence in the onClick:

@Override 
 public void onClick(View v) 
 { 
  // If the animation is in progress, it will be blocked 
  if (isAniming) 
   return;

Up to this point, our animation switch has been perfectly completed~~

When switching, should we judge whether it has succeeded~~

Judgment of game victory

We have completed the switch, and now we perform the checkSuccess(); judgment; fortunately, we have kept the correct order of the images in the tag~~

/**
 * To determine if the game is successful
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */
private void checkSuccess(){ 
  boolean isSuccess = true; 
  for (int i = 0; i < mGamePintuItems.length; i++{ 
   ImageView first = mGamePintuItems[i]; 
   Log.e("TAG", getIndexByTag((String) first.getTag()) + "" 
   if (getIndexByTag((String) first.getTag()) != i){ 
    isSuccess = false; 
   } 
  } 
  if (isSuccess){ 
   Toast.makeText(getContext(), "Éxito, Subir de Nivel !", 
     Toast.LENGTH_LONG).show(); 
   // nextLevel(); 
  } 
 } 
 /** 
  * Obtener el índice real de la imagen 
  * @param tag 
  * @return 
  */ 
 private int getIndexByTag(String tag){ 
  String[] split = tag.split("_"); 
  return Integer.parseInt(split[1]); 
 }

Es muy simple, recorrer todos los Item, obtener el índice real y la secuencia natural en función del Tag, si son completamente consistentes, se gana ~ Después de ganar, ingrese al siguiente nivel

Respecto al código del siguiente nivel:

public void nextLevel(){ 
  this.removeAllViews(); 
  mAnimLayout = null; 
  mColumn++; 
  initBitmap(); 
  initItem(); 
 }

Resumen

Bueno, con esto, la introducción de este artículo ha terminado básicamente. Los amigos interesados pueden intentar hacerlo por su cuenta, de esta manera les ayudará a entender y aprender mejor. Si tienen alguna pregunta, pueden dejar un mensaje para intercambiar.

Te gustará