美文网首页
仿九宫图的Android小游戏

仿九宫图的Android小游戏

作者: Routee | 来源:发表于2018-04-28 17:33 被阅读0次

    本Demo主要目的为学习及研究自定义ViewGroup,通过实现一种拼图游戏而熟悉ViewGroup的onMeasure,onLayout及onTouchEvent的处理

    image
    • demo展示
    image

    实现说明及注意点

    • 使用RelaytiveLayout,通过addView(ImageView.setImageBitmap(Bitmap))方式添加所有小方块,其中添加的View需设置合适的LayoutParams
        1.根据RelaytiveLayout及所选择图片的宽高选择游戏区域,并创建各个小滑块
    
        private List<Units> calcBitmap() {
            if (TextUtils.isEmpty(mImagePath)) {
                return null;
            }
            mBitmap = BitmapFactory.decodeFile(mImagePath);
            List<Units> bms = new ArrayList<>();
            int bw = mBitmap.getWidth();
            int bh = mBitmap.getHeight();
            float scale = 1;
            if ((getWidth() * 1.0f) / (bw * 1.0f) > (getHeight() * 1.0f) / (bh * 1.0f)) {
                scale = (getWidth() * 1.0f) / (bw * 1.0f);
            } else {
                scale = (getHeight() * 1.0f) / (bh * 1.0f);
            }
            Matrix matrix = new Matrix();
            matrix.setScale(scale, scale);
            mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(),
                    mBitmap.getHeight(), matrix, true);
            bw = mBitmap.getWidth();
            bh = mBitmap.getHeight();
            if (bw * 1.f / bh > getWidth() * 1.f / getHeight()) {
                mBitmap = Bitmap.createBitmap(mBitmap, (bw - getWidth()) / 2, 0, getWidth(), getHeight());
            } else {
                mBitmap = Bitmap.createBitmap(mBitmap, 0, (bh - getHeight()) / 2, getWidth(), getHeight());
            }
            for (int i = 0; i < mColumns * mColumns; i++) {
                if (i != mColumns * mColumns - 1) {
                    bms.add(new Units(Bitmap.createBitmap(mBitmap, i % mColumns * getWidth() / mColumns, i / mColumns * getHeight() / mColumns, getWidth() / mColumns, getHeight() / mColumns), i));
                } else {
                    sortBlocks(bms);        //排序滑块
                    bms.add(new Units(Bitmap.createBitmap(getWidth() / mColumns, getHeight() / mColumns, Bitmap.Config.ALPHA_8), i));
                }
            }
            return bms;
        }
        
        class Units {
            Bitmap bm;
            //用于判断滑块所在位置是否正确
            int    tag;
    
            Units(Bitmap bm, int tag) {
                this.bm = bm;
                this.tag = tag;
            }
        }
    
        2.计算所有滑块的所处的ViewGroup区域
        private void initRects() {
            mRects = new ArrayList<>();
            int unitWidth = getWidth() / mColumns;
            int unitHeight = getHeight() / mColumns;
    
            for (int i = 0; i < mColumns * mColumns; i++) {
                mRects.add(new Rect(i % mColumns * unitWidth
                                           , i / mColumns * unitHeight
                                           , i % mColumns * unitWidth + unitWidth
                                           , i / mColumns * unitHeight + unitHeight));
            }
        }
    
        3.创建ImageView并添加到ViewGroup中
        for (int i = 0; i < mBms.size(); i++) {
            ImageView imageView = new ImageView(getContext());
            imageView.setImageBitmap(mBms.get(i).bm);
            RelativeLayout.LayoutParams lp = new LayoutParams(getWidth() / mColumns, getHeight() / mColumns);
            imageView.setLayoutParams(lp);
            addView(imageView);
        }
    
        4.处理onTouchEvent
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getPointerCount() > 1 || TextUtils.isEmpty(mImagePath) || finished) {
                return false;
            }
            mX = event.getX();
            mY = event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDownX = event.getX();
                    mDownY = event.getY();
                    calcCurrentItem();      //计算当前触摸条目
                    break;
                case MotionEvent.ACTION_MOVE:
                    calcOrientation();      //计算滑动方向
                    calcMoveAble();         //根据游戏规则判断触摸滑块是否可滑动
                    calcMoveFinish();       //判断滑块是否完成了滑动
                    if (!mMoveAble) {
                        return false;
                    }
                    requestLayout();
                    break;
                case MotionEvent.ACTION_UP:
                    if (!mMoveAble) {
                        return false;
                    }
                    if (isMoveFinish) {
                        Collections.swap(mBms, mCurrentItem, mBlankItem);
                        resetChild();
                    } else {
                        calcNeedMoveBlock();    //根据手指移动距离判断是否需要在手指抬起时移动滑块
                    }
                    calcResult();
                    requestLayout();
                    break;
                default:
                    break;
            }
            return true;
        }
        
        private void calcCurrentItem() {
            if (TextUtils.isEmpty(mImagePath)) {
                return;
            }
            for (int i = 0; i < mRects.size(); i++) {
                if (mRects.get(i).contains((int) mDownX, (int) mDownY)) {
                    mCurrentItem = i;
                    return;
                }
            }
        }
        
        private void calcOrientation() {
            if (Math.abs(mX - mDownX) > Math.abs(mY - mDownY) && mX - mDownX < 0) {
                mMoveOrientation = 1;
            } else if (Math.abs(mX - mDownX) > Math.abs(mY - mDownY) && mX - mDownX > 0) {
                mMoveOrientation = 3;
            } else if (Math.abs(mX - mDownX) < Math.abs(mY - mDownY) && mY - mDownY > 0) {
                mMoveOrientation = 4;
            } else {
                mMoveOrientation = 2;
            }
        }
        
        private void calcMoveAble() {
            for (int i = 0; i < mBms.size(); i++) {
                if (mBms.get(i).tag == mColumns * mColumns - 1) {
                    mBlankItem = i;
                    break;
                }
            }
            if ((mBlankItem + 1) % mColumns == 0) {
                switch (mMoveOrientation) {
                    case 2:
                        mMoveAble = mCurrentItem - mColumns == mBlankItem;
                        break;
                    case 3:
                        mMoveAble = mCurrentItem + 1 == mBlankItem;
                        break;
                    case 4:
                        mMoveAble = mCurrentItem + mColumns == mBlankItem;
                        break;
                    case 1:
                    default:
                        mMoveAble = false;
                        break;
                }
            } else if ((mBlankItem + 1) % mColumns == 1) {
                switch (mMoveOrientation) {
                    case 1:
                        mMoveAble = mCurrentItem - 1 == mBlankItem;
                        break;
                    case 2:
                        mMoveAble = mCurrentItem - mColumns == mBlankItem;
                        break;
                    case 4:
                        mMoveAble = mCurrentItem + mColumns == mBlankItem;
                        break;
                    case 3:
                    default:
                        mMoveAble = false;
                        break;
                }
            } else {
                switch (mMoveOrientation) {
                    case 1:
                        mMoveAble = mCurrentItem - 1 == mBlankItem;
                        break;
                    case 2:
                        mMoveAble = mCurrentItem - mColumns == mBlankItem;
                        break;
                    case 3:
                        mMoveAble = mCurrentItem + 1 == mBlankItem;
                        break;
                    case 4:
                        mMoveAble = mCurrentItem + mColumns == mBlankItem;
                        break;
                    default:
                        mMoveAble = false;
                        break;
                }
            }
        }
        
        private void calcMoveFinish() {
            switch (mMoveOrientation) {
                case 1:
                case 3:
                    isMoveFinish = Math.abs(mX - mDownX) > mRects.get(mBlankItem).right - mRects.get(mBlankItem).left;
                    break;
                case 2:
                case 4:
                    isMoveFinish = Math.abs(mY - mDownY) > mRects.get(mBlankItem).bottom - mRects.get(mBlankItem).top;
                    break;
                default:
                    break;
            }
        }
        
        private void calcNeedMoveBlock() {
            switch (mMoveOrientation) {
                case 1:
                case 3:
                    isMoveFinish = Math.abs(mX - mDownX) * 3 > Math.abs(mRects.get(mCurrentItem).right - mRects.get(mCurrentItem).left);
                    break;
                case 2:
                case 4:
                    isMoveFinish = Math.abs(mY - mDownY) * 3 > Math.abs(mRects.get(mCurrentItem).bottom - mRects.get(mCurrentItem).top);
                    break;
                default:
                    break;
            }
            if (isMoveFinish) {
                Collections.swap(mBms, mCurrentItem, mBlankItem);
                resetChild();
            } else {
                resetChild();
            }
        }
    
        5.重写onMeasure及onLayout
        
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
            if (widthSpecMode == AT_MOST && heightSpecMode == AT_MOST) {
                setMeasuredDimension(mMinSize, mMinSize);
            } else if (widthMeasureSpec == AT_MOST) {
                setMeasuredDimension(mMinSize, heightSpecSize);
            } else if (heightMeasureSpec == AT_MOST) {
                setMeasuredDimension(widthSpecSize, mMinSize);
            }
            measureChild();
        }
        
        private void measureChild() {
            for (int i = 0; i < getChildCount(); i++) {
                if (i == mColumns * mColumns) {
                    getChildAt(i).measure(getWidth(), getHeight());
                    return;
                }
                getChildAt(i).measure(getWidth() / mColumns - mSeparatorWidth, getHeight() / mColumns - mSeparatorWidth);
            }
        }
        
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
            for (int i = 0; i < getChildCount(); i++) {
                if (i == mColumns * mColumns) {
                    getChildAt(i).layout(0, 0, getWidth(), getHeight());
                    return;
                }
                if (mCurrentItem == i) {
                    if (isMoveFinish) {
                        getChildAt(i).layout(mRects.get(mBlankItem).left + mSeparatorWidth / 2
                                , mRects.get(mBlankItem).top + mSeparatorWidth / 2
                                , mRects.get(mBlankItem).right - mSeparatorWidth / 2
                                , mRects.get(mBlankItem).bottom - mSeparatorWidth / 2);
                    } else {
                        switch (mMoveOrientation) {
                            case 1:
                                getChildAt(i).layout((int) (mX - mDownX + mRects.get(mCurrentItem).left) + mSeparatorWidth / 2
                                        , mRects.get(mCurrentItem).top + mSeparatorWidth / 2
                                        , (int) (mX - mDownX + mRects.get(mCurrentItem).right) - mSeparatorWidth / 2
                                        , mRects.get(mCurrentItem).bottom - mSeparatorWidth / 2);
                                break;
                            case 3:
                                getChildAt(i).layout((int) (mX - mDownX + mRects.get(mCurrentItem).left) + mSeparatorWidth / 2
                                        , mRects.get(mCurrentItem).top + mSeparatorWidth / 2
                                        , (int) (mX - mDownX + mRects.get(mCurrentItem).right) - mSeparatorWidth / 2
                                        , mRects.get(mCurrentItem).bottom - mSeparatorWidth / 2);
                                break;
                            case 2:
                                getChildAt(i).layout(mRects.get(mCurrentItem).left + mSeparatorWidth / 2
                                        , (int) (mY - mDownY + mRects.get(mCurrentItem).top) + mSeparatorWidth / 2
                                        , mRects.get(mCurrentItem).right - mSeparatorWidth / 2
                                        , (int) (mY - mDownY + mRects.get(mCurrentItem).bottom) - mSeparatorWidth / 2);
                                break;
                            case 4:
                                getChildAt(i).layout(mRects.get(mCurrentItem).left + mSeparatorWidth / 2
                                        , (int) (mY - mDownY + mRects.get(mCurrentItem).top) + mSeparatorWidth / 2
                                        , mRects.get(mCurrentItem).right - mSeparatorWidth / 2
                                        , (int) (mY - mDownY + mRects.get(mCurrentItem).bottom) - mSeparatorWidth / 2);
                                break;
                            default:
                                getChildAt(i).layout(mRects.get(i).left + mSeparatorWidth / 2
                                        , mRects.get(i).top + mSeparatorWidth / 2
                                        , mRects.get(i).right - mSeparatorWidth / 2
                                        , mRects.get(i).bottom - mSeparatorWidth / 2);
                                break;
                        }
                    }
                } else {
                    getChildAt(i).layout(mRects.get(i).left + mSeparatorWidth / 2
                            , mRects.get(i).top + mSeparatorWidth / 2
                            , mRects.get(i).right - mSeparatorWidth / 2
                            , mRects.get(i).bottom - mSeparatorWidth / 2);
                }
            }
        }
    
        6.对外暴露的接口
        public void showResult() {
            int childCount = getChildCount();
            if (childCount == mColumns * mColumns + 1 || TextUtils.isEmpty(mImagePath)) {
                return;
            }
            ImageView imageView = new ImageView(getContext());
            imageView.setImageBitmap(mBitmap);
            RelativeLayout.LayoutParams lp = new LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
            imageView.setLayoutParams(lp);
            addView(imageView);
            invalidate();
        }
    
        public void hideResult() {
            if (TextUtils.isEmpty(mImagePath)) {
                return;
            }
            removeViewAt(getChildCount() - 1);
            invalidate();
        }
    
        public void setEasier() {
            if (TextUtils.isEmpty(mImagePath) || mColumns <= 3) {
                return;
            }
            mColumns -= 1;
            setImage(mImagePath);
        }
    
        public void setHarder() {
            if (TextUtils.isEmpty(mImagePath) || mColumns >= 5) {
                return;
            }
            mColumns += 1;
            setImage(mImagePath);
        }
    
        public interface FinishListener {
            void finish(long mills);
        }
    
        public void addFinishListener(FinishListener listener) {
            mListener = listener;
        }
    
    • 难点:简单的使用ColleCtions.suffle()打乱各个方块将有一半的几率导致游戏无法完成,需使用Collections.swap(int i,int j)方式打乱各模块
    private void sortBlocks(List<Units> bms) {
            if (mColumns % 2 == 0) {
                for (int i = 0; i < 40; i++) {
                    int index = (int) (Math.random() * bms.size());
                    if (index % mColumns + 2 > mColumns) {
                        if (index - 2 >= 0) {
                            Collections.swap(bms, index, index - 2);
                        } else {
                            Collections.swap(bms, index, index + mColumns * 2);
                        }
                    } else {
                        if (index + 2 < bms.size()) {
                            Collections.swap(bms, index, index + 2);
                        } else {
                            Collections.swap(bms, index, index - mColumns * 2);
                        }
                    }
                }
            } else {
                for (int i = 0; i < 60; i++) {
                    int index = (int) (Math.random() * bms.size());
                    if (index + 2 >= bms.size()) {
                        Collections.swap(bms, index, index - 2);
                    } else {
                        Collections.swap(bms, index, index + 2);
                    }
                }
            }
        }
    
    • 错误示例(相邻两个滑块直接进行奇数数次交换位置,最后将会剩下两个滑块位置颠倒,且无法通过移动滑块位置还原)
    image
    • 正确示例
    image

    游戏说明

    • 可以通过easy和hard减少和增加游戏难度(最易为3x3难度,最难为5x5难度)
    • 可以通过按住showpic按钮显示完成后效果图片

    其他说明

    • 使用了RxPermission对不同版本手机进行了拍照及文件读取权限处理
    • 使用了ImagePicker图片选择框架选择手机内存图片或拍照

    相关文章

      网友评论

          本文标题:仿九宫图的Android小游戏

          本文链接:https://www.haomeiwen.com/subject/butnlftx.html