English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
먼저 효과를 보겠습니다:
이미지를 많이 잘라서, 클릭하여 교환하여 전체 이미지를 완성합니다. 이렇게 하면 레벨도 쉽게 설계할 수 있습니다3 3을 계속 합니다4 4을 계속 합니다5 5을 계속 합니다6 6을 계속 합니다
교체 애니메이션을 추가해 두었는데, 효과는 꽤 좋습니다. 사실 이 게임은 컨트롤을 직접 정의한 것입니다. 이제 직접 정의하는 여정에 시작해 보겠습니다.
게임 설계
먼저 이 게임을 어떻게 설계할지 분석해 보겠습니다:
1를 넣을 수 있는 컨테이너가 필요합니다. 편리함을 위해RelativeLayout과 addRule를 사용할 계획입니다
2각 이미지의 블록을 ImageView로 사용하려고 합니다
3을 잘라서, n
기본적인 설계를 마친 후, 이 게임은 매우 쉬운 것 같습니다~
게임 레이아웃의 구현
먼저, 우리는 한 장의 이미지를 n*n개의 부분으로图片를 잘라서, 지정된 위치에 배치합니다. 우리는 n 이라는 숫자를 설정하고, 레이아웃의 너비나 높이 중 작은 값을 n으로 나누고, 여러 가지 여유를 뺀다면 ImageView의 너비와 높이를 얻을 수 있습니다~~
생성자 /** * Item의 수 n을 설정합니다*n;기본적으로3 */ private int mColumn = 3; /** * 레이아웃의 너비 */ private int mWidth; /** * 레이아웃의 패딩 */ private int mPadding; /** * 전체 Item을 저장합니다. */ 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로 변환합니다;레이아웃의 패딩 값을 얻습니다;전체가 정方形이므로 패딩 네 방향 중 가장 작은 값을 선택합니다;마진은 아이템 간의 가로와 세로 간격으로, 원하시면 사용자 정의 속성으로 추출할 수 있습니다~~
onMeasure /** * 사용자 정의 View의 너비와 높이를 설정하는 데 사용됩니다. * @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: 이미지 슬라이스 클래스 * 데이터: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; } }
/** * 설명: 이미지 베인 * 데이터: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); + "_" + 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을 설정하며, 주의 깊게 주석을 보세요~~
이 점을 주의하세요:
1、우리는 Item에 setOnClickListener을 설정했습니다. 당연히요, 우리의 게임은 Item을 클릭하는 게 아닌가요?
2、우리는 Item에 Tag를 설정했습니다: item.setTag(i + "_" + mItemBitmaps.get(i).index);
tag에 index가 저장되어 있으며, 이는 올바른 위치이며, 또한 i가 현재 아이템의 이미지를 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; } //첫 번째 아이템을 클릭했습니다 if (mFirst == null) { mFirst.setColorFilter(Color.parseColor("#, ")),55FF0000")); } else//두 번째 Item을 클릭합니다; { 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(); //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을 교환 설정하고, 마지막으로 tag를 교환합니다;
이제 우리의 교환 효과는 끝났습니다. 우리의 게임이 끝났습니다~~
효과는 이렇습니다:
이제 우리는 이미 가능합니다. 깨끗한 풍경 사진을 사용하지 않은 이유는, 그 부분이 어디에 해당하는지 알 수 없기 때문입니다. 여자 친구가 직관적으로 보여서 그렇습니다;
물론, 많은 사람들이 토로할 것입니다. 그저 애니메이션 전환을 왜 안 쓰냐고요? 명확히 말하면, 두 개의 이미지가 서로의 위치를 교환하는 것처럼 보이지 않는 거죠, 이게 무엇인가요?
물론이죠, 프로그램에 대해 우리는 추구해야 합니다. 아래에서는 애니메이션 전환 효과를 추가해 보겠습니다~~
无缝的动画切换
먼저 추가하는 방법에 대해 이야기해 보겠습니다. 저는 TranslationAnimation을 사용할 계획입니다. 그리고 두 개의 Item의 top과 left는 레이어에서 가져옵니다;
그러나 이해해야 합니다. 실제로는 Item이 setImage이 변했을 뿐, 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;
이제 우리의 애니메이션切换는 완벽하게 끝났습니다~~
이동할 때, 우리가 성공했는지�断해야 하나~~
게임 승리�断정
이미지의 올바른顺序을 tag에 저장했기 때문에, 이미지를切换할 때 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(); }
결론
좋아요, 이제 우리가 이 글에서 설명한 내용은 기본적으로 끝났습니다. 관심이 있는 분들은 스스로 실습해 보세요. 이렇게 하면 학습을 이해하는 데 더 많은 도움이 됩니다. 의문이 있으면 댓글을 달고 교류하세요.