ViewDragHelper에 대해 기억해야할 몇가지
public class DragLayout extends LinearLayout {
private final ViewDragHelper mDragHelper;
private View mDragView;
public DragLayout(Context context) {
  this(context, null);
}
public DragLayout(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
}
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
}
ViewGroup을 확장한 LinerLayout 초기화
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
}
ViewDragHelper 생성자를 통해 초기화 한다. 1.0f는 드래그 시작시 민감도이다.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  final int action = MotionEventCompat.getActionMasked(ev);
  if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
      mDragHelper.cancel();
      return false;
  }
  return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
  mDragHelper.processTouchEvent(ev);
  return true;
}
가장중요한것은 onInterceptTouchEvent와 onTouchEvent에서 ViewDragHelper를 호출하도록 구현하는 것이다. 여기까지 해줌으로써 ViewDragHelper Callback동작을 위해 구성했다.
수평드래그를 하는 예제를 하나 구현 해보자. ViewDragHelper.Callback를 implement를 해서 clampViewPositionHorizontal를 구현하면 된다. ViewGroup내 ChildView가 좌우위치가 변경되길 원하는 시점에 Callback된다. 
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
  Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);
  final int leftBound = getPaddingLeft();
  final int rightBound = getWidth() - mDragView.getWidth();
  final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
  return newLeft;
}
변경될 위치를 return한다. 이런식으로 clampViewPositionVertical을 통해서 수직드래그도 구현가능하다.
 
특정 ChildView만 그래그하게 하고 싶다면 tryCaptureView를 구현해 특정뷰일때만 return true를 하면된다. 또한 가장자리에서 드래그를 원한다면 setEdgeTrackingEnabled()를 통해 쉽게 처리 가능하다.  
 
그럼 YouTube 앱의 재생 레이아웃을 구성해보자.
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <ListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:tag="list"
            />
    <com.example.vdh.YoutubeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/youtubeLayout"
            android:orientation="vertical"
            android:visibility="visible">
        <TextView
                android:id="@+id/viewHeader"
                android:layout_width="match_parent"
                android:layout_height="128dp"
                android:fontFamily="sans-serif-thin"
                android:textSize="25sp"
                android:tag="text"
                android:gravity="center"
                android:textColor="@android:color/white"
                android:background="#AD78CC"/>
        <TextView
                android:id="@+id/viewDesc"
                android:tag="desc"
                android:textSize="35sp"
                android:gravity="center"
                android:text="Loreum Loreum"
                android:textColor="@android:color/white"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#FF00FF"/>
    </com.example.vdh.YoutubeLayout>
