English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
دعونا نرى النتيجة أولاً:
تقسيم الصور إلى العديد من قطع، عند النقر يمكن تبديلها لتكوين صورة كاملة؛ بهذه الطريقة يمكن تصميم المستوى أيضًا بسهولة، 3 3؛ 4 4؛ 5 5؛ 6 6؛ إلى آخره
أضفنا تحركًا للتبديل، والنتيجة كانت جيدة جدًا، في الواقع اللعبة هي تعريف عنصر مخصص، ونبدأ في رحلة التعريف الآن.
تصميم اللعبة
أولاً نحن نحلل كيفية تصميم هذه اللعبة:
1、نحن بحاجة إلى وعاء يمكنه وضع هذه الصور في قطع، ونحن نعد لاستخدام RelativeLayout مع addRule لتحقيق ذلك
2. كل قطعة من الصور، نحن نستعد لاستخدام ImageView
3. النقر على تبادل، نحن نستعد لاستخدام TranslationAnimation التقليدية
مع التصميم الأولي، يبدو هذا اللعبة سهلة جدًا~
تحقيق تصميم اللعبة
أولاً، نحن نستعد لتحقيق أن تكون قادرًا على قطع صورة واحدة إلى 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 السياق * @param attrs الخصائص * @param defStyle النمط الافتراضي * @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()); // Set the inner padding of Layout, all sides are consistent, set it to the minimum value of the four inner paddings mPadding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); }
In the constructor, we get the margin value set converted to dp; obtain the layout padding value; the whole is a square, so we take the minimum value of the padding in all four directions; as for the margin, as the horizontal and vertical spacing between Items, you can abstract it into a custom attribute if you like~~
onMeasure /** * It is used to set the width and height of the custom 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); // Obtain the edge length of the game layout mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth()); if (!once) { initBitmap(); initItem(); } once = true; setMeasuredDimension(mWidth, mWidth); }
In onMeasure, the main task is to obtain the layout width, then prepare the image, and initialize our Item, setting the width and height of the Item
initBitmap naturally means preparing the image:
/** * initialization of 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 ونرجع قائمة من 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; للدورة في i = 0; i < piece; i++; للدورة في 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 الصورة المحفوظة والINDEX، بالمناسبة، هاتان الفئتان كانتا قد وجدتا بيأس من الإنترنت~~
الصورة هنا جاهزة، الآن ننظر إلى إنشاء Item حيث تم تعيين العرض والارتفاع، أي initItems
/** * تحديث كل item * @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]; // وضع العنصر للدوران (int i = 0; i < mGamePintuItems.الطول; i++) { ImageView النموذج = new ImageView(getContext()); النموذج.تعيين_النقر_على_الذات(this); النموذج.تعيين_الصورة(mItemBitmaps.get(i).الBITMAP); mGamePintuItems[i] = النموذج; النموذج.تعيين_الرقم_المرجعي(i + 1); النموذج.تعيين_العلامة(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 بينهما، ثم تقسيمها على عدد Items في السطر الواحد للحصول على عرض Item~~
ثم، يتم إنشاء Item بشكل متكرر، بناءً على مواقعهم يتم تعيين القاعدة، انظر إلى التعليقات بعناية~~
لنلاحظ نقطتين:
1، لقد قمنا بتعيين setOnClickListener لـ Item، هذا طبيعي، لأن لعبتنا هي نقرة Item~
2، لقد قمنا بتعيين Tag لـ Item: item.setTag(i + "_" + mItemBitmaps.get(i).index);
يحتوي tag على index، وهو الموقع الصحيح؛ بالإضافة إلى i، يمكن أن يساعدنا i في العثور على صورة Item الحالية في mItemBitmaps: (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 إعداد هذا التخطيط~~
الآن، التأثير هو:
تأثير التبديل في اللعبة
تبديل初步
هل تتذكر أننا قمنا بتعيين الاستماع إلى onClick على العنصر؟~~
الآن نحتاج إلى تنفيذ، عند النقر على العنصرين، يمكن تبادل صورهما ~
لذلك، نحتاج إلى متغيرين عضويين لتحديد هذين العنصرين، ثم نتبادلهم
/** * تسجيل ImageView الأولى */ private ImageView mFirst; /** * تسجيل ImageView الثانية */ private ImageView mSecond; /** * أحداث النقر * @param view الرؤية * @author qiu 博客:www.qiuchengjia.cn 时间:2016-09-12 */ @Override public void onClick(View v) { /** * إذا كانت اللمسات مرتين على نفس العنصر */ if (mFirst == v) { mFirst.setColorFilter(null); mFirst = null; return; } //انقر على الأول if (mFirst == null) { mFirst = (ImageView) v; mFirst.setColorFilter(Color.parseColor("#55FF0000")); } else//انقر على الثاني { mSecond = (ImageView) v; exchangeView(); } }
انقر على الأول، باستخدام setColorFilter لضبط تأثير التحديد، ثم انقر على الآخر، نحن مستعدون لاستدعاء exchangeView للتبديل صورة، بالطبع، هذه الطريقة لم ن напишите بعد، نتركها هنا ~
إذا كان هناك ضغطان متكرران على نفسه، نزيل تأثير التحديد، ونعتبر أن لا شيء قد حدث
الآن، دعونا نفهم 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(علامةالثانية); mSecond.setTag(علامةالأولية); mFirst = mSecond = null; }
يجب أن تذكرنا setTag السابق، هل نسيت، أذهب لرؤيته، نعم، كنا نتحدث عن التأكيد~
من خلال getTag، نحصل على индكس في القائمة، ثم نحصل على bitmap لإجراء التبديل الإعداد، وأخيرًا نتبديل tag
إلى هنا، تم إكمال تأثير التبديل، يمكننا أن نقول إن اللعبة قد انتهت~~
النتيجة ستكون مثل هذا:
يمكننا رؤية أننا يمكننا اللعب، ولماذا لا نستخدم صورة مناظر طبيعية نظيفة، لأننا لا نستطيع رؤية ما يقع بجانب ما، أو أن الفتاة تكون أكثر وضوحًا~
سوف ينتقد الجميع، ياله، تبديل التحريك، كان يجب أن يكون تبادل موقعهما بشكل رائع، هذا ما هو عليه
بالطبع، يجب أن نكون لدينا طموح لبرنامجنا، دعونا نضيف تأثير تبديل التحريك~~
تبديل التحريك بدون فجوة
دعونا نتحدث عن كيفية إضافة، وأنا أستعد لاستخدام TranslationAnimation، ثم الحصول على top وleft لـ Item من القالب
لكن، يجب أن نفهم، في الواقع، Item يتغير فقط في تعيين الصورة، موقع Item لم يتغير
حاليًا نحتاج إلى تأثير تحريك، مثل تحريك A إلى B، لا مشكلة، بعد الانتهاء من التحريك، يجب أن يرجع Item، ولكن الصورة لم تتغير، لذا نحتاج إلى تعيين setImage يدويًا
بهذا أصبح لدينا تأثير تبديل التحريك، ولكن في النهاية سيتعين علينا أن نرى شعور الوميض، لأننا نتبديل الصور
لإزالة هذا الظاهرة، والقيام بتنفيذ التبديل بشكل مثالي، هنا نستخدم طبقة تحريك مخصصة لتحقيق تأثير التحريك، تشبه طبقات Photoshop، دعونا نرى كيف نفعل ذلك;
/** * علامة استفادة التحريك */ 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(غيرمرئي); mSecond.setVisibility(غيرمرئي); } @Override public void onAnimationRepeat(Animation animation){ } @Override public void onAnimationEnd(Animation animation){ String firstTag = (String) mFirst.getTag(); String secondTag = (String) mSecond.getTag(); String[] الأولىالمستويات = firstTag.split("_"); String[] الثانيةالمستويات = secondTag.split("_"); mFirst.setImageBitmap(mItemBitmaps.get(عددكامل(Integer .parseInt(الثانيةالمستويات[0])).bitmap); mSecond.setImageBitmap(mItemBitmaps.get(عددكامل(Integer .parseInt(الأولىالمستويات[0])).bitmap); mFirst.setTag(علامةالثانية); mSecond.setTag(علامةالأولية); mFirst.setVisibility(مرئي); 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();的判断;好在我们把图片的正确的顺序存在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]); }
بسيطة جدًا، استعرض جميع العناصر، حسب Tag للحصول على المعدل الحقيقي والترتيب الطبيعي، إذا كانوا متطابقين فالانتصار~~ بعد الانتصار، انتقل إلى المستوى التالي
بالنسبة للغة البرمجة في المستوى التالي:
public void nextLevel(){ this.removeAllViews(); mAnimLayout = null; mColumn++; initBitmap(); initItem(); }
الخلاصة
حسنًا، نهاية محتوى هذا المقال، يمكن للذين يهمهم الأمر تجربة التنفيذ بأنفسهم، هذا سيساعدهم على فهم وتعلم المزيد، إذا كان لديك أي أسئلة، يمكنك ترك تعليق للتفاعل.