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