English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
まず、効果を見てみましょう:
画像をたくさん切分し、クリックで交換して一つの完全な画像に組み立てます;このステージのデザインも簡単です3 3;4 4;5 5;6 6;次々に
切り替えアニメーションを追加し、効果はそれほど悪くありません、実際にはゲームはカスタムコントロールを1つ作成しています、以下からカスタムの旅を始めます。
ゲームのデザイン
まず、このゲームのデザイン方法を分析します:
1、これらの画像のブロックを入れることができるコンテナが必要です、便利にするためにRelativeLayoutとaddRuleを使用する準備をします
2、各画像のブロック、私たちはImageViewを使用する準備をします
3、クリックで交換、私たちは伝統的なTranslationAnimationを使用して実現する準備をします
基本的なデザインができたら、このゲームはとても簡単に感じられます~
ゲームレイアウトの実現
まず、私たちは1枚の画像をn*n分割した画像を、指定された位置に配置します;私たちはnという数字を設定し、レイアウトの幅または高さの小さい方でnで割り、いくつかのエッジマージンを引くことでImageViewの幅と高さを得ることができます~~
コンストラクタ /** * アイテムの数nを設定する*n;デフォルトは3 */ private int mColumn = 3; /** * レイアウトの幅 */ private int mWidth; /** * レイアウトのpadding */ private int mPadding; /** * すべてのアイテムを保存する */ private ImageView[] mGamePintuItems; /** * アイテムの幅 */ private int mItemWidth; /** * アイテムの横と縦のマージン */ 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); //設定されたマージン値をdpに変換します mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mMargin, getResources().getDisplayMetrics()); // Layoutの内側マージンを設定します。四辺が一貫して、四内側マージンの最小値に設定します mPadding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); }
コンストラクタ内で、設定されたマージン値をdpに変換します;レイアウトのパディング値を取得します;全体が正方形なので、パディングの4方向の最小値を取得します;マージンはアイテム間の横と縦の間隔として使用されます。好きな場合は、カスタム属性として抽出できます~~
onMeasure /** * カスタムビューの幅と高さを設定するために使用されます。 * @param widthMeasureSpec the width measure spec * @param heightMeasureSpec 高さの測定スペック * @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に切り分けます * 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に保存された画像とインデックス、話をするとこの2つのクラスは私が無意識にインターネットで見つけたんです~~
画像はここで準備ができました。今はアイテムの生成が幅と高さが設定されていることを確認しています、initItems
/** * 各アイテムを初期化 * @author qiu ブログ:www.qiuchengjia.cn 時間:2016-09-12 */ private void initItem() { // アイテムの幅を取得 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); + "_" + mItemBitmaps.get(i).index); new LayoutParams(mItemWidth, mItemWidth); // 水平余白を設定します、最後の一列ではありません if ((i + 1) % mColumn != 0) { lp.rightMargin = mMargin; } // 最初の一列でない場合、 lp.addRule(RelativeLayout.RIGHT_OF,// mGamePintuItems[i - 1].getId()); } // 最初の一列でない場合、//垂直余白を設定します、最後の一列ではありません if ((i + 1) > mColumn) { 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を設定します。コメントを慎重に読んでください~~
注意する点が2つあります:
1、ItemにsetOnClickListenerを設定しました。もちろんです。なぜなら、私たちのゲームはItemをクリックするものだからです~
2、そして、ItemにTagを設定しました:item.setTag(i + "_" + mItemBitmaps.get(i).index);
タグにはindexが保存されており、それは正しい位置であり、iもmItemBitmapsから現在のアイテムの画像を見つけるのに役立ちます:(mItemBitmaps.get(i).bitmap)
ここで、私たちのゲームのレイアウトのコードは終わりました~~~
その後、レイアウトファイル内で以下のように宣言します:
<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>
Activity内でこのレイアウトを設定するように忘れずに~~
現在の効果は:
ゲームの切り替え効果
初期の切り替え
アイテムにonClickリスナーを追加したことを忘れずに~~
今、2つのアイテムをクリックすると、彼らの画像が交換されることを実現する必要があります
したがって、この2つのアイテムを保存するために2つのメンバ変数が必要であり、それから交換する必要があります
/** * 1回目のクリックのImageViewを記録する */ private ImageView mFirst; /** * 2回目のクリックのImageViewを記録する */ private ImageView mSecond; /** * クリックイベント * @param view the view * @author qiu ブログ:www.qiuchengjia.cn 時間:2016-09-12 */ @Override public void onClick(View v) { /** * もし、2回目のクリックが同じアイテムであれば */ if (mFirst == v) { mFirst.setColorFilter(null); mFirst = null; return; } //クリックされた最初のアイテム if (mFirst == null) { mFirst = (ImageView) v; mFirst.setColorFilter(Color.parseColor("#"))55FF0000")); } else//2番目の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(); //リスト内のインデックス位置を取得します 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を使用して、リスト内のインデックスを取得し、bitmapを交換設定し、最後にtagを交換します;
ここで交換効果の記述が完了しました。ゲームが完了した~~
効果はこんな感じです:
すでに遊べるようになりました。なぜクリーンな風景画像を使わないのかというと、実際にはどの部分がどの部分に対してかは見えにくく、女性の直感がより強いからです~
もちろんです。アニメーション切り替えがあれば、明らかに2つのアイテムが位置を交換するように見えますが、これは何ですか、本当に
もちろんです。プログラムに対して私たちは追求すべきです。次に、アニメーション切り替え効果を追加しましょう~~
スムーズなアニメーション切り替え
まずは追加方法について話しましょう。私はTranslationAnimationを使用する予定で、そして2つのアイテムのtop、leftもフレームワークから取得します;
しかし、理解しておくべきは、実際にはアイテムがsetImageが変更されただけで、アイテムの位置は変わっていないことです;
私たちは現在、アニメーション移動効果が必要です。例えば、AがBに移動する場合は問題ありませんが、移動が完了した後、アイテムが元の位置に戻る必要がありますが、画像に何も変わっていません。私たちはまだ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を2つ追加し、元の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();の判断を行います;幸いにも、画像の正しい順序をtagに保存しています~~
/** * ゲームが成功しているかどうかを判断するために * @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(); }
まとめ
ここまで、私たちが紹介した内容は基本的に終わりました。興味を持たれた方々は自分で手を動かしてみてください。これにより、皆さんへの理解と学習がより助けになります。何か疑問があれば、コメントを残してください。