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

Dettagli dell'implementazione del gioco di puzzle di belle donne in Android

先来看看效果:

图片切分很多份,点击交换拼成一张完整的;这样关卡也很容易设计,3 3;4 4;5 5;6 6;一直下去

加了个切换动画,效果还是不错的,其实游戏就是自定义了一个控件,下面我们开始自定义之旅。

游戏的设计

首先我们分析下如何设计这款游戏:

1、我们需要一个容器,可以放这些图片的块块,为了方便,我们准备使用RelativeLayout配合addRule实现

2. Each piece of the image, we are ready to use ImageView

3. Click to swap, we are ready to use the traditional TranslationAnimation to implement

With the preliminary design, it feels like this game is so easy~

Implementation of the game layout

Firstly, we prepare to implement the ability to cut a picture into n*n pieces and place them at specified positions; we just need to set the number n, then according to the smaller width or height of the layout, divide it by n, and subtract some margin to get the width and height of our ImageView~~

Constructor method
/** 
  * Set the number of Items n*n; default is 3 
  */ 
 private int mColumn = 3; 
 /** 
  * The width of the layout 
  */ 
 private int mWidth; 
 /** 
  * The padding of the layout 
  */ 
 private int mPadding; 
 /** 
  * Store all the Items 
  */ 
 private ImageView[] mGamePintuItems; 
 /** 
  * The width of the Item 
  */ 
 private int mItemWidth; 
 /** 
  * The margin horizontally and vertically for the Item 
  */ 
 private int mMargin = 3; 
 /** 
  * The image for the puzzle 
  */ 
 private Bitmap mBitmap; 
 /** 
  * Store the image bean after cutting 
  */ 
 private List<ImagePiece> mItemBitmaps; 
 private boolean once; 
 public GamePintuLayout(Context context) { 
  this(context, null); 
 } 
 public GamePintuLayout(Context context, AttributeSet attrs) { 
  this(context, attrs, 0); 
 } 
 /**
  * Constructor, used for initialization
  * @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); 
 //Convert the set margin value to dp
  mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 
    mMargin, getResources().getDisplayMetrics()); 
  // 设置Layout的内边距,四边一致,设置为四内边距中的最小值 
  mPadding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), 
    getPaddingBottom()); 
 }

在构造方法中,我们将设置的margin值转换为dp;获取布局的padding值;整体是一个正方形,因此我们取padding四个方向中的最小值;至于margin,作为Item之间的横向和纵向间距,如果您喜欢,可以将其抽取为自定义属性~~

onMeasure
/**
  * 用来设置设置自定义的View的宽高,
  * @param widthMeasureSpec the width measure spec
  * @param heightMeasureSpec the height measure spec
  * @author qiu Blog: www.qiuchengjia.cn Date: 2016-09-12
  */
@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  // 获得游戏布局的边长 
  mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth()); 
  if (!once) { 
   initBitmap(); 
   initItem(); 
  } 
  once = true; 
  setMeasuredDimension(mWidth, mWidth); 
 }

onMeasure里面主要就是获得到布局的宽度,然后进行图片的准备,以及初始化我们的Item,为Item设置宽度和高度

initBitmap自然就是准备图片了:

/**
 * 初始化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); 
 // Ordina le immagini
  Collections.sort(mItemBitmaps, new Comparator<ImagePiece>(){ 
   @Override 
   public int compare(ImagePiece lhs, ImagePiece rhs){ 
   // Usiamo random per confrontare le dimensioni
    return Math.random() > 0.5 ? 1 : -1; 
   } 
  }); 
 }

Se non è stato impostato mBitmap, prepariamo un'immagine di riserva e chiamiamo ImageSplitter.split per tagliare l'immagine in n * n e restituire una List<ImagePiece> . Dopo aver tagliato, dobbiamo disordinare l'ordine, quindi chiamiamo il metodo sort, per quanto riguarda il comparatore, usiamo random per confrontare le dimensioni, così completiamo la nostra operazione di disordine, che ne dice? Zitto zitto~~

/**
 * Descrizione: Classe per la segmentazione delle immagini
 * Data: 2016/9/11-19:53
 * Blog:www.qiuchengjia.cn
 * Author: qiu
 */
