欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android利用ViewDragHelper輕松實現(xiàn)拼圖游戲的示例

 更新時間:2017年11月01日 12:04:14   作者:Kevinmob  
本篇文章主要介紹了Android利用ViewDragHelper輕松實現(xiàn)拼圖游戲的示例,非常具有實用價值,需要的朋友可以參考下

前言

最近一段時間看了一些介紹ViewDragHelper的博客,感覺這是一個處理手勢滑動的神奇,看完以后就想做點東西練練手,于是就做了這個Android拼圖小游戲。

先上個效果圖


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

ViewDragHelper

其實ViewDragHelper并不是第一個用于分析手勢處理的類,gesturedetector也是,但是在和拖動相關(guān)的手勢分析方面gesturedetector只能說是勉為其難。

關(guān)于ViewDragHelper有如下幾點:

ViewDragHelper.Callback是連接ViewDragHelper與view之間的橋梁(這個view一般是指擁子view的容器即parentView);

ViewDragHelper的實例是通過靜態(tài)工廠方法創(chuàng)建的;

你能夠指定拖動的方向;

 ViewDragHelper可以檢測到是否觸及到邊緣;

ViewDragHelper并不是直接作用于要被拖動的View,而是使其控制的視圖容器中的子View可以被拖動,如果要指定某個子view的行為,需要在Callback中想辦法;

ViewDragHelper的本質(zhì)其實是分析onInterceptTouchEvent和onTouchEvent的MotionEvent參數(shù),然后根據(jù)分析的結(jié)果去改變一個容器中被拖動子View的位置( 通過offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在觸摸的時候判斷當(dāng)前拖動的是哪個子View;

雖然ViewDragHelper的實例方法 ViewDragHelper create(ViewGroup forParent, Callback cb) 可以指定一個被ViewDragHelper處理拖動事件的對象 。

實現(xiàn)思路

  1. 自定義PuzzleLayout繼承自RelativeLayout。
  2. 將PuzzleLayout的onInterceptTouchEvent和onTouchEvent交給ViewDragHelper來處理。
  3. 將拼圖Bitmap按九宮格切割,生成ImageView添加到PuzzleLayout并進(jìn)行排列。
  4. 創(chuàng)建ImageView的對應(yīng)數(shù)據(jù)模型。
  5. ViewDragHelper.Callback控制滑動邊界的實現(xiàn)。
  6. 打亂ImageView的擺放位置。

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

第一步: 創(chuàng)建一個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來處理。

這里我們會用到ViewDragHelper這個處理手勢滑動的神器。
在使用之前我們先簡單的了解一下它的相關(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)

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

  1. 第一個參數(shù)是當(dāng)前的ViewGroup。
  2. 第二個參數(shù)是檢測拖動開始的靈敏度,1.0f為正常值。
  3. 第三個參數(shù)Callback,是ViewDragHelper給ViewGroup的回調(diào)。

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

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

public abstract boolean tryCaptureView(View child, int pointerId)

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

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

控制childView在水平方向的滑動,主要用來限定childView滑動的左右邊界。

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

控制childView在垂直方向的滑動,主要用來限定childView滑動的上下邊界。

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

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

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

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)行排列。


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

創(chuàng)建子view, mHelper是封裝的用來操作對應(yīng)數(shù)據(jù)模型的幫助類DataHelper。

/**
 * 將子View index與mHelper中models的index一一對應(yīng),
 * 每次在交換子View位置的時候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的對應(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一一對應(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<Block> 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控制滑動邊界的實現(xiàn)。

tryCaptureView的實現(xiàn)

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

DataHelper的getScrollDirection函數(shù)

/**
 * 獲取索引處model的可移動方向,不能移動返回 -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)前位置是否可以移動,如果可以移動就return可移動的方向。
   */

  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的實現(xiàn)細(xì)節(jié),獲取滑動方向左或右,再控制對應(yī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的實現(xiàn)細(xì)節(jié),獲取滑動方向上或下,再控制對應(yī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的實現(xiàn),當(dāng)松手時,不可見View和松開的View之間進(jìn)行布局參數(shù)交換,同時對應(yīng)的model之間也需要通過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配合實現(xiàn)松手后的動畫效果。

PuzzleLayout重寫computeScroll函數(shù)。

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

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


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

/**
 * 交換兩個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ù)原拼圖了,這里使用的辦法是每次在不可見View附近隨機(jī)找一個View與不可見View進(jìn)行位置交換,這里的位置交換指的是布局參數(shù)的交換,同時對應(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ī)查詢出不可見
 * 位置周圍的一個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();
}

/**
 * 通過給定的位置獲取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;
}

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

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論