成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

利用ViewDragHelper輕松實(shí)現(xiàn)Android拼圖游戲

zzir / 655人閱讀

摘要:將拼圖按九宮格切割,生成添加到并進(jìn)行排列??刂苹瑒?dòng)邊界的實(shí)現(xiàn)??刂圃谒椒较虻幕瑒?dòng),主要用來(lái)限定滑動(dòng)的左右邊界。和配合實(shí)現(xiàn)松手后的動(dòng)畫效果。交換兩個(gè)的值判斷是否拼圖完成。

前言

最近一段時(shí)間看了一些介紹ViewDragHelper的博客,感覺(jué)這是一個(gè)處理手勢(shì)滑動(dòng)的神奇,看完以后就想做點(diǎn)東西練練手,于是就做了這個(gè)Android拼圖小游戲。

先上個(gè)效果圖

源碼 https://github.com/kevin-mob/Puzzle

實(shí)現(xiàn)思路

自定義PuzzleLayout繼承自RelativeLayout。

將PuzzleLayout的onInterceptTouchEvent和onTouchEvent交給ViewDragHelper來(lái)處理。

將拼圖Bitmap按九宮格切割,生成ImageView添加到PuzzleLayout并進(jìn)行排列。

創(chuàng)建ImageView的對(duì)應(yīng)數(shù)據(jù)模型。

ViewDragHelper.Callback控制滑動(dòng)邊界的實(shí)現(xiàn)。

打亂ImageView的擺放位置。

下面介紹一下以上5步的具體實(shí)現(xiàn)細(xì)節(jié)。

第一步: 創(chuàng)建一個(gè)PuzzleLayout繼承自RelativeLayout。
public class PuzzleLayout extends RelativeLayout {
    public PuzzleLayout(Context context) {
            super(context);
        }
    
        public PuzzleLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public PuzzleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        }
}
第二步:將PuzzleLayout的onInterceptTouchEvent和onTouchEvent交給ViewDragHelper來(lái)處理。

這里我們會(huì)用到ViewDragHelper這個(gè)處理手勢(shì)滑動(dòng)的神器。
在使用之前我們先簡(jiǎn)單的了解一下它的相關(guān)函數(shù)。

/**
 * Factory method to create a new ViewDragHelper.
 *
 * @param forParent Parent view to monitor
 * @param sensitivity Multiplier for how sensitive the helper
 *  should be about detecting the start of a drag. 
 *  Larger values are more sensitive. 1.0f is normal.
 * @param cb Callback to provide information and receive events
 * @return a new ViewDragHelper instance
 */
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)

上面這個(gè)是創(chuàng)建一個(gè)ViewDragHelper的靜態(tài)函數(shù),根據(jù)注釋我們可以了解到:

第一個(gè)參數(shù)是當(dāng)前的ViewGroup。

第二個(gè)參數(shù)是檢測(cè)拖動(dòng)開(kāi)始的靈敏度,1.0f為正常值。

第三個(gè)參數(shù)Callback,是ViewDragHelper給ViewGroup的回調(diào)。

這里我們主要來(lái)看看Callback這個(gè)參數(shù),Callback會(huì)在手指觸摸當(dāng)前ViewGroup的過(guò)程中不斷返回解析到的相關(guān)事件和狀態(tài),并獲取ViewGroup返回給ViewDragHelper的狀態(tài),來(lái)決定接下來(lái)的操作是否需要執(zhí)行,從而達(dá)到了在ViewGroup中管理和控制ViewDragHelper的目的。

Callback的方法很多,這里主要介紹本文用到的幾個(gè)方法

public abstract boolean tryCaptureView(View child, int pointerId)

嘗試捕獲當(dāng)前手指觸摸到的子view, 返回true 允許捕獲,false不捕獲。

public int clampViewPositionHorizontal(View child, int left, int dx)

控制childView在水平方向的滑動(dòng),主要用來(lái)限定childView滑動(dòng)的左右邊界。

public int clampViewPositionVertical(View child, int top, int dy)

控制childView在垂直方向的滑動(dòng),主要用來(lái)限定childView滑動(dòng)的上下邊界。

public void onViewReleased(View releasedChild, float xvel, float yvel)

當(dāng)手指從childView上離開(kāi)時(shí)回調(diào)。

有了以上這些函數(shù),我們的拼圖游戲大致就可以做出來(lái)了,通過(guò)ViewDragHelper.create()來(lái)創(chuàng)建一個(gè)ViewDragHelper,通過(guò)Callback中tryCaptureView來(lái)控制當(dāng)前觸摸的子view是否可以滑動(dòng),clampViewPositionHorizontal、clampViewPositionVertical來(lái)控制水平方向和垂直方向的移動(dòng)邊界,具體的方法實(shí)現(xiàn)會(huì)在后面講到。

