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 |