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

Detaillierte Erklärung der Android-Bilderrätselspiel-Implementierung von Beautiful Girls

Schauen wir uns zunächst den Effekt an:

Das Bild wird in viele Stücke geschnitten, durch Klicken zum Austausch ein vollständiges Bild zusammengefügt; So ist das Level auch einfach zu gestalten,3 3;4 4;5 5;6 6; und so weiter

Haben eine Umschaltanimation hinzugefügt, der Effekt ist immer noch gut, in der Tat ist das Spiel nur ein benutzerdefinierter Steuerelement, beginnen wir mit dem Customizing

Entwurf des Spiels

Zunächst analysieren wir, wie wir dieses Spiel gestalten können:

1, Wir benötigen einen Behälter, um diese Bildblöcke zu platzieren, um es einfacher zu machen, wir planen, RelativeLayout mit addRule zu verwenden

2, Jeder Bildblock, wir planen, ImageView zu verwenden

3, Klicken zum Austausch, wir planen, die traditionelle TranslationAnimation zu verwenden

Mit dem ersten Design, fühlt sich das Spiel so einfach an~

Umsetzung der Spiel-Layout

Zunächst einmal, wir möchten eine Implementierung realisieren, die ein Bild in n*n Stücke, an bestimmten Positionen platzieren; Wir müssen nur die Zahl n einstellen und dann, basierend auf dem kleineren Wert von Breite oder Höhe des Layouts, durch n teilen und einige Abstände abziehen, um die Breite und Höhe des ImageView zu erhalten~~

Konstruktor
/** 
  * Die Anzahl der Elemente n setzen*n; Standardwert ist3 
  */ 
 private int mColumn = 3; 
 /** 
  * Breite der Layout 
  */ 
 private int mWidth; 
 /** 
  * Padding der Layout 
  */ 
 private int mPadding; 
 /** 
  * Speichert alle Elemente 
  */ 
 private ImageView[] mGamePintuItems; 
 /** 
  * Breite des Items 
  */ 
 private int mItemWidth; 
 /** 
  * Abstand zwischen den horizontalen und vertikalen Kanten der Items 
  */ 
 private int mMargin = 3; 
 /** 
  * Bilder des Puzzles 
  */ 
 private Bitmap mBitmap; 
 /** 
  * Speichert das Bildbean nach dem Schneiden 
  */ 
 private List<ImagePiece> mItemBitmaps; 
 private boolean once; 
 public GamePintuLayout(Context context) { 
  this(context, null); 
 } 
 public GamePintuLayout(Context context, AttributeSet attrs) { 
  this(context, attrs, 0); 
 } 
 /**
  * Konstruktor, um zu initialisieren
  * @param context der Kontext
  * @param attrs die Attribute
  * @param defStyle der Standardstil
  * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
  */
 public GamePintuLayout(Context context, AttributeSet attrs, int defStyle) { 
  super(context, attrs, defStyle); 
 //Den festgelegten Margenwert in dp umwandeln
  mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 
    mMargin, getResources().getDisplayMetrics()); 
  // Setzen Sie den Innenabstand des Layouts, vierseitig gleich, auf den kleinsten Wert der vier Innenabstände 
  mPadding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), 
    getPaddingBottom()); 
 }

Im Konstruktor konvertieren wir die festgelegten Margenwerte in dp; erhalten wir den Paddingswert der Anordnung; da der gesamte Körper ein Quadrat ist, nehmen wir den kleinsten Wert der vier Paddings in Betracht; was die Margen betrifft, können sie als horizontaler und vertikaler Abstand zwischen den Items extrahiert werden, falls Sie möchten ~~~

onMeasure
/**
  * Wird verwendet, um die Breite und Höhe eines benutzerdefinierten Views zu setzen
  * @param widthMeasureSpec der Breitenmaßspezifikation
  * @param heightMeasureSpec der Höhe-Messspezifikationsparameter
  * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
  */
@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  // Ecken der Spielansicht erhalten 
  mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth()); 
  if (!once) { 
   initBitmap(); 
   initItem(); 
  } 
  once = true; 
  setMeasuredDimension(mWidth, mWidth); 
 }

In onMeasure wird hauptsächlich die Breite des Layouts erhalten, dann wird das Bild vorbereitet und unser Item initialisiert, um der Item Breite und Höhe zu setzen

initBitmap bedeutet natürlich, das Bild vorzubereiten:

/**
 * Bitmap initialisieren
 * @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); 
 //Bilder sortieren
  Collections.sort(mItemBitmaps, new Comparator<ImagePiece>(){ 
   @Override 
   public int compare(ImagePiece lhs, ImagePiece rhs){ 
   //Wir verwenden random, um die Größe zufällig zu vergleichen
    return Math.random() > 0.5 ? 1 : -1; 
   } 
  }); 
 }

Falls hier mBitmap nicht gesetzt ist, bereiten wir ein Ersatzbild vor und rufen ImageSplitter.split auf, um das Bild in n zu schneiden * n gibt eine Liste von ImagePiece zurück. Nachdem das Schneiden abgeschlossen ist, müssen wir die Reihenfolge durcheinander bringen, daher haben wir die Methode sort aufgerufen. Was den Comparator betrifft, verwenden wir random, um die Größe zufällig zu vergleichen, so dass wir unsere Durcheinander-Operation abgeschlossen haben, toll oder nicht?

/**
 * Beschreibung: Bild-Schnittklasse
 * Daten:2016/9/11-19:53
 * Blog: www.qiuchengjia.cn
 * Autor: qiu
 */