public class PuzzleLayout extends RelativeLayout {
    private ViewDragHelper viewDragHelper;
    public PuzzleLayout(Context context) {
        super(context);
        init();
    }

    public PuzzleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PuzzleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                mHeight = getHeight();
                mWidth = getWidth();
                getViewTreeObserver().removeOnPreDrawListener(this);
                if(mDrawableId != 0 && mSquareRootNum != 0){
                    createChildren();
                }
                return false;
            }
        });
        viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                return true;
            }

            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {

                return left;
            }

            @Override
            public int clampViewPositionVertical(View child, int top, int dy) {
                return top;
            }

            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event){
        return viewDragHelper.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        viewDragHelper.processTouchEvent(event);
        return true;
    }
}
第三步,將拼圖Bitmap按九宮格切割,生成ImageView添加到PuzzleLayout并進(jìn)行排列。

首先,外界需要傳入一個(gè)切割參數(shù)mSquareRootNum做為寬和高的切割份數(shù),我們需要獲取PuzzleLayout的寬和高,然后計(jì)算出每一塊的寬mItemWidth和高mItemHeight, 將Bitmap等比例縮放到和PuzzleLayout大小相等,然后將圖片按照類似上面這張圖所標(biāo)的形式進(jìn)行切割,生成mSquareRootNum*mSquareRootNum份Bitmap,每個(gè)Bitmap對(duì)應(yīng)創(chuàng)建一個(gè)ImageView載體添加到PuzzleLayout中,并進(jìn)行布局排列。
創(chuàng)建子view, mHelper是封裝的用來(lái)操作對(duì)應(yīng)數(shù)據(jù)模型的幫助類DataHelper。

/**
 *  將子View index與mHelper中models的index一一對(duì)應(yīng),
 *  每次在交換子View位置的時(shí)候model同步更新currentPosition。
 */
private void createChildren(){
    mHelper.setSquareRootNum(mSquareRootNum);

    DisplayMetrics dm = getResources().getDisplayMetrics();
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inDensity = dm.densityDpi;

    Bitmap resource = BitmapFactory.decodeResource(getResources(), mDrawableId, options);
    Bitmap bitmap = BitmapUtil.zoomImg(resource, mWidth, mHeight);
    resource.recycle();

    mItemWidth = mWidth / mSquareRootNum;
    mItemHeight = mHeight / mSquareRootNum;

    for (int i = 0; i < mSquareRootNum; i++){
        for (int j = 0; j < mSquareRootNum; j++){
            Log.d(TAG, "mItemWidth * x " + (mItemWidth * i));
            Log.d(TAG, "mItemWidth * y " + (mItemWidth * j));
            ImageView iv = new ImageView(getContext());
            iv.setScaleType(ImageView.ScaleType.FIT_XY);
            LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            lp.leftMargin = j * mItemWidth;
            lp.topMargin = i * mItemHeight;
            iv.setLayoutParams(lp);
            Bitmap b = Bitmap.createBitmap(bitmap, lp.leftMargin, lp.topMargin, mItemWidth, mItemHeight);
            iv.setImageBitmap(b);
            addView(iv);
        }
    }
}
第四步,創(chuàng)建ImageView的對(duì)應(yīng)數(shù)據(jù)模型。
public class Block {
    public Block(int position, int vPosition, int hPosition){
        this.position = position;
        this.vPosition = vPosition;
        this.hPosition = hPosition;
    }
    public int position;
    public int vPosition;
    public int hPosition;
}

DataHelper.class
子View在父類的index與mHelper中model在models的index一一對(duì)應(yīng)

class DataHelper {
    static final int N = -1;
    static final int L = 0;
    static final int T = 1;
    static final int R = 2;
    static final int B = 3;
    private static final String TAG = DataHelper.class.getSimpleName();

    private int squareRootNum;
    private List models;

    DataHelper(){
        models = new ArrayList<>();
    }

    private void reset() {
        models.clear();
        int position = 0;
        for (int i = 0; i< squareRootNum; i++){
            for (int j = 0; j < squareRootNum; j++){
                models.add(new Block(position, i, j));
                position ++;
            }
        }
    }

    void setSquareRootNum(int squareRootNum){
        this.squareRootNum = squareRootNum;
        reset();
    }
}
第五步,ViewDragHelper.Callback控制滑動(dòng)邊界的實(shí)現(xiàn)。

tryCaptureView的實(shí)現(xiàn)

public boolean tryCaptureView(View child, int pointerId) {
            int index = indexOfChild(child);
            return mHelper.getScrollDirection(index) != DataHelper.N;
        }

DataHelper的getScrollDirection函數(shù)

/**
 * 獲取索引處model的可移動(dòng)方向,不能移動(dòng)返回 -1。
 */
