中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Android中使用ViewDragHelper實現一個拼圖游戲

發布時間:2021-04-08 17:08:38 來源:億速云 閱讀:142 作者:Leah 欄目:移動開發

本篇文章給大家分享的是有關怎么在Android中使用ViewDragHelper實現一個拼圖游戲,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

ViewDragHelper

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

關于ViewDragHelper有如下幾點:

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

ViewDragHelper的實例是通過靜態工廠方法創建的;

你能夠指定拖動的方向;

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

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

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

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

實現思路

  1. 自定義PuzzleLayout繼承自RelativeLayout。

  2. 將PuzzleLayout的onInterceptTouchEvent和onTouchEvent交給ViewDragHelper來處理。

  3. 將拼圖Bitmap按九宮格切割,生成ImageView添加到PuzzleLayout并進行排列。

  4. 創建ImageView的對應數據模型。

  5. ViewDragHelper.Callback控制滑動邊界的實現。

  6. 打亂ImageView的擺放位置。

下面介紹一下以上5步的具體實現細節。

第一步: 創建一個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這個處理手勢滑動的神器。
在使用之前我們先簡單的了解一下它的相關函數。

/**
 * 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)

上面這個是創建一個ViewDragHelper的靜態函數,根據注釋我們可以了解到:

  1. 第一個參數是當前的ViewGroup。

  2. 第二個參數是檢測拖動開始的靈敏度,1.0f為正常值。

  3. 第三個參數Callback,是ViewDragHelper給ViewGroup的回調。

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

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

public abstract boolean tryCaptureView(View child, int pointerId)

嘗試捕獲當前手指觸摸到的子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)

當手指從childView上離開時回調。

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

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

怎么在Android中使用ViewDragHelper實現一個拼圖游戲

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

創建子view, mHelper是封裝的用來操作對應數據模型的幫助類DataHelper。

/**
 * 將子View index與mHelper中models的index一一對應,
 * 每次在交換子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);
    }
  }
}

第四步,創建ImageView的對應數據模型。

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一一對應

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控制滑動邊界的實現。

tryCaptureView的實現

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

DataHelper的getScrollDirection函數

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

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

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

  /*
   * 判斷當前位置是否可以移動,如果可以移動就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的實現細節,獲取滑動方向左或右,再控制對應的滑動區域。

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的實現細節,獲取滑動方向上或下,再控制對應的滑動區域。

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的實現,當松手時,不可見View和松開的View之間進行布局參數交換,同時對應的model之間也需要通過swapValueWithInvisibleModel函數進行數據交換。

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配合實現松手后的動畫效果。

PuzzleLayout重寫computeScroll函數。

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

swapValueWithInvisibleModel函數,每次交換完成后會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的擺放位置。

這里不能隨意打亂順序,否則你可能永遠也不能復原拼圖了,這里使用的辦法是每次在不可見View附近隨機找一個View與不可見View進行位置交換,這里的位置交換指的是布局參數的交換,同時對應的數據模型也需要進行數據交換。

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函數

/**
 * 隨機查詢出不可見
 * 位置周圍的一個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;
}

以上就是怎么在Android中使用ViewDragHelper實現一個拼圖游戲,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

康乐县| 尼玛县| 濮阳县| 滨海县| 大名县| 垣曲县| 冀州市| 方山县| 永寿县| 大洼县| 九龙城区| 奉贤区| 双辽市| 磐石市| 黄平县| 绵竹市| 铜陵市| 辽阳县| 九寨沟县| 兴义市| 开封县| 柞水县| 莆田市| 广宁县| 拉孜县| 包头市| 收藏| 莒南县| 永福县| 曲沃县| 永顺县| 达州市| 岑巩县| 郴州市| 新宁县| 淅川县| 南丹县| 金寨县| 龙南县| 嘉定区| 尉犁县|