Androidは美女のパズルゲームの詳細を実現します。


まず効果を見てみます。

写真はたくさんカットして、交換をクリックして完全な一枚になります。このようにステージも設計しやすいです。4.5.6.まっすぐ行く
アニメーションの切り替えを追加しましたが、効果はいいです。実はゲームはコントロールをカスタマイズしました。
ゲームのデザイン
まず、このゲームのデザインを分析します。
1、私達はこれらの写真のブロックを入れる容器が必要です。便利さのために、RelativeLayoutを使ってaddRuleと協力して実現するつもりです。
2、各写真のブロックはImageViewを使用する予定です。
3、クリックして交換して、私達は伝統的なTranslationAnimationを使って実現するつもりです。
初歩的なデザインがあって、このゲームはso easy~
ゲームレイアウトの実現
まず、一枚の写真をn*nに切って、指定の位置に置くことができます。nという数字だけを設定して、レイアウトの幅や高さの中の小さい値によって、nを除いて、いくつかの余白を引いたら、ImageViewの幅と高さが得られます。

    
/** 
  *   Item   n*n;   3 
  */ 
 private int mColumn = 3; 
 /** 
  *       
  */ 
 private int mWidth; 
 /** 
  *    padding 
  */ 
 private int mPadding; 
 /** 
  *      Item 
  */ 
 private ImageView[] mGamePintuItems; 
 /** 
  * Item    
  */ 
 private int mItemWidth; 
 
 /** 
  * Item         
  */ 
 private int mMargin = 3; 
  
 /** 
  *       
  */ 
 private Bitmap mBitmap; 
 /** 
  *          bean 
  */ 
 private List<ImagePiece> mItemBitmaps; 
  
 private boolean once; 
  
 public GamePintuLayout(Context context) { 
  this(context, null); 
 } 
 
 public GamePintuLayout(Context context, AttributeSet attrs) { 
  this(context, attrs, 0); 
 } 
 
 /**
  *     ,     
  * @param context the context
  * @param attrs the attrs
  * @param defStyle the def style
  * @author qiu   :www.qiuchengjia.cn   :2016-09-12
  */
 public GamePintuLayout(Context context, AttributeSet attrs, int defStyle) { 
  super(context, attrs, defStyle); 
 
 //    margin    dp
  mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 
    mMargin, getResources().getDisplayMetrics()); 
  //   Layout    ,    ,             
  mPadding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), 
    getPaddingBottom()); 
 }
構造方法では、設定されたmargin値をdpに変換します。レイアウトのpadding値を取得する。全体は正方形ですので、paddingの4つの方向の中の最小値を取ります。marginについては、Item間の横方向と縦方向の間隔として、気に入ったらカスタム属性として抽出できます。

onMeasure
/**
  *           View   ,
  * @param widthMeasureSpec the width measure spec
  * @param heightMeasureSpec the height measure spec
  * @author qiu   :www.qiuchengjia.cn   :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   :www.qiuchengjia.cn   :2016-09-12
 */
private void initBitmap() { 
  if (mBitmap == null) 
   mBitmap = BitmapFactory.decodeResource(getResources(), 
     R.drawable.aa); 
 
  mItemBitmaps = ImageSplitter.split(mBitmap, mColumn); 
 
 //       
  Collections.sort(mItemBitmaps, new Comparator<ImagePiece>(){ 
   @Override 
   public int compare(ImagePiece lhs, ImagePiece rhs){ 
   //    random      
    return Math.random() > 0.5 ? 1 : -1; 
   } 
  }); 
 }
ここでmBitmapが設定されていない場合は、予備画像を準備し、ImageSplitter.splitを呼び出して、画像をnに切ってList<ImagePiece>に戻ります。切った後、順番を狂わす必要があります。だから、私達はsortの方法を呼び出しました。コンパレータに関しては、私達はrandomを使ってランダムに大きさを比較します。そうすると、私達は私達の順番の乱れた操作を完成しました。いいですね。

/**
 * Description:      
 * Data:2016/9/11-19:53
 * Blog:www.qiuchengjia.cn
 * Author: 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; 
 } 
}

/**
 * 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   :www.qiuchengjia.cn   :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); 
   //       ,       
   if ((i + 1) % mColumn != 0) { 
    lp.rightMargin = mMargin; 
   } 
   //         
   if (i % mColumn != 0) { 
    lp.addRule(RelativeLayout.RIGHT_OF,// 
      mGamePintuItems[i - 1].getId()); 
   } 
   //        ,//      ,      
   if ((i + 1) > mColumn) { 
    lp.topMargin = mMargin; 
    lp.addRule(RelativeLayout.BELOW,// 
      mGamePintuItems[i - mColumn].getId()); 
   } 
   addView(item, lp); 
  } 
 }
私達のItem幅の計算が見られます。childWidth = (mWidth - mPadding 2 - mMargin (mColumn - 1) ) / mColumn;容器の幅は自分の内側余白を除いてItem間の間隔を除いてItem行の個数で割ってItemの幅を得ました。
次に、Itemを巡回生成し、彼らの位置によってRuleを設置し、自分で注意深く注釈を見ます。
注意点:
1、私達はItemのためにsetOnClickListenerを設置しました。これはもちろん、私達のゲームはItemを注文するのですか?
2、そして私達はItemのためにTag:item.setTag(i + "_" + mItemBitmaps.get(i).index);を設置しました。
(中にindexが保存されています。つまり正しい位置です。また、iは、mItemBitmapsにおいて、現在のItemの画像を見つけるのを助けることができる:(mItemBitmaps.get(i).bitmap)これで、ゲームのレイアウトのコードが終わります。
そしてレイアウトファイルの中で声明を出します。

<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>
Activityにこのレイアウトを設定してください。
現在の効果は:

ゲームの切り替え効果
初歩的な切り替えItemonClickの傍受を追加した覚えがありますか?
今私達は実現したいです。2つのItemをクリックして、彼らの写真が交換できます。
では、私たちはこの2つのItemを記憶してから交換するために2つのメンバー変数が必要です。

/**
 *         ImageView
 */