int getScrollDirection(int index){

    Block model = models.get(index);
    int position = model.position;

    //獲取當(dāng)前view所在位置的坐標(biāo) x y
    /*
     *      * * * *
     *      * o * *
     *      * * * *
     *      * * * *
     */
    int x = position % squareRootNum;
    int y = position / squareRootNum;
    int invisibleModelPosition = models.get(0).position;

    /*
     * 判斷當(dāng)前位置是否可以移動(dòng),如果可以移動(dòng)就return可移動(dòng)的方向。
     */

    if(x != 0 && invisibleModelPosition == position - 1)
        return L;

    if(x != squareRootNum - 1 && invisibleModelPosition == position + 1)
        return R;

    if(y != 0 && invisibleModelPosition == position - squareRootNum)
        return T;

    if(y != squareRootNum - 1 && invisibleModelPosition == position + squareRootNum)
        return B;

    return N;
}

clampViewPositionHorizontal的實(shí)現(xiàn)細(xì)節(jié),獲取滑動(dòng)方向左或右,再控制對(duì)應(yīng)的滑動(dòng)區(qū)域。

public int clampViewPositionHorizontal(View child, int left, int dx) {

            int index = indexOfChild(child);
            int position = mHelper.getModel(index).position;
            int selfLeft = (position % mSquareRootNum) * mItemWidth;
            int leftEdge = selfLeft - mItemWidth;
            int rightEdge = selfLeft + mItemWidth;
            int direction = mHelper.getScrollDirection(index);
            //Log.d(TAG, "left " + left + " index" + index + " dx " + dx + " direction " + direction);
            switch (direction){
                case DataHelper.L:
                    if(left <= leftEdge)
                        return leftEdge;
                    else if(left >= selfLeft)
                        return selfLeft;
                    else
                        return left;

                case DataHelper.R:
                    if(left >= rightEdge)
                        return rightEdge;
                    else if (left <= selfLeft)
                        return selfLeft;
                    else
                        return left;
                default:
                    return selfLeft;
            }
        }

clampViewPositionVertical的實(shí)現(xiàn)細(xì)節(jié),獲取滑動(dòng)方向上或下,再控制對(duì)應(yīng)的滑動(dòng)區(qū)域。

public int clampViewPositionVertical(View child, int top, int dy) {
            int index = indexOfChild(child);
            Block model = mHelper.getModel(index);
            int position = model.position;

            int selfTop = (position / mSquareRootNum) * mItemHeight;
            int topEdge = selfTop - mItemHeight;
            int bottomEdge = selfTop + mItemHeight;
            int direction = mHelper.getScrollDirection(index);
            //Log.d(TAG, "top " + top + " index " + index + " direction " + direction);
            switch (direction){
                case DataHelper.T:
                    if(top <= topEdge)
                        return topEdge;
                    else if (top >= selfTop)
                        return selfTop;
                    else
                        return top;
                case DataHelper.B:
                    if(top >= bottomEdge)
                        return bottomEdge;
                    else if (top <= selfTop)
                        return selfTop;
                    else
                        return top;
                default:
                    return selfTop;
            }
        }

onViewReleased的實(shí)現(xiàn),當(dāng)松手時(shí),不可見(jiàn)View和松開(kāi)的View之間進(jìn)行布局參數(shù)交換,同時(shí)對(duì)應(yīng)的model之間也需要通過(guò)swapValueWithInvisibleModel函數(shù)進(jìn)行數(shù)據(jù)交換。

public void onViewReleased(View releasedChild, float xvel, float yvel) {
            Log.d(TAG, "xvel " + xvel + " yvel " + yvel);
            int index = indexOfChild(releasedChild);
            boolean isCompleted = mHelper.swapValueWithInvisibleModel(index);
            Block item =  mHelper.getModel(index);
            viewDragHelper.settleCapturedViewAt(item.hPosition * mItemWidth, item.vPosition * mItemHeight);
            View invisibleView = getChildAt(0);
            ViewGroup.LayoutParams layoutParams = invisibleView.getLayoutParams();
            invisibleView.setLayoutParams(releasedChild.getLayoutParams());
            releasedChild.setLayoutParams(layoutParams);
            invalidate();
            if(isCompleted){
                invisibleView.setVisibility(VISIBLE);
                mOnCompleteCallback.onComplete();
            }
        }

viewDragHelper.settleCapturedViewAt和viewDragHelper.continueSettling配合實(shí)現(xiàn)松手后的動(dòng)畫效果。

PuzzleLayout重寫computeScroll函數(shù)。

@Override
public void computeScroll() {
    if(viewDragHelper.continueSettling(true)) {
        invalidate();
    }
}

swapValueWithInvisibleModel函數(shù),每次交換完成后會(huì)return拼圖是否完成

