English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Давайте посмотрим на результат:
Изображения разделены на много частей, нажав на них, можно сложить целое; таким образом, дизайн миссий также легко создать, 3 3; 4 4; 5 5; 6 6; и так далее
Добавили анимацию переключения, эффект неплохой, 其实 игра представляет собой пользовательский контроллер, а теперь начнем наш путь к созданию.
Дизайн игры
Сначала давайте проанализируем, как спроектировать эту игру:
1、Нам нужен контейнер, чтобы поместить эти блоки изображений, для удобства мы решили использовать RelativeLayout в сочетании с addRule
2. Each picture block, 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 game layout
Firstly, we prepare to implement the ability to cut a picture into n*n pieces and place them at specified positions; we only need to set the number n, and then based on the smaller width or height of the layout, divide by n, and subtract some margin to get the width and height of our ImageView~~
Constructor method /** * Sets the number of Items n*n; default is 3 */ private int mColumn = 3; /** * Layout width */ private int mWidth; /** * Layout padding */ private int mPadding; /** * Stores all Items */ private ImageView[] mGamePintuItems; /** * Item width */ private int mItemWidth; /** * Item horizontal and vertical margins */ private int mMargin = 3; /** * Puzzle picture */ private Bitmap mBitmap; /** * Stores the picture 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 to initialize * @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); //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()); }
в методе конструктора мы преобразуем установленные значения отступа в dp; получаем значения отступа макета; это квадрат, поэтому мы берем минимальное значение отступа в четырех направлениях; что касается отступа, он служит интервалом между Item, и если вам нравится, вы можете извлечь его как пользовательский атрибут~~
onMeasure /** * используется для установки размеров пользовательского View, * @param widthMeasureSpec спецификация измерения ширины * @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 в основном получают ширину макета, затем готовят изображение и initializes наш 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){ // Мы используем случайный порядок для сравнения return Math.random() > 0.5 ? 1 : -1; } }); }
Если мы не установили mBitmap, мы подготавливаем запасное изображение, затем вызываем ImageSplitter.split, чтобы разрезать изображение на n * n и вернуть List<ImagePiece> . После разреза нам нужно перетасовать порядок, поэтому мы вызываем метод sort, а для сравнения используем случайный порядок, таким образом мы завершаем нашу операцию shuffle, ах, да!~
/** * Описание: Класс для разрезания изображений * Дата: 2016/9/11-19:53 * Блог: www.qiuchengjia.cn * Автор: 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; } }
/** * Описание: bean изображения * Дата: 2016/9/11-19:54 * Блог: www.qiuchengjia.cn * Автор: 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, установка Правил в зависимости от их положения, обратите внимание на комментарии~~
Обратите внимание на два момента:
1. Мы установили setOnClickListener для Item, это очевидно, так как наша игра заключается в нажатии на Item~
2. Мы также установили Tag для Item: item.setTag(i + "_" + mItemBitmaps.get(i).index);
В tag хранится index, то есть правильное положение; также 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 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 для замены изображений, конечно, этот метод мы еще не написали, оставим его на потом~
Если дважды нажать на один и тот же, удалить эффект выбора, мы просто будем считать, что ничего не произошло
Давайте реализуем exchangeView:
/** * Обмен изображениями двух элементов * @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;
До этого момента наш эффект обмена завершен, наша игра может быть завершена~~
Эффект будет таким:
Как видите, мы уже можем поиграть, почему не использовать свежие пейзажи, потому что,实在是 трудно понять, что к чему, или девушка более直观~
Конечно, многие из вас будут жаловаться, черт, анимационный переход?明明是两个元素交换位置飞过去么, что это за херня
Да, к программе мы должны подходить с определенными требованиями, давайте добавим эффект анимационного перехода~~
Гладкий переход анимации
Давайте сначала поговорим о том, как добавить, я планирую использовать TranslationAnimation, а также top и left двух элементов также можно получить из контейнера;
Но, чтобы понять, на самом деле, элемент только изменяет setImage, его положение не изменилось;
Теперь нам нужно анимационное движение, например, A перемещается в B, это нормально, после завершения перемещения, элемент должен вернуться, но изображение не изменилось, нам все равно нужно вручную установить setImage
Таким образом, возникает phenomenon, анимационный эффект есть, но в конце концов все равно происходит мерцание, вызванное切换 изображений;
Чтобы избежать вышеупомянутого явления и完美地实现切换效果, мы вводим анимационный слой,专门 для анимационных эффектов, несколько напоминающий слои в Photoshop, как мы это будем делать, посмотрим ниже;
/** * Флаг выполнения анимации */ private boolean isAniming; /** * Слой анимации */ private RelativeLayout mAnimLayout; /** * Обмен изображениями двух элементов * @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[] 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(Видимое); 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, как и в предыдущем случае
Теперь наш результат:
Теперь результат нас устраивает ~~~ Чтобы предотвратить постоянные нажатия пользователя, добавьте строку в 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, получим настоящий индекс и сравним его с ожидаемым порядком. Если они совпадут, то победа~~ После победы перейдем к следующему уровню
Что касается кода для следующего уровня:
public void nextLevel(){ this.removeAllViews(); mAnimLayout = null; mColumn++; initBitmap(); initItem(); }
Итог
Итак, до этого момента мы в основном рассказали о том, что мы обсуждали. Те, кто интересуется, могут попробовать сами. Это поможет вам лучше понять и изучить. Если у вас есть вопросы, вы можете оставить комментарии для обсуждения.