public class ImageSplitter { 
 /** 
  * Taglia l'immagine in , 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", "larghezza bitmap = " + width + " , altezza = " + 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; 
 } 
}
/**
 * Description: 图片bean
 * Data:2016/9/11-19:54
 * Blog:www.qiuchengjia.cn
 * Author: qiu
 */
public class ImagePiece 
{ 
 public int index = 0; 
 public Bitmap bitmap = null; 
}

总说的就是一个根据宽度高度,和n,来切图保存的过程~~

ImagePiece保存的图片以及索引,话说这两个类还是我无意中在网上发现的~~

图片到此就准备好了,现在看Item的生成已经设置宽高,即initItems

/**
 * 初始化每一个item
 * @author qiu Blog: www.qiuchengjia.cn Date: 2016-09-12
 */
private void initItem() { 
  // 获得Item的宽度 
  int childWidth = (mWidth - mPadding * 2 - mMargin * 
  (mColumn - 1)) / mColumn; 
  mItemWidth = childWidth; 
  mGamePintuItems = new ImageView[mColumn * mColumn]; 
  // 放置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); 
   // Impostare il margine orizzontale, non l'ultima colonna 
   if ((i + 1) % mColumn != 0) { 
    lp.rightMargin = mMargin; 
   } 
   // Se non è la prima colonna 
   if (i % mColumn != 0) { 
    lp.addRule(RelativeLayout.RIGHT_OF,// 
      mGamePintuItems[i - 1].getId()); 
   } 
   // Se non è la prima riga, // impostare il margine verticale, non l'ultima riga 
   if ((i + 1) > mColumn) { 
    lp.topMargin = mMargin; 
    lp.addRule(RelativeLayout.BELOW,// 
      mGamePintuItems[i - mColumn].getId()); 
   } 
   addView(item, lp); 
  } 
 }

Si può vedere come calcoliamo la larghezza degli Item: childWidth = (mWidth - mPadding 2 - mMargin (mColumn - 1) ) / mColumn; la larghezza del contenitore, sottratto il margine interno, sottratto lo spazio tra gli Item, poi diviso per il numero di Item per riga, otteniamo la larghezza dell'Item~~

Poi, è il momento di generare gli Item in modo iterativo, impostando le Regole in base alle loro posizioni, guardate attentamente i commenti~~

Attenzione a due punti:

1、abbiamo impostato un setOnClickListener per l'Item, ovviamente, perché il nostro gioco è basato su clic sugli Item~

2、abbiamo impostato un Tag per l'Item: item.setTag(i + "_" + mItemBitmaps.get(i).index);

L'etichetta contiene l'indice, ossia la posizione corretta; ci sono anche i, che ci aiuta a trovare l'immagine corrente dell'oggetto Item in mItemBitmaps: (mItemBitmaps.get(i).bitmap)

Fino a questo punto, il codice della nostra layout di gioco è finito~~~

Poi, dichiamo nel file di layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 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"
  android:padding="5dp" >
 </game.qiu.com.beautygame.GamePintuLayout>
</RelativeLayout>

Ricorda di impostare questa layout nell'Activity~~

L'effetto attuale è:

L'effetto di cambio del gioco

Il cambio iniziale

Ricordi che abbiamo aggiunto l'ascoltatore onClick agli Item?~~

Ora dobbiamo implementare, cliccando due Item, le loro immagini possono scambiarsi~

Quindi, abbiamo bisogno di due variabili membro per memorizzare questi due Item e poi scambiarli

/**
 * Registriamo l'ImageView cliccato per la prima volta
 */
private ImageView mFirst; 
/**
 * Registriamo l'ImageView cliccato per la seconda volta
 */
private ImageView mSecond; 
/**
 * Evento di clic
 * @param view la vista
 * @author qiu Blog: www.qiuchengjia.cn Date: 2016-09-12
 */ 
@Override 
public void onClick(View v) { 
 /** 
  * Se due clic sono su uno stesso oggetto 
  */ 
 if (mFirst == v) { 
  mFirst.setColorFilter(null); 
  mFirst = null; 
  return; 
 } 
 //Clicca il primo Item 
 if (mFirst == null) { 
  mFirst = (ImageView) v; 
  mFirst.setColorFilter(Color.parseColor("#55FF0000")); 
 } else//Clicca il secondo Item 
 { 
  mSecond = (ImageView) v; 
  exchangeView(); 
 } 
}

Clicca il primo, imposta l'effetto selezionato tramite setColorFilter, clicca un altro, allora stiamo pronti a chiamare exchangeView per scambiare le immagini, naturalmente questo metodo non è ancora scritto, mettiamolo da parte~

