可拖拽的ListView

長按拖拽item並實現變更排序;
安卓默認長按時間間隔500ms。
在這裏插入圖片描述

說明: WindowManager在addView時尚未測量完成, 獲取寬高都是0。 可以這樣測量:

 view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int width = view.getMeasuredWidth();

使用getViewTreeObserver().addOnGlobalLayoutListener是佈局完成後可以獲取寬高;

public class DragListView extends ListView {

  private WindowManager.LayoutParams windowParams;
  private WindowManager windowManager;
  private ImageView dragImageView;

  private int offsetScreenTop; //距離屏幕頂部的位置
  private int offsetViewTop;  //手指按下位置距離item頂部的位置
  private int dragPosition;

  //上次點擊按下的座標
  private int mLastMotionX, mLastMotionY;

  private final int TOUCH_SLOP = 20;
  private Runnable mLongPressRunnable;
  private Bitmap mCurItemBmp;

  //當前是否長按狀態
  private boolean mIsLongTouch;

  public DragListView(Context context) {
    super(context);
  }

  public DragListView(Context context, AttributeSet attrs) {
    super(context, attrs);

    mLongPressRunnable = new Runnable() {
      @Override public void run() {
        mIsLongTouch = true;

        startDrag(mCurItemBmp, mLastMotionY);
      }
    };
  }

  //itemview裏有view處理點擊事件時, 當前ListView無法收到UP或CANCEL消息
  @Override public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_UP
        || ev.getAction() == MotionEvent.ACTION_CANCEL) {
      removeCallbacks(mLongPressRunnable);
      stopDrag();
    }
    return super.dispatchTouchEvent(ev);
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    int rawY = (int) ev.getRawY();

    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
      mLastMotionX = x;
      mLastMotionY = y;
      mIsLongTouch = false;
      stopDrag();  //復位
      removeCallbacks(mLongPressRunnable);

      int currentPostion = dragPosition = pointToPosition(x, y);

      //拖拽效果設置熱區
      if (currentPostion == AdapterView.INVALID_POSITION) {
        return super.onInterceptTouchEvent(ev);
      }
      postDelayed(mLongPressRunnable, ViewConfiguration.getLongPressTimeout());

      //getChildAt是獲取可見位置的item
      ViewGroup itemView = (ViewGroup) getChildAt(currentPostion - getFirstVisiblePosition());
      offsetScreenTop = rawY - y;
      offsetViewTop = y - itemView.getTop();

      itemView.setDrawingCacheEnabled(true);// 開啓cache.
      mCurItemBmp = Bitmap.createBitmap(itemView.getDrawingCache());// 根據cache創建一個新的bitmap對象.
      itemView.setDrawingCacheEnabled(false);// 一定關閉cache,否則複用會出現錯亂
    }
    return super.onInterceptTouchEvent(ev);
  }


  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    int y = (int) ev.getY();
    int x = (int) ev.getX();
    switch (ev.getAction()) {
      case MotionEvent.ACTION_MOVE:
        if (Math.abs(mLastMotionX - x) > TOUCH_SLOP
           || Math.abs(mLastMotionY - y) > TOUCH_SLOP) {
          //滑動超過閾值時判定不是長按
          removeCallbacks(mLongPressRunnable);
        }

        if (mIsLongTouch) {
          onDrag(y);
          getChildAt(dragPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
          return true; //避免列表滑動
        }
        removeCallbacks(mLongPressRunnable);
        break;
      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:
        removeCallbacks(mLongPressRunnable);
        if (mIsLongTouch) {
          stopDrag();
          getChildAt(dragPosition - getFirstVisiblePosition()).setVisibility(View.VISIBLE);

          mIsLongTouch = false;
        }
        break;
    }

    return super.onTouchEvent(ev);
  }

  private void startDrag(Bitmap bm, int y) {
    int[] location = new int[2];
    getLocationInWindow(location);

    /***
     * 初始化window.
     */
    windowParams = new WindowManager.LayoutParams();
    windowParams.gravity = Gravity.TOP|Gravity.LEFT;
    windowParams.x = location[0];
    windowParams.y = y - offsetViewTop + offsetScreenTop;
    windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

    windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE// 不需獲取焦點
        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE// 不需接受觸摸事件
        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON// 保持設備常開,並保持亮度不變。
        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;// 窗口占滿整個屏幕,忽略周圍的裝飾邊框(例如狀態欄)。此窗口需考慮到裝飾邊框的內容。

    // windowParams.format = PixelFormat.TRANSLUCENT;// 默認爲不透明,這裏設成透明效果.
    windowParams.windowAnimations = 0;// 窗口所使用的動畫設置

    ImageView imageView = new ImageView(getContext());
    imageView.setImageBitmap(bm);
    windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
    windowManager.addView(imageView, windowParams);
    dragImageView = imageView;
  }


  /**
   * 拖動圖像在ListView範圍內, 不能超過邊界
   *
   * @param y
   */
  private void onDrag(int y) {
    int offsetTop = y - offsetViewTop; //頂部不能出界
    int offsetBottom = offsetViewTop + getHeight() - dragImageView.getHeight();  //下邊界
    if (dragImageView != null && offsetTop >= 0
        && offsetTop <= getChildAt(getChildCount() - 1).getTop()
        && y <= offsetBottom) {
      windowParams.alpha = 0.8f;// 透明度
      windowParams.y = y - offsetViewTop + offsetScreenTop;// 移動y值.//記得要加上dragOffset,windowManager計算的是整個屏幕.(標題欄和狀態欄都要算上)
      windowManager.updateViewLayout(dragImageView, windowParams);// 時時移動.
    }

    onChange(y);

    scrollListView(y);
  }

  /**
   * 同步滑動ListView
   * @param y
   */
  private void scrollListView(int y) {
    View view = getChildAt(dragPosition - getFirstVisiblePosition());
    int offsetY = mLastMotionY - y;

    if (y < getHeight() / 3 && y < mLastMotionY) { //listview向上滑
      setSelectionFromTop(dragPosition, offsetY + view.getTop());
    } else if (y > getHeight() / 3 * 2 && y > mLastMotionY) { //listview向下滑
      setSelectionFromTop(dragPosition, offsetY + view.getTop());
    }
    mLastMotionY = y;
  }


  /**
   * 同步改變item的位置
   * @param y
   */
  private void onChange(int y) {
    int currentPostion = pointToPosition(0, y);

    if (currentPostion == AdapterView.INVALID_POSITION) {
      currentPostion = dragPosition;
    }

    if (dragPosition != currentPostion) {
      DragAdapter adapter = (DragAdapter) getAdapter();
      adapter.change(dragPosition, currentPostion);
      swich(dragPosition, currentPostion);
    }

    dragPosition = currentPostion;
  }

  /***
   * 切換隱藏的位置
   */
  private void swich(int start, int end) {
    getChildAt(start - getFirstVisiblePosition()).setVisibility(View.VISIBLE);
    getChildAt(end - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
  }

  /**
   * 停止拖動,刪除影像
   */
  public void stopDrag() {
    if (dragImageView != null) {
      windowManager.removeView(dragImageView);
      dragImageView = null;
    }
  }
}

源碼:
https://github.com/brycegao/draglistview

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章