public class ImageSplitter { 
 /** 
  * 将图片切成, piece *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", "bitmap Width = " + 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); 
   } 
  } 
  return pieces; 
 } 
}
/**
 * Beschreibung: Bildbean
 * Daten:2016/9/11-19:54
 * Blog: www.qiuchengjia.cn
 * Autor: qiu
 */
public class ImagePiece 
{ 
 public int index = 0; 
 public Bitmap bitmap = null; 
}

Es geht immer darum, ein Verfahren zur Schere und Speicherung von Bildern basierend auf Breite, Höhe und n zu beschreiben~~

Das Bild und der Index, die von ImagePiece gespeichert werden, übrigens habe ich diese beiden Klassen zufällig im Internet entdeckt~~

Das Bild ist jetzt bereit, das Erstellen des Elements ist bereits auf Breite und Höhe eingestellt, d.h. initItems

/**
 * initialisieren Sie jedes Element
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */
private void initItem() { 
  // erhalten Sie die Breite des Elements 
  int childWidth = (mWidth - mPadding * 2 - mMargin * 
  (mColumn - 1)) / mColumn; 
  mItemWidth = childWidth; 
  mGamePintuItems = new ImageView[mColumn * mColumn]; 
  // Item platzieren 
  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); 
   // den horizontalen Abstand einstellen, nicht die letzte Spalte 
   if ((i + 1) % mColumn != 0) { 
    lp.rightMargin = mMargin; 
   } 
   // Wenn es nicht die erste Spalte ist, 
    
    lp.addRule(RelativeLayout.RIGHT_OF,// 
      mGamePintuItems[i - 1].getId()); 
   } 
   // Wenn es nicht die erste Zeile ist,//den vertikalen Abstand einstellen, nicht das letzte Zeile 
   if ((i + 1) > mColumn) { 
     
    lp.addRule(RelativeLayout.BELOW,// 
      mGamePintuItems[i - mColumn].getId()); 
   } 
   addView(item, lp); 
  } 
 }

Man kann sehen, wie wir die Breite des Items berechnen: childWidth = (mWidth - mPadding 2 - mMargin (mColumn - 1) ) / mColumn; die Breite des Containers, abzüglich der eigenen Innenabstand, abzüglich des Abstands zwischen Item, dann durch die Anzahl der Item in einer Reihe zu teilen, erhalten wir die Breite des Item~~

Nächste, das ist das Durchlaufen und Erstellen von Item, basierend auf ihrer Position Rule zu setzen, schauen Sie sich die Kommentare sorgfältig an~~

Beachten Sie zwei Punkte:

1、wir haben dem Item setOnClickListener zugewiesen, das ist natürlich, weil unser Spiel ja auf Items klickt, oder~

2、und wir haben dem Item ein Tag gesetzt: item.setTag(i + "_" + mItemBitmaps.get(i).index);

Der Tag enthält den Index, also die richtige Position; sowie i, das uns hilft, das aktuelle Bild des Elements in mItemBitmaps zu finden: (mItemBitmaps.get(i).bitmap))

Bis hierher ist der Code für das Layout unseres Spiels beendet~~~

Dann erklären wir im Layout-Datei:

<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>

Denken Sie daran, in der Activity diese Layout zu setzen~~

Derzeitige Wirkung ist:

Effekt des Wechsels im Spiel

Erste Wechsel

Denken Sie daran, dass wir jedem Element das onClick-Event hinzugefügt haben~~

Jetzt müssen wir implementieren, dass bei einem Klick auf zwei Elemente ihre Bilder ausgetauscht werden~

Daher benötigen wir zwei Member-Variable, um diese beiden Elemente zu speichern und sie dann auszutauschen

/**
 * Speichert das erste geklickte ImageView
 */
private ImageView mFirst; 
/**
 * Speichert das zweite geklickte ImageView
 */
private ImageView mSecond; 
/**
 * Klickereignis
 * @param view der View
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */ 
@Override 
public void onClick(View v) { 
 /** 
  * Wenn der zweite Klick auf dasselbe Element erfolgt 
  */ 
 if (mFirst == v) { 
  mFirst.setColorFilter(null); 
  mFirst = null; 
  return; 
 } 
 //Klicken Sie auf das erste Element 
 if (mFirst == null) { 
  mFirst = (ImageView) v; 
  mFirst.setColorFilter(Color.parseColor("#"));55FF0000")); 
 } else//Klicken Sie auf den zweiten Item 
 { 
  mSecond = (ImageView) v; 
  exchangeView(); 
 } 
}