Se si fa clic due volte sulla stessa, rimuovere l'effetto di selezione, consideriamo che non è successo nulla

Quindi, implementiamo exchangeView:

/**
 * Scambia le immagini di due 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(); 
  //Ottieni la posizione dell'indice nella 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; 
 }

Dovrebbe ricordare il setTag che abbiamo usato prima, dimenticato, tornare a guardarlo, abbiamo anche detto di prestare attenzione a questo~

Attraverso getTag, ottenere l'indice nella lista, quindi ottenere il bitmap per lo scambio delle impostazioni, infine scambiare il tag;

Fino a questo punto, abbiamo completato l'effetto di scambio, il nostro gioco può finire~~

L'effetto è questo:

Si può vedere che possiamo giocare, riguardo a perché non usiamo immagini di paesaggio fresche, è perché, non si può vedere che cosa corrisponde a cosa, o la ragazza è più intuitiva~

Sicuramente qualcuno si farà un'idea, cazzo, la commutazione dell'animazione? È chiaro che sono due che scambiano posizioni, cos'è questo?

Anche così, dobbiamo avere ambizioni per il nostro programma, ora aggiungiamo l'effetto di commutazione dell'animazione~~

Commutazione dell'animazione senza interruzioni

Parliamo prima di come aggiungerlo, ho intenzione di usare TranslationAnimation, quindi il top e il left di due Item possono essere ottenuti anche dal contenitore;

Ma, è necessario capire che di fatto, l'Item cambia solo setImage, la posizione dell'Item non cambia;

Ora abbiamo bisogno di un effetto di movimento dell'animazione, ad esempio A si muove a B, non c'è problema, una volta completato il movimento, l'Item deve tornare, ma l'immagine non è cambiata, dobbiamo ancora impostare manualmente setImage

Questo ha causato un fenomeno, l'effetto di commutazione dell'animazione è stato ottenuto, ma alla fine c'è ancora uno scintillio, è causato dalla commutazione delle immagini;

Per evitare questo fenomeno, per ottenere un effetto di commutazione perfetto, introduciamo uno strato di animazione speciale per fare l'effetto animazione, un po' come lo strato di Photoshop, vediamo come facciamo di seguito;

/** 
 * Flag di esecuzione dell'animazione 
 */ 
private boolean isAniming; 
/** 
 * Strato animazione 
 */ 
private RelativeLayout mAnimLayout; 
/**
 * Scambia le immagini di due Item
 * @author qiu Blog: www.qiuchengjia.cn Date: 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); 
  // Aggiungi ascoltatore dell'animazione 
  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 the exchange begins, we create an animation layer, then add two identical Items to this layer, hide the original Item, and then perform the animation switch freely, setFillAfter to true~

After the animation is completed, we have quietly exchanged the images of the Item and directly displayed them. This is a perfect switch:

The general process:

    1, Hide A, B

    2, Animate the copy of A to the position of B; move the copy of B to the position of A

    3, A set the image to B, remove the copy of B, show A, so that it perfectly fits, and the user feels that B has moved over

    4, B as above

Now our effect:

Now that the effect is satisfactory, let's add a line in onClick to prevent the user from clicking too quickly:

@Override 
 public void onClick(View v) 
 { 
  // If an animation is being executed, block it 
  if (isAniming) 
   return;

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

When switching, should we judge whether it has been successful~~

Judgment of game victory

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

/**
 * Used to determine if 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 , Livello Avanzato !", 
     Toast.LENGTH_LONG).show(); 
   // nextLevel(); 
  } 
 } 
 /** 
  * Ottiene l'indice reale dell'immagine 
  * @param tag 
  * @return 
  */ 
 private int getIndexByTag(String tag){ 
  String[] split = tag.split("_"); 
  return Integer.parseInt(split[1]); 
 }

è molto semplice, attraversare tutti gli Item, ottenere l'indice reale e l'ordine naturale confrontandoli, se sono completamente identici allora vince~~Vinto poi entra nel livello successivo

Riguardo al codice del livello successivo:

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

Conclusione

Bene, con questo l'articolo che presentiamo è praticamente finito. Chi è interessato può mettersi all'opera, così sarà più utile per comprendere e imparare. Chi ha domande può lasciare un commento per discuterle.

Ti potrebbe interessare