본문 바로가기
프로그래밍 놀이터/안드로이드, Java

Android ViewDragHelper Tutorial

by 돼지왕 왕돼지 2014. 6. 9.
반응형

 Android ViewDragHelper Tutorial



Android ViewDragHelper Tutorial, Callback, cancel, clampviewpositionvertical, communication channel, compat, computeScroll, CREATE, drag direction, drawerlayout, Edge, edge_left, edge_right, getTouchSlop, getViewVerticalDragRange, isViewUnder, layout, onattachedtowindow, onedgedragstarted, onedgetouched, onInterceptTouchEvent, onViewPositionChanged, onViewReleased, Parent, postInvalidateOnAnimation, processTouchEvent, requestLayout, scroller, setedgetrackingenabled, settlecapturedviewat, shouldInterceptTouchEvent, smoothSlideViewTo, source, static factory method, support-v4, trycaptureview, tutorial, vdh, VelocityTracker, ViewCompat, viewdraghelper, viewgroup, Youtube, 기본 setting


이 녀석은 Youtube 비디오가 우하단으로 축소되는 것의 구현에 사용된 녀석이다.


ViewDragHelper ( 이하 VDH ) 는 다음과 같은 특징을 가지고 있다.


- ViewDragHelper.Callback 은 parent view 와 VDH 간의 communication channel 이다.

- VDH instance 를 만들기 위해서는 static factory method 를 이용하면 된다.

- Drag direction 은 설정 가능하다.

- View 가 없어도 drag detection 이 가능하다.


VDH 는 support-v4 library 에 있다.

VDH 는 VelocityTracker 나 Scroller 를 사용하여 구현되어 있다.

Source 코드를 읽어보면 VDH 를 더 잘 사용할 수 있다.


출처 : http://blog.denevell.org/android-viewdraghelper-example-tutorial.html


< 기본 Setting 코드 >

@Override

protected void onAttachedToWindow() {

    super.onAttachedToWindow();

    mDragHelper = ViewDragHelper.create( this, 1.0f /* sensitivity */, mMyVDHCallback  );

    ...

}


private ViewDragHelper.Callback mMyVDHCallback = new ViewDragHelper.Callback() {


    @Override

    public boolean tryCaptureView(View arg0, int pointerId) {

       return true;  // dragged view. return true means this view is drag target.

    }


    @Override

    public int clampViewPositionVertical(View child, int top, int dy) {

       return top; // vertical drag return new top position. 

    }


    @Override

    public int getViewVerticalDragRange(View child) {

       return parent.getMeasuredHeight()-child.getMeasuredHeight(); 

       // smoothSlideViewTo 나 settleCapturedViewAt 에서 scroll duration, velocity 계산 등에 쓰인다.

       // touch slop 을 계산할때도 사용된다.

    }


    @Override

    public void onViewReleased(View releasedChild, float xvel, float yvel) {

       super.onViewReleased(releasedChild, xvel, yvel);

       if( yvel > 0 ) {

         mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), parent.getMeasuredHeight()-releasedChild.getMeasuredHeight());

       } 

       else {

         mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), 0);

       }

       invalidate();

    }


    // mDragHelper.setEdgeTrackingEnabled( ViewDragHelper.EDGE_LEFT );

    // edge 를 track 해서 onEdgeTouched 와 onEdgeDragStarted 가 호출된다.

    // DrawerLayout에서는 onEdgeTouched 와 onEdgeDragStarted 를 통해서 dragging 을 검출한다.

});


// settleCapturedViewAt 을 호출했을 때 animation 이 계속 되도록 하려면, 

// computeScroll 에서 아래와 같은 코드가 작성되어야 한다.

@Override

public void computeScroll() {

    super.computeScroll();

    if(mDragHelper.continueSettling(true)) {

       ViewCompat.postInvalidateOnAnimation(this);

    }

}


출처 : http://flavienlaurent.com/blog/2013/08/28/each-navigation-drawer-hides-a-viewdraghelper/


아래 예제는 Youtube 의 구현을 비슷하게 흉내낸 것이다.



< example code >

package com.example.test2;


import android.content.Context;

import android.support.v4.view.MotionEventCompat;

import android.support.v4.view.ViewCompat;

import android.support.v4.widget.ViewDragHelper;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.View;

import android.view.ViewGroup;


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);

}

}






<example xml code>

<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.test2.YoutubeLayout

        android:id="@+id/youtubeLayout"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:orientation="vertical"

        android:visibility="visible" >


        <TextView

            android:id="@+id/viewHeader"

            android:layout_width="match_parent"

            android:layout_height="128dp"

            android:background="#AD78CC"

            android:fontFamily="sans-serif-thin"

            android:gravity="center"

            android:tag="text"

            android:text="Drag Me!"

            android:textColor="@android:color/white"

            android:textSize="25sp" />


        <TextView

            android:id="@+id/viewDesc"

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            android:background="#FF00FF"

            android:gravity="center"

            android:tag="desc"

            android:text="Loreum Loreum"

            android:textColor="@android:color/white"

            android:textSize="35sp" />

    </com.example.test2.YoutubeLayout>


</FrameLayout>







반응형

댓글