</FrameLayout>
하단에 ListView를 덮은 후 그위에 ViewDragHelper를 구현한 View 를 얻는 레이아웃이다.
public class YoutubeLayout extends ViewGroup {
private final ViewDragHelper mDragHelper;
private View mHeaderView;
private View mDescView;
private float mInitialMotionX;
private float mInitialMotionY;
private int mDragRange;
private int mTop;
private float mDragOffset;
public YoutubeLayout(Context context) {
  this(context, null);
}
public YoutubeLayout(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
}
@Override
protected void onFinishInflate() {
    mHeaderView = findViewById(R.id.viewHeader);
    mDescView = findViewById(R.id.viewDesc);
}
public YoutubeLayout(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());
}
public void maximize() {
    smoothSlideTo(0f);
}
boolean smoothSlideTo(float slideOffset) {
    final int topBound = getPaddingTop();
    int y = (int) (topBound + slideOffset * mDragRange);
    if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {
        ViewCompat.postInvalidateOnAnimation(this);
        return true;
    }
    return false;
}
private class DragHelperCallback extends ViewDragHelper.Callback {
  @Override
  public boolean tryCaptureView(View child, int pointerId) {
        return child == mHeaderView;
  }
    @Override
  public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
      mTop = top;
      mDragOffset = (float) top / mDragRange;
        mHeaderView.setPivotX(mHeaderView.getWidth());
        mHeaderView.setPivotY(mHeaderView.getHeight());
        mHeaderView.setScaleX(1 - mDragOffset / 2);
        mHeaderView.setScaleY(1 - mDragOffset / 2);
        mDescView.setAlpha(1 - mDragOffset);
        requestLayout();
  }
  @Override
  public void onViewReleased(View releasedChild, float xvel, float yvel) {
      int top = getPaddingTop();
      if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {
          top += mDragRange;
      }
      mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
  }
  @Override
  public int getViewVerticalDragRange(View child) {
      return mDragRange;
  }
  @Override
  public int clampViewPositionVertical(View child, int top, int dy) {
      final int topBound = getPaddingTop();
      final int bottomBound = getHeight() - mHeaderView.getHeight() - mHeaderView.getPaddingBottom();
      final int newTop = Math.min(Math.max(top, topBound), bottomBound);
      return newTop;
  }
}
@Override
public void computeScroll() {
  if (mDragHelper.continueSettling(true)) {
      ViewCompat.postInvalidateOnAnimation(this);
  }
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  final int action = MotionEventCompat.getActionMasked(ev);
  if (( action != MotionEvent.ACTION_DOWN)) {
      mDragHelper.cancel();
      return super.onInterceptTouchEvent(ev);
  }
  if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
      mDragHelper.cancel();
      return false;
  }
  final float x = ev.getX();
  final float y = ev.getY();
  boolean interceptTap = false;
  switch (action) {
      case MotionEvent.ACTION_DOWN: {
          mInitialMotionX = x;
          mInitialMotionY = y;
            interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
          break;
      }
      case MotionEvent.ACTION_MOVE: {
          final float adx = Math.abs(x - mInitialMotionX);
          final float ady = Math.abs(y - mInitialMotionY);
          final int slop = mDragHelper.getTouchSlop();
          if (ady > slop && adx > ady) {
              mDragHelper.cancel();
              return false;
          }
      }
  }
  return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
  mDragHelper.processTouchEvent(ev);
  final int action = ev.getAction();
    final float x = ev.getX();
    final float y = ev.getY();
    boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
    switch (action & MotionEventCompat.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN: {
          mInitialMotionX = x;
          mInitialMotionY = y;
          break;
      }
      case MotionEvent.ACTION_UP: {
          final float dx = x - mInitialMotionX;
          final float dy = y - mInitialMotionY;
          final int slop = mDragHelper.getTouchSlop();
          if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {
              if (mDragOffset == 0) {
                  smoothSlideTo(1f);
              } else {
                  smoothSlideTo(0f);
              }
          }
          break;
      }
  }
  return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);
}
private boolean isViewHit(View view, int x, int y) {
    int[] viewLocation = new int[2];
    view.getLocationOnScreen(viewLocation);
    int[] parentLocation = new int[2];
    this.getLocationOnScreen(parentLocation);
    int screenX = parentLocation[0] + x;
    int screenY = parentLocation[1] + y;
    return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
            screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
    int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
            resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
  mDragRange = getHeight() - mHeaderView.getHeight();
    mHeaderView.layout(
            0,
            mTop,
            r,
            mTop + mHeaderView.getMeasuredHeight());
    mDescView.layout(
            0,
            mTop + mHeaderView.getMeasuredHeight(),
            r,
            mTop  + b);
}
 
이처럼 ViewDragHelper는 View의 그래그를 좀더 쉽게 관리 할 수있기때문에 앞으로 기존처럼 딱딱한 방식이 아닌 좀더 화려한 UX를 기대해본다.
'안드로이드 개발 > View' 카테고리의 다른 글
| Android 유연성 있는 ViewHolder Pattern (7) | 2013.09.11 | 
|---|---|
| 리스트뷰 퍼포먼스 팁 (4) | 2013.08.31 | 
| Flat디자인의 핵심 안드로이드 이미지 Blur 효과 내기 (2) | 2013.08.12 |