/**
 * 將索引出的model的值與不可見(jiàn)
 * model的值互換。
 */
boolean swapValueWithInvisibleModel(int index){
    Block formModel = models.get(index);
    Block invisibleModel = models.get(0);
    swapValue(formModel, invisibleModel);
    return isCompleted();
}

/**
 * 交換兩個(gè)model的值
 */
private void swapValue(Block formModel, Block invisibleModel) {

    int position = formModel.position;
    int hPosition = formModel.hPosition;
    int vPosition = formModel.vPosition;

    formModel.position = invisibleModel.position;
    formModel.hPosition = invisibleModel.hPosition;
    formModel.vPosition = invisibleModel.vPosition;

    invisibleModel.position = position;
    invisibleModel.hPosition = hPosition;
    invisibleModel.vPosition = vPosition;
}

/**
 * 判斷是否拼圖完成。
 */
private boolean isCompleted(){
    int num = squareRootNum * squareRootNum;
    for (int i = 0; i < num; i++){
        Block model = models.get(i);
        if(model.position != i){
            return false;
        }
    }
    return true;
}
第六步,打亂ImageView的擺放位置。

這里不能隨意打亂順序,否則你可能永遠(yuǎn)也不能復(fù)原拼圖了,這里使用的辦法是每次在不可見(jiàn)View附近隨機(jī)找一個(gè)View與不可見(jiàn)View進(jìn)行位置交換,這里的位置交換指的是布局參數(shù)的交換,同時(shí)對(duì)應(yīng)的數(shù)據(jù)模型也需要進(jìn)行數(shù)據(jù)交換。

public void randomOrder(){
    int num = mSquareRootNum * mSquareRootNum * 8;
    View invisibleView = getChildAt(0);
    View neighbor;
    for (int i = 0; i < num; i ++){
        int neighborPosition = mHelper.findNeighborIndexOfInvisibleModel();
        ViewGroup.LayoutParams invisibleLp = invisibleView.getLayoutParams();
        neighbor = getChildAt(neighborPosition);
        invisibleView.setLayoutParams(neighbor.getLayoutParams());
        neighbor.setLayoutParams(invisibleLp);
        mHelper.swapValueWithInvisibleModel(neighborPosition);
    }
    invisibleView.setVisibility(INVISIBLE);
}

DataHelper中findNeighborIndexOfInvisibleModel函數(shù)

/**
 * 隨機(jī)查詢出不可見(jiàn)
 * 位置周圍的一個(gè)model的索引。
 */
public int findNeighborIndexOfInvisibleModel() {
    Block invisibleModel = models.get(0);
    int position = invisibleModel.position;
    int x = position % squareRootNum;
    int y = position / squareRootNum;
    int direction = new Random(System.nanoTime()).nextInt(4);
    Log.d(TAG, "direction " + direction);
    switch (direction){
        case L:
            if(x != 0)
                return getIndexByCurrentPosition(position - 1);
        case T:
            if(y != 0)
                return getIndexByCurrentPosition(position - squareRootNum);
        case R:
            if(x != squareRootNum - 1)
                return getIndexByCurrentPosition(position + 1);
        case B:
            if(y != squareRootNum - 1)
                return getIndexByCurrentPosition(position + squareRootNum);
    }
    return findNeighborIndexOfInvisibleModel();
}

/**
 * 通過(guò)給定的位置獲取model的索引
 */
private int getIndexByCurrentPosition(int currentPosition){
    int num = squareRootNum * squareRootNum;
    for (int i = 0; i < num; i++) {
        if(models.get(i).position == currentPosition)
            return i;
    }
    return -1;
}

以上為主要的代碼實(shí)現(xiàn),全部工程已上傳Github,歡迎學(xué)習(xí),歡迎star,傳送門
https://github.com/kevin-mob/Puzzle

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/67920.html

相關(guān)文章

  • 打造專屬自己的html5拼圖游戲

    摘要:最近公司剛好有個(gè)活動(dòng)是要做一版的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來(lái)吧,大家一起加油。。。 最近公司剛好有個(gè)活動(dòng)是要做一版 html5的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...

    codeKK 評(píng)論0 收藏0
  • 打造專屬自己的html5拼圖游戲

    摘要:最近公司剛好有個(gè)活動(dòng)是要做一版的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來(lái)吧,大家一起加油。。。 最近公司剛好有個(gè)活動(dòng)是要做一版 html5的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...

    JowayYoung 評(píng)論0 收藏0
  • 打造專屬自己的html5拼圖游戲

    摘要:最近公司剛好有個(gè)活動(dòng)是要做一版的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來(lái)吧,大家一起加油。。。 最近公司剛好有個(gè)活動(dòng)是要做一版 html5的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...

    趙春朋 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<