private ImageView mFirst; 
/**
 *         ImageView
 */
private ImageView mSecond; 
/**
 *     
 * @param view the view
 * @author qiu   :www.qiuchengjia.cn   :2016-09-12
 */ 
@Override 
public void onClick(View v) { 
 /** 
  *            
  */ 
 if (mFirst == v) { 
  mFirst.setColorFilter(null); 
  mFirst = null; 
  return; 
 } 
 //     Item 
 if (mFirst == null) { 
  mFirst = (ImageView) v; 
  mFirst.setColorFilter(Color.parseColor("#55FF0000")); 
 } else//     Item 
 { 
  mSecond = (ImageView) v; 
  exchangeView(); 
 } 
 
}
最初をクリックして、setColorFilterで選択効果を設定して、もう一つをクリックします。exchangeViewを呼び出して、写真を交換するつもりです。もちろんこの方法はまだ書いていません。先に置いておきます。
同じボタンを2回クリックしたら、選択効果を削除します。何も発生していません。
次に、exchangeViewを実現する。

/**
 *     Item   
 * @author qiu   :www.qiuchengjia.cn   :2016-09-12
 */
 private void exchangeView() { 
    
  mFirst.setColorFilter(null); 
  String firstTag = (String) mFirst.getTag(); 
  String secondTag = (String) mSecond.getTag(); 
   
  //   list      
  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; 
 
 }
私達の前のsetTagを覚えているはずです。忘れました。帰ってみます。注意してくださいと言っています。getTagを通じて、Listにインデックスを取得し、bitmapを得て交換設定を行い、最後に交換する。
これで私達の交換効果が終わりました。私達のゲームは終わります。
効果はこうです。

私たちはもう遊んでもいいです。なぜさわやかな風景図を使わないですか?
みんなはきっと突っ込むことができて、私は拭いて、アニメーションは切り替えて、2つが飛んで行って位置を交換するのではありませんか?
また、プログラムに対する追求が必要です。アニメーションの切り替え効果を追加します。
シームレスなアニメーション切り替え
私達は先にどのように追加するかについて話をします。TranslationAnimationを使って、Itemのtopを二つ使います。leftも容器に入れます。
しかし、私たちは実際にItemsetImageだけで変化しました。Itemの位置は変わっていません。
私達は今アニメーションの移動効果が必要です。例えば、AはBに移動します。大丈夫です。移動が終わったら、Itemは戻らなければなりません。でも、写真は変わっていません。マニュアルsetImageが必要です。
このように一つの現象をもたらしました。アニメーションの切り替え効果はありましたが、最後にはまだちらっとしています。
このような現象を避けるために、完璧に切り替え効果が得られます。ここではアニメーションレイヤーを導入して、アニメーション効果を専門に作って、psのレイヤーに似ています。どうすればいいですか?

/** 
 *          
 */ 
private boolean isAniming; 
/** 
 *     
 */ 
private RelativeLayout mAnimLayout; 
  
/**
 *     Item  
 * @author qiu   :www.qiuchengjia.cn   :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); 
  //        
  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; 
   } 
  }); 
 
 } 
 
 /** 
  *       
  */ 
 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]); 
 
 }
交換を開始する時、私達はアニメーション層を作成して、この階に二つの同じItemを追加して、もとのItemを隠して、思いきりアニメーションの切り替えを行います。setFillAfterはtrueです。
アニメーションが終わりました。私たちはそっとItemの写真を交換しました。直接に表示します。これで完璧な切り替えができます。
大体のプロセス:
    1、A、Bは隠します
    2、A複製アニメーションはBの位置に移動する。BコピーはAの位置に移動します。
    3、Aは写真をBに設定し、Bのコピーを削除し、Aは表示し、これで完璧にマッチングし、ユーザーはBが過去に移動したと感じています。
    4、B同上
現在の効果:

今の効果は満足しました。ユーザーの狂点を防止するために、onClickに一言を追加しました。

@Override 
 public void onClick(View v) 
 { 
  //         ,    
  if (isAniming) 
   return;
これで私達のアニメの切り替えは完璧に終わりました。
切り替え時、成功したかどうかを判断するべきですか?
ゲーム勝利の判断
私たちは切り替えが完了し、checkSuccess()を行います。判断する幸いにも私たちは写真の正確な順序を(中に)存在しています。

/**
 *           
 * @author qiu   :www.qiuchengjia.cn   :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(); 
 }
締め括りをつける
はい、ここで紹介した内容は基本的に終わりました。興味のある友達は自分で操作してもいいです。そうすると、皆さんの理解や学習にもっと役立ちます。もし疑問があれば、メッセージを書いて交流してもいいです。