Klicken Sie auf den ersten, um die Auswahlwirkung durch setColorFilter zu setzen, klicken Sie erneut auf den anderen, dann rufen wir exchangeView zum Austausch des Bildes auf, natürlich haben wir diese Methode noch nicht geschrieben, legen wir sie erst mal beiseite~

Wenn auf dasselbe zweimal geklickt wird, wird das Auswahlereignis entfernt, wir behandeln es so, als wäre nichts passiert;

Nun, wir implementieren exchangeView:

/**
 * 交换两个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(); 
  //Erhalten wir den Indexposition in der 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; 
 }

Man sollte sich an unser之前的setTag erinnern, vergisst, schau nach, wir haben noch gesagt, darauf zu achten~

Durch getTag erhalten wir den Index in der Liste, dann erhalten wir das Bitmap zum Austausch und setzen schließlich den Tag;

Bis hierher ist unser Austauscheffekt abgeschlossen, unser Spiel kann abgeschlossen sein~~

Der Effekt ist so:

Man kann sehen, dass wir bereits spielen können, warum wir nicht eine klare Landschaftsaufnahme verwenden, ist, weil es wirklich nicht zu erkennen ist, was zu was gehört, oder das Mädchen ist intuitiv~

Natürlich wird jemand nörgeln, oh, der Animationsswitch, das ist doch nicht das, was zwei fliegen und ihre Positionen tauschen sollen, was ist das für ein Zeug?

Ja, wir müssen für das Programm Ansprüche haben, lassen Sie uns also den Animationsswitch hinzufügen~~

Seamlose Animationsswitch

Lassen Sie uns zunächst darüber sprechen, wie man hinzufügt, ich plane, TranslationAnimation zu verwenden, und dann die top und left der beiden Items auch vom Container zu erhalten;

Aber, um zu verstehen, dass wir tatsächlich nur setImage des Items geändert haben, die Position des Items hat sich nicht geändert;

Wir benötigen jetzt einen Animationsschiebereffekt, zum Beispiel A bewegt sich zu B, kein Problem, nach Abschluss der Bewegung muss das Item zurückkehren, aber das Bild hat sich nicht verändert, wir müssen also manuell setImage aufrufen;

Dies führt zu einem Phänomen, der Übergangseffekt der Animation ist vorhanden, aber am Ende wird es immer noch einen Blitz geben, der durch das Wechseln der Bilder verursacht wird;

Um das genannte Phänomen zu vermeiden und eine perfekte Übergangseffekt zu erzielen, führen wir hier einen Animationslayer ein, der speziell für Animationseffekte verwendet wird, ähnlich wie eine Ebene in Photoshop. Schauen wir uns an, wie wir das machen;

/** 
 * 动画运行的标志位 
 */ 
private boolean isAniming; 
/** 
 * 动画层 
 */ 
private RelativeLayout mAnimLayout; 
/**
 * 交换两个Item图片
 * @author qiu Blog: www.qiuchengjia.cn Time:2016-09-12
 */
private void exchangeView(){ 
  mFirst.setColorFilter(null); 
  setUpAnimLayout(); 
  // 添加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); 
  // 添加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); 
  // 设置动画 
  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); 
  // Fügen Sie einen Animationshörer hinzu 
  anim.setAnimationListener(new AnimationListener(){ 
   @Override 
   public void onAnimationStart(Animation animation){ 
    isAnimating = 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 the exchange starts, we create an animation layer, then add two identical Items to this layer, hide the original Item, and then freely perform the animation switch, setFillAfter to true~

After the animation is finished, we have quietly swapped the image of the Item and displayed it directly. This perfectly switches:

The general process:

    1A, B are 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 B copy, A is displayed, and it perfectly fits together, making the user feel that B has moved over

    4Same as B

Now our effect:

Now the effect is satisfactory. To prevent users from clicking repeatedly, add a sentence in the onClick:

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

By now, our animation switch has been perfectly completed~~

When switching, shouldn't we judge if it has succeeded~~

Judgment of game victory

We have completed the switch, now we make the judgment of checkSuccess(); lucky us, we have the correct order of the images stored in tag~~

/**
 * Used 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(), "Success , Level Up !", 
     Toast.LENGTH_LONG).show(); 
   // nextLevel(); 
  } 
 } 
 /** 
  * 获得图片的真正索引 
  * @param tag 
  * @return 
  */ 
 private int getIndexByTag(String tag){ 
  String[] split = tag.split("_"); 
  return Integer.parseInt(split[1]); 
 }

很简单,遍历所有的Item,根据Tag拿到真正的索引和当然顺序比较,完全一致则胜利~~胜利以后进入下一关

至于下一关的代码:

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

总结

好了,到此我们本文介绍的内容就基本结束了,感兴趣的朋友们可以自己动手操作起来,这样会对大家理解学习更有帮助,如果有疑问大家可以留言交流。

Gefällt dir