国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > Android > 正文

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

2019-10-22 18:24:24
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

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

先上個(gè)效果圖

Android,ViewDragHelper拼圖,實(shí)現(xiàn)拼圖游戲

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

ViewDragHelper

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

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

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

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

你能夠指定拖動(dòng)的方向;

 ViewDragHelper可以檢測(cè)到是否觸及到邊緣;

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

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

雖然ViewDragHelper的實(shí)例方法 ViewDragHelper create(ViewGroup forParent, Callback cb) 可以指定一個(gè)被ViewDragHelper處理拖動(dòng)事件的對(duì)象 。

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

  1. 自定義PuzzleLayout繼承自RelativeLayout。
  2. 將PuzzleLayout的onInterceptTouchEvent和onTouchEvent交給ViewDragHelper來(lái)處理。
  3. 將拼圖Bitmap按九宮格切割,生成ImageView添加到PuzzleLayout并進(jìn)行排列。
  4. 創(chuàng)建ImageView的對(duì)應(yīng)數(shù)據(jù)模型。
  5. ViewDragHelper.Callback控制滑動(dòng)邊界的實(shí)現(xiàn)。
  6. 打亂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ù)注釋我們可以了解到:

  1. 第一個(gè)參數(shù)是當(dāng)前的ViewGroup。
  2. 第二個(gè)參數(shù)是檢測(cè)拖動(dòng)開(kāi)始的靈敏度,1.0f為正常值。
  3. 第三個(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)行排列。

Android,ViewDragHelper拼圖,實(shí)現(xià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<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控制滑動(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)畫(huà)效果。

PuzzleLayout重寫(xiě)computeScroll函數(shù)。

@Overridepublic 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) * 位置周?chē)囊粋€(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,傳送門(mén)
https://github.com/kevin-mob/Puzzle

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到Android開(kāi)發(fā)頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 绿春县| 京山县| 安陆市| 威信县| 鹤峰县| 锡林郭勒盟| 龙山县| 大英县| 阳新县| 金门县| 鄂托克前旗| 会同县| 任丘市| 铁岭县| 绩溪县| 丰都县| 普安县| 昭通市| 扶绥县| 麻栗坡县| 左权县| 永春县| 巴林左旗| 肇州县| 离岛区| 安国市| 新泰市| 江川县| 乐亭县| 武城县| 邵阳县| 宜兰市| 长丰县| 巴塘县| 太原市| 定州市| 莲花县| 红安县| 海淀区| 梁山县| 满洲里市|