English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
d'abord, regardons l'effet :
l'image est coupée en plusieurs parties, cliquez pour échanger et former une image complète ; ainsi, la conception des niveaux est aussi facile3 3;4 4;5 5;6 6;continue ainsi
ajouté une animation de basculement, l'effet est toujours bon, en réalité, le jeu consiste à personnalisé un contrôle, nous allons commencer notre voyage de personnalisation.
conception du jeu
Tout d'abord, analysons comment concevoir ce jeu :
1、nous avons besoin d'un conteneur pour mettre ces blocs d'images, pour la commodité, nous préparons à utiliser RelativeLayout avec addRule pour réaliser
2、chaque bloc d'image, nous préparons à utiliser ImageView
3、échange au clic, nous préparons à utiliser la TranslationAnimation traditionnelle pour réaliser
Avec une conception initiale, on a l'impression que ce jeu est très facile~
implémentation du layout de jeu
Tout d'abord, nous préparons pour réaliser la coupe d'une image en n*n parts, placées à des emplacements spécifiés ; nous n'avons besoin que de définir le nombre n, puis de diviser la plus petite valeur de la largeur ou de la hauteur du layout par n, et de soustraire un certain espace de marge pour obtenir la largeur et la hauteur de l'ImageView~~
constructeur /** * définir le nombre d'Item n*n;par défaut3 */ private int mColumn = 3; /** * largeur du layout */ private int mWidth; /** * padding du layout */ private int mPadding; /** * stocke tous les Item */ private ImageView[] mGamePintuItems; /** * Largeur de l'élément */ private int mItemWidth; /** * Marge horizontale et verticale de l'élément */ private int mMargin = 3; /** * Image du puzzle */ private Bitmap mBitmap; /** * Stocke le bean d'image après la coupe */ private List<ImagePiece> mItemBitmaps; private boolean once; public GamePintuLayout(Context context) { this(context, null); } public GamePintuLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * Constructeur, utilisé pour initialiser * @param context the context * @param attrs the attrs * @param defStyle the def style * @author qiu Blog: www.qiuchengjia.cn Date:2016-09-12 */ public GamePintuLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); //Convertir la valeur de marge définie en dp mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mMargin, getResources().getDisplayMetrics()); // Définir l'intérieur du Layout, les quatre côtés sont identiques, définir la plus petite valeur des quatre marges internes mPadding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); }
Dans le constructeur, nous transformons les valeurs de marge définies en dp ; nous obtenons les valeurs de marge de remplissage du layout ; étant donné que l'ensemble est un carré, nous prenons la plus petite valeur des quatre directions de marge de remplissage ; pour ce qui est de la marge, en tant que distance horizontale et verticale entre les éléments, vous pouvez la extraire comme une propriété personnalisée si vous le souhaitez~~
onMeasure /** * Utilisé pour définir la largeur et la hauteur personnalisées de la vue * @param widthMeasureSpec the width measure spec * @param heightMeasureSpec le spécificateur de mesure de hauteur * @author qiu Blog: www.qiuchengjia.cn Date:2016-09-12 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // Obtenir la longueur et la largeur du layout de jeu mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth()); if (!once) { initBitmap(); initItem(); } once = true; setMeasuredDimension(mWidth, mWidth); }
Dans onMeasure, il s'agit principalement de obtenir la largeur du layout, puis de préparer l'image, ainsi que d'initialiser notre Item, de définir la largeur et la hauteur de l'Item
initBitmap c'est naturellement préparer l'image :
/** * Initialiser bitmap * @author qiu Blog: www.qiuchengjia.cn Date:2016-09-12 */ private void initBitmap() { if (mBitmap == null) mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.aa); mItemBitmaps = ImageSplitter.split(mBitmap, mColumn); //Trier les images Collections.sort(mItemBitmaps, new Comparator<ImagePiece>(){ @Override public int compare(ImagePiece lhs, ImagePiece rhs){ //Nous utilisons random pour comparer la taille aléatoirement return Math.random() > 0.5 ? 1 : -1; } }); }
Si nous n'avons pas configuré mBitmap, nous préparons une image de rechange, puis appelons ImageSplitter.split pour couper l'image en n * n Retourne une liste de ImagePiece. Après la coupe, nous devons désorganiser l'ordre, donc nous appelons la méthode sort, en ce qui concerne le comparateur, nous utilisons random pour comparer la taille aléatoirement, ainsi nous avons terminé notre opération de désordre, est-ce que c'est bien ou pas~~
/** * Description: Classe de coupe d'image * Données :2016/9/11-19:53 * Blog : www.qiuchengjia.cn * Auteur: qiu */ public class ImageSplitter { /** * Couper l'image en , morceau *piece * @param bitmap * @param piece * @return */ public static List<ImagePiece> split(Bitmap bitmap, int piece){ List<ImagePiece> pieces = new ArrayList<ImagePiece>(piece * piece); int width = bitmap.getWidth(); int height = bitmap.getHeight(); Log.e("TAG", "largeur bitmap = " + width + " , height = " + 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); } } retour pieces; } }
/** * Description : objet d'image * Données :2016/9/11-19:54 * Blog : www.qiuchengjia.cn * Auteur: qiu */ public class ImagePiece { public int index = 0; public Bitmap bitmap = null; }
On parle toujours d'un processus de découpe et d'enregistrement d'images basé sur la largeur, la hauteur, et n~~
L'image enregistrée par ImagePiece et l'index, d'ailleurs, ces deux classes ont été découvertes par hasard sur Internet~~
L'image est prête maintenant, regardez l'élaboration de l'élément a déjà fixé la largeur et la hauteur, c'est-à-dire initItems
/** * initialiser chaque élément * @author qiu Blog: www.qiuchengjia.cn Date:2016-09-12 */ private void initItem() { // obtenir la largeur de l'élément int childWidth = (mWidth - mPadding * 2 - mMargin * (mColumn - 1)) / mColumn; mItemWidth = childWidth; mGamePintuItems = new ImageView[mColumn * mColumn]; // Placer l'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); item.setTag(i + "_" + mItemBitmaps.get(i).index); RelativeLayout.LayoutParams lp = new LayoutParams(mItemWidth, mItemWidth); // Configurer la marge horizontale, sauf pour la dernière colonne if ((i + 1) % mColumn != 0) { lp.rightMargin = mMargin; } // Si ce n'est pas la première colonne lp.addRule(RelativeLayout.RIGHT_OF,// mGamePintuItems[i - 1].getId()); } // Si ce n'est pas la première ligne,//Configurer la marge verticale, sauf pour la dernière ligne if ((i + 1) > mColumn) { lp.addRule(RelativeLayout.BELOW,// mGamePintuItems[i - mColumn].getId()); } addView(item, lp); } }
On peut voir la calcul de la largeur de notre Item : childWidth = (mWidth - mPadding 2 - mMargin (mColumn - 1) ) / La largeur du conteneur mColumn; enlevant les marges internes, enlevant l'espacement entre les Item, puis en divisant par le nombre d'Item par ligne, on obtient la largeur de l'Item~~
Ensuite, il s'agit de parcourir et générer des Item, en fonction de leurs positions pour configurer les Rules, regardez attentivement les commentaires~~
Il y a deux points à noter :
1et nous avons configuré un setOnClickListener pour l'Item, bien sûr, car notre jeu consiste à cliquer sur l'Item, non~
2et aussi, nous avons configuré un Tag pour l'Item : item.setTag(i + "_" + mItemBitmaps.get(i).index);
Le tag contient l'index, c'est-à-dire la position correcte ; il y a aussi i, qui peut nous aider à trouver l'image actuelle de l'élément dans mItemBitmaps : (mItemBitmaps.get(i).bitmap))
Ainsi, le code de mise en page de notre jeu est terminé~~~
Ensuite, déclarons-le dans le fichier de mise en page :
<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>
N'oubliez pas de configurer ce layout dans l'Activity~~
L'effet actuel est :
Effet de commutation du jeu
Changement initial
Souvenez-vous que nous avons ajouté un écouteur d'événement onClick aux éléments~
Nous devons maintenant réaliser que, en cliquant sur deux éléments, leurs images peuvent être échangées~
Alors, nous avons besoin de deux variables membres pour stocker ces deux éléments, puis pour les échanger
/** * Enregistre l'ImageView du premier clic */ private ImageView mFirst; /** * Enregistre l'ImageView du deuxième clic */ private ImageView mSecond; /** * Événement de clic * @param view la vue * @author qiu Blog: www.qiuchengjia.cn Date:2016-09-12 */ @Override public void onClick(View v) { /** * Si les deux clics sont sur le même élément */ if (mFirst == v) { mFirst.setColorFilter(null); mFirst = null; return; } //Cliquez sur le premier élément if (mFirst == null) { mFirst = (ImageView) v; mFirst.setColorFilter(Color.parseColor("#"),55FF0000")); } else//Cliquez sur le deuxième Item { mSecond = (ImageView) v; exchangeView(); } }
Cliquez sur le premier, configurez l'effet de sélection avec setColorFilter, cliquez à nouveau sur l'autre, alors nous allons appeler exchangeView pour échanger des images, bien sûr, cette méthode n'est pas encore écrite, mettons-la de côté~
Si vous cliquez deux fois sur le même, retirez l'effet de sélection, nous prenons cela pour rien ne s'est passé
Ensuite, nous allons réaliser exchangeView :
/** * Échanger les images de deux Item * @author qiu Blog: www.qiuchengjia.cn Date:2016-09-12 */ private void exchangeView() { mFirst.setColorFilter(null); String firstTag = (String) mFirst.getTag(); String secondTag = (String) mSecond.getTag(); //Obtenir l'index de position dans la liste 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; }
Vous devriez vous souvenir de notre setTag précédent, si vous l'avez oublié, allez-y voir, nous avons dit d'attention~
Nous obtenons l'index dans la liste via getTag, puis nous obtenons le bitmap pour effectuer l'échange de configuration, et enfin, nous échangeons les tag;
Ainsi, notre effet d'échange est terminé, notre jeu peut être terminé~~
L'effet est ainsi :
Vous pouvez voir que nous pouvons jouer maintenant, pourquoi ne pas utiliser des images de paysage fraîches, c'est parce que, il est vraiment difficile de voir quel morceau correspond à quel autre, ou que la fille est plus intuitive~
Beaucoup de gens vont sans doute se plaindre, oh, la transition d'animation, c'est clair, ce n'est pas deux qui volent pour échanger des positions ? C'est quoi ça ?
C'est aussi vrai, nous devons avoir des exigences pour le programme, ajoutons maintenant l'effet de transition d'animation~~
Transition d'animation fluide
Parlons d'abord de la manière d'ajouter, je vais utiliser TranslationAnimation, puis les Item top et left aussi sont récupérés du conteneur;
Mais, il faut comprendre que nous avons en réalité, l'Item n'a changé que l'image setImage, la position de l'Item n'a pas changé;
Nous avons besoin de l'effet de déplacement de l'animation maintenant, par exemple, A se déplace vers B, pas de problème, une fois le déplacement terminé, l'Item doit retourner à sa place, mais l'image n'a pas changé, nous devons toujours utiliser setImage manuellement;
Cela cause un phénomène : l'effet de transition d'animation est là, mais il y aura encore un éclair, c'est à cause de la transition d'image que nous avons faite;
Pour éviter ce phénomène, pour obtenir un effet de transition parfait, nous introduisons une couche d'animation spéciale pour l'effet d'animation, un peu comme les calques dans Photoshop, voyons comment nous le faisons ci-dessous;
/** * Drapeau de l'exécution de l'animation */ private boolean isAniming; /** * Couche d'animation */ private RelativeLayout mAnimLayout; /** * Échanger les images de deux Item * @author qiu Blog: www.qiuchengjia.cn Date:2016-09-12 */ private void exchangeView(){ mFirst.setColorFilter(null); setUpAnimLayout(); // Ajouter 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); // Ajouter 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); // Configurer l'animation 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); // ajouter un écouteur d'animation 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]); }
At the beginning of the exchange, we create an animation layer, then add two identical Items to this layer, hide the original Item, and then carry out the animation switch freely, setFillAfter to true~
After the animation is finished, we have quietly exchanged the image of the Item and directly displayed it. This perfectly switches:
The general process:
1A, B hidden
2A copy moves to the position of B; B copy moves to the position of A
3A sets the image to B, removes the copy of B, and shows A, so that it perfectly fits, and the user feels that B moves over
4Same as B
Now our effect:
Now the effect is satisfactory~~ To prevent users from clicking too hard, add a line in onClick:
@Override public void onClick(View v) { // If the animation is in progress, then block if (isAniming) return;
Up to now, our animation switch has been perfectly completed~~
When switching, should we judge whether we have succeeded~~
Judgment of game victory
We have finished the switch, now we are performing the checkSuccess(); judgment; fortunately, we have stored the correct order of the pictures in the tag~~
/** * used to judge whether the game is successful * @author qiu Blog: www.qiuchengjia.cn Date: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(), "Success , Level Up !", Toast.LENGTH_LONG).show(); // nextLevel(); } } /** * Obtenir l'index réel de l'image * @param tag * @return */ private int getIndexByTag(String tag){ String[] split = tag.split("_"); return Integer.parseInt(split[1]); }
Très simple, itérer sur tous les Item, obtenir l'index réel et la comparaison de l'ordre naturel, s'ils sont complètement identiques, la victoire~~ Après la victoire, passez au niveau suivant
Quant au code du niveau suivant :
public void nextLevel(){ this.removeAllViews(); mAnimLayout = null; mColumn++; initBitmap(); initItem(); }
Résumé
Bon, voilà, la présentation de notre article est pratiquement terminée. Les amis intéressés peuvent essayer par eux-mêmes, ce qui aidera davantage à comprendre et à apprendre. Si vous avez des questions, vous pouvez laisser des commentaires pour échanger.