科普一下
- 2013年谷歌i/o大會(huì)上介紹了兩個(gè)新的layout: SlidingPaneLayout和DrawerLayout,現(xiàn)在這倆個(gè)類被廣泛的運(yùn)用,其實(shí)研究他們的源碼你會(huì)發(fā)現(xiàn)這兩個(gè)類都運(yùn)用了ViewDragHelper來(lái)處理拖動(dòng)。ViewDragHelper是Framework中不為人知卻非常有用的一個(gè)工具。ViewDragHelper解決了android中手勢(shì)處理過(guò)于復(fù)雜的問(wèn)題,在DrawerLayout出現(xiàn)之前,側(cè)滑菜單都是由第三方開(kāi)源代碼實(shí)現(xiàn)的,其中著名的當(dāng)屬M(fèi)enuDrawer,MenuDrawer重寫onTouchEvent方法來(lái)實(shí)現(xiàn)側(cè)滑效果,代碼量很大,實(shí)現(xiàn)邏輯也需要很大的耐心才能看懂。如果每個(gè)開(kāi)發(fā)人員都從這么原始的步奏開(kāi)始做起,那對(duì)于安卓生態(tài)是相當(dāng)不利的。所以說(shuō)ViewDragHelper等的出現(xiàn)反映了安卓開(kāi)發(fā)框架已經(jīng)開(kāi)始向成熟的方向邁進(jìn)。引用了網(wǎng)上的片段,其實(shí)說(shuō)了那么多,ViewDragHelper就如名字說(shuō)的一樣,一個(gè)拖拽View的幫助類。只要我們這些普通開(kāi)發(fā)者使用這個(gè)類,就可以輕松實(shí)現(xiàn)以前寫了一大堆代碼才實(shí)現(xiàn)的效果,代碼更簡(jiǎn)潔,更優(yōu)雅。
ViewDragHelper基本用法
- 創(chuàng)建ViewDragHelper,建議在ViewGroup內(nèi)部創(chuàng)建使用,而不是在外面使用。
//第二個(gè)參數(shù)設(shè)置滑動(dòng)靈敏度
mViewDragHelper = ViewDragHelper.create(this, 1.0f,mCallback);
- 看看mCallback回調(diào)接口ViewDragHelper.Callback,到底有什么重要的方法,直接上源碼。
public static abstract class Callback {
/**
* 當(dāng)ViewDragHelper狀態(tài)發(fā)生變化時(shí)回調(diào)(IDLE,DRAGGING,SETTING[自動(dòng)滾動(dòng)時(shí)])
* @see #STATE_IDLE
* @see #STATE_DRAGGING
* @see #STATE_SETTLING
*/
public void onViewDragStateChanged(int state) {}
/**
* 當(dāng)captureView的位置發(fā)生改變時(shí)回調(diào)
* @param changedView View whose position changed
* @param left New X coordinate of the left edge of the view
* @param top New Y coordinate of the top edge of the view
* @param dx Change in X position from the last call
* @param dy Change in Y position from the last call
*/
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
/**
*當(dāng)CaptureView被捕獲時(shí)回調(diào)
* @param capturedChild Child view that was captured
* @param activePointerId Pointer id tracking the child capture
*/
public void onViewCaptured(View capturedChild, int activePointerId) {}
/**
*手指釋放的時(shí)候回調(diào)
*
* @param releasedChild The captured child view now being released
* @param xvel X velocity of the pointer as it left the screen in pixels per second.
* @param yvel Y velocity of the pointer as it left the screen in pixels per second.
*/
public void onViewReleased(View releasedChild, float xvel, float yvel) {}
/**
* 當(dāng)觸摸到邊界時(shí)回調(diào)。
*
* @param edgeFlags A combination of edge flags describing the edge(s) currently touched
* @param pointerId ID of the pointer touching the described edge(s)
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void onEdgeTouched(int edgeFlags, int pointerId) {}
/**
*true的時(shí)候會(huì)鎖住當(dāng)前的邊界,false則unLock。
*
* @param edgeFlags A combination of edge flags describing the edge(s) locked
* @return true to lock the edge, false to leave it unlocked
*/
public boolean onEdgeLock(int edgeFlags) {
return false;
}
/**
*
*在邊界拖動(dòng)時(shí)狀態(tài)回調(diào)
* @param edgeFlags A combination of edge flags describing the edge(s) dragged
* @param pointerId ID of the pointer touching the described edge(s)
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
/**
* 改變同一個(gè)坐標(biāo)(x,y)去尋找captureView位置的方法
*
* @param index the ordered position to query for
* @return index of the view that should be ordered at position <code>index</code>
*/
public int getOrderedChildIndex(int index) {
return index;
}
/**
*
*水平方向,子View要是消耗事件,就要重寫此方法返回大于1的數(shù)。
* @param child Child view to check
* @return range of horizontal motion in pixels
*/
public int getViewHorizontalDragRange(View child) {
return 0;
}
/**
*垂直方向,子View要是消耗事件,就要重寫此方法返回大于1的數(shù)。
*
* @param child Child view to check
* @return range of vertical motion in pixels
*/
public int getViewVerticalDragRange(View child) {
return 0;
}
/**
*tryCaptureView如何返回ture則表示可以捕獲該view即是哪個(gè)View可以滑動(dòng),哪個(gè)不可以滑動(dòng),在這個(gè)方
*法里面控制
* @param child Child the user is attempting to capture
* @param pointerId ID of the pointer attempting the capture
* @return true if capture should be allowed, false otherwise
*/
public abstract boolean tryCaptureView(View child, int pointerId);
/**
* 水平滑動(dòng)的邊界處理
*
*
* @param child Child view being dragged
* @param left Attempted motion along the X axis
* @param dx Proposed change in position for left
* @return The new clamped position for left
*/
public int clampViewPositionHorizontal(View child, int left, int dx) {
return 0;
}
/**
* 垂直滑動(dòng)的邊界處理
*
*
* @param child Child view being dragged
* @param top Attempted motion along the Y axis
* @param dy Proposed change in position for top
* @return The new clamped position for top
*/
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
}
大概了解所有的回調(diào)方法后,回歸主題。
一個(gè)上拉抽屜布局,我腦子里首先想到的就是這樣一個(gè)東東,圖1。

既然是這樣,新建一個(gè)PullUpDragLayout直接繼承ViewGroup,代碼如下:
/**
* @作 用:上拉的抽屜布局
* @創(chuàng) 建 人: linguoding 郵箱:linggoudingg@gmail.com
* @日 期: 2016年11月16日 10:32
*/
public class PullUpDragLayout extends ViewGroup {
private ViewDragHelper mViewDragHelper;//拖拽幫助類
private View mBottomView;//底部?jī)?nèi)容View
private View mContentView;//內(nèi)容View
private int mBottomBorderHeigth = 20;//底部邊界凸出的高度
public PullUpDragLayout(Context context) {
super(context);
}
public PullUpDragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullUpDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
}
}
PullUpDragLayout 有兩個(gè)子View,這兩個(gè)子View怎么創(chuàng)建?可以java代碼new,可以在xml布局里面寫,也可以自定義屬性,引用屬性得到。new 的話就pass掉,這里直接使用后面兩種方式。
<declare-styleable name="PullUpDragLayout">
<attr name="PullUpDrag_BottomView" format="reference"/><!--底部View-->
<attr name="PullUpDrag_ContentView" format="reference"/><!--內(nèi)容View-->
<attr name="PullUpDrag_BottomBorderHeigth" format="reference|integer|dimension"/><!--邊界高度-->
</declare-styleable>
有了屬性,接下來(lái)就可以實(shí)例化mBottomView , mContentView ,還有得到邊界高度 mBottomBorderHeigth的值。代碼如下:
/**
* @作 用:上拉的抽屜布局
* @創(chuàng) 建 人: linguoding 郵箱:linggoudingg@gmail.com
* @日 期: 2016年11月16日 10:32
*/
public class PullUpDragLayout extends ViewGroup {
private ViewDragHelper mViewDragHelper;//拖拽幫助類
private View mBottomView;//底部?jī)?nèi)容View
private View mContentView;//內(nèi)容View
private LayoutInflater mLayoutInflater;
private int mBottomBorderHeigth = 20;//底部邊界凸出的高度
public PullUpDragLayout(Context context) {
this(context, null, 0);
}
public PullUpDragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullUpDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
initCustomAttrs(context, attrs);
}
private void init(Context context) {
mLayoutInflater = LayoutInflater.from(context);
}
/**
* 初始化自定義屬性,并實(shí)例化子View
* @param context
* @param attrs
*/
private void initCustomAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PullUpDragLayout);
if (typedArray != null) {
if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_ContentView)) {
inflateContentView(typedArray.getResourceId(R.styleable.PullUpDragLayout_PullUpDrag_ContentView, 0));
}
if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_BottomView)) {
inflateBottomView(typedArray.getResourceId(R.styleable.PullUpDragLayout_PullUpDrag_BottomView, 0));
}
if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_BottomBorderHeigth)) {
mBottomBorderHeigth = (int) typedArray.getDimension(R.styleable.PullUpDragLayout_PullUpDrag_BottomBorderHeigth, 20);
}
typedArray.recycle();
}
}
private void inflateContentView(int resourceId) {
mContentView = mLayoutInflater.inflate(resourceId, this, true);
}
private void inflateBottomView(int resourceId) {
mBottomView = mLayoutInflater.inflate(resourceId, this, true);
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
}
}
實(shí)例化了子View后,PullUpDragLayout 既然是直接繼承ViewGroup,那肯定要計(jì)算子View,還有子View在PullUpDragLayout怎么布局。那就要重寫onMeasure(int widthMeasureSpec, int heightMeasureSpec),還有onLayout(boolean changed, int left, int top, int right, int bottom)方法。
- onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法計(jì)算子View的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mContentView = getChildAt(0);
mBottomView = getChildAt(1);
measureChild(mBottomView, widthMeasureSpec, heightMeasureSpec);
int bottomViewHeight = mBottomView.getMeasuredHeight();
measureChild(mContentView, widthMeasureSpec, heightMeasureSpec);
int contentHeight = mContentView.getMeasuredHeight();
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), bottomViewHeight +contentHeight + getPaddingBottom() + getPaddingTop());
}
- onLayout(boolean changed, int left, int top, int right, int bottom)方法排列子View的位置
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mContentView = getChildAt(0);
mBottomView = getChildAt(1);
mContentView.layout(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), mContentView.getMeasuredHeight());
mBottomView.layout(getPaddingLeft(), mContentView.getHeight() - mBottomBorderHeigth, getWidth() - getPaddingRight(), getMeasuredHeight() - mBottomBorderHeigth);
}
應(yīng)用ViewDragHelper實(shí)現(xiàn)上拉效果
到此,就實(shí)現(xiàn)上面圖1的靜態(tài)效果啦。想要底部可以上拉,或者點(diǎn)擊上拉到指定位置。那就要用到ViewDragHelper。在init()方法中創(chuàng)建ViewDragHelper.create(this, 1.0f,mCallback)。重寫onInterceptTouchEvent()、onTouchEvent,將event事件交給ViewDragHelper攔截及處理,代碼如下:
private void init(Context context) {
mLayoutInflater = LayoutInflater.from(context);
mViewDragHelper = ViewDragHelper.create(this, 1.0f, mCallback);
}
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mBottomView == child;
}
};
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
這樣底部抽屜就可以隨意滑動(dòng)啦,但是這樣還遠(yuǎn)遠(yuǎn)沒(méi)有達(dá)到我要的效果。我們要實(shí)現(xiàn)以下效果
- 指定方向滑動(dòng)
- 邊界檢測(cè)、加速度檢測(cè)
- 手指抬起,自動(dòng)展開(kāi)/收縮
- 點(diǎn)擊Button,展開(kāi)/關(guān)閉
- 監(jiān)聽(tīng)展開(kāi)/關(guān)閉,上拉過(guò)程發(fā)生改變時(shí)回調(diào)
慢慢來(lái),下面一個(gè)一個(gè)地去實(shí)現(xiàn)。
- 指定方向滑動(dòng)
控制水平方向不滑動(dòng),上下滑動(dòng)。重寫ViewDragHelper.Callback中的clampViewPositionHorizontal()方法,還有這個(gè)clampViewPositionVertical()。代碼如下:
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - mBottomView.getWidth() - leftBound;
final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
return newLeft;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int topBound = mContentView.getHeight() - mBottomView.getHeight();
int bottomBound = mContentView.getHeight() - mBottomBorderHeigth;
return Math.min(bottomBound, Math.max(top, topBound));
}
- 手指抬起,自動(dòng)展開(kāi)/收縮
重寫onViewReleased()方法:代碼如下:
//手指釋放的時(shí)候回調(diào)
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (releasedChild == mBottomView) {
if (releasedChild.getY() < mBoundTopY || yvel <= -1000) {
mViewDragHelper.settleCapturedViewAt(mAutoBackTopPos.x, mAutoBackTopPos.y);
isOpen = true;
if (mOnStateListener != null) mOnStateListener.open();
} else if (releasedChild.getY() >= mBoundTopY || yvel >= 1000) {
mViewDragHelper.settleCapturedViewAt(mAutoBackBottomPos.x, mAutoBackBottomPos.y);
isOpen = false;
if (mOnStateListener != null) mOnStateListener.close();
}
invalidate();
}
}
};
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) { invalidate();
}
}
- 點(diǎn)擊Button,展開(kāi)/關(guān)閉
/**
* 切換底部View
*/
public void toggleBottomView() {
if (isOpen) {
mViewDragHelper.smoothSlideViewTo(mBottomView, mAutoBackBottomPos.x, mAutoBackBottomPos.y);
if (mOnStateListener != null) mOnStateListener.close();
} else {
mViewDragHelper.smoothSlideViewTo(mBottomView, mAutoBackTopPos.x, mAutoBackTopPos.y);
if (mOnStateListener != null) mOnStateListener.open();
}
invalidate();
isOpen = !isOpen;
}
- 監(jiān)聽(tīng)展開(kāi)/關(guān)閉,上拉過(guò)程發(fā)生改變時(shí)回調(diào)
定義兩個(gè)接口
public void setOnStateListener(OnStateListener onStateListener) {
mOnStateListener = onStateListener;
}
public void setScrollChageListener(OnScrollChageListener scrollChageListener) {
mScrollChageListener = scrollChageListener;
}
public interface OnStateListener {
void open();
void close();
}
public interface OnScrollChageListener {
void onScrollChange(float rate);
}
重寫onViewPositionChanged 監(jiān)聽(tīng)位置的改變,計(jì)算上拉過(guò)程的比率值0-1。代碼如下:
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (changedView == mBottomView) {
float startPosition = mContentView.getHeight() - mBottomView.getHeight();
float endPosition = mContentView.getHeight() - mBottomBorderHeigth;
float totalLength = endPosition - startPosition;
float rate = 1 - ((top - startPosition) / totalLength);
if (mScrollChageListener != null) {
mScrollChageListener.onScrollChange(rate);
}
}
}
到此,整個(gè)過(guò)程就走一遍了。下面放上完整代碼:
/**
* @作 用:上拉的抽屜布局
* @創(chuàng) 建 人: linguoding 郵箱:linggoudingg@gmail.com
* @日 期: 2016年10月28日 14:13
*/
public class PullUpDragLayout extends ViewGroup {
private ViewDragHelper mViewDragHelper;//拖拽幫助類
private View mBottomView;//底部?jī)?nèi)容View
private View mContentView;//內(nèi)容View
LayoutInflater mLayoutInflater;
private int mBottomBorderHeigth = 20;//底部邊界凸出的高度
private Point mAutoBackBottomPos = new Point();
private Point mAutoBackTopPos = new Point();
private int mBoundTopY;
private boolean isOpen;
private OnStateListener mOnStateListener;
private OnScrollChageListener mScrollChageListener;
public void setOnStateListener(OnStateListener onStateListener) {
mOnStateListener = onStateListener;
}
public void setScrollChageListener(OnScrollChageListener scrollChageListener) {
mScrollChageListener = scrollChageListener;
}
public interface OnStateListener {
void open();
void close();
}
public interface OnScrollChageListener {
void onScrollChange(float rate);
}
public PullUpDragLayout(Context context) {
this(context, null, 0);
}
public PullUpDragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullUpDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
initCustomAttrs(context, attrs);
}
private void init(Context context) {
mLayoutInflater = LayoutInflater.from(context);
mViewDragHelper = ViewDragHelper.create(this, 1.0f,mCallback);
}
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mBottomView == child;
}
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth() - child.getMeasuredWidth();
}
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - mBottomView.getWidth() - leftBound;
final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
return newLeft;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int topBound = mContentView.getHeight() - mBottomView.getHeight();
int bottomBound = mContentView.getHeight() - mBottomBorderHeigth;
return Math.min(bottomBound, Math.max(top, topBound));
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (changedView == mBottomView) {
float startPosition = mContentView.getHeight() - mBottomView.getHeight();
float endPosition = mContentView.getHeight() - mBottomBorderHeigth;
float totalLength = endPosition - startPosition;
float rate = 1 - ((top - startPosition) / totalLength);
if (mScrollChageListener != null) {
mScrollChageListener.onScrollChange(rate);
}
}
}
//手指釋放的時(shí)候回調(diào)
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (releasedChild == mBottomView) {
if (releasedChild.getY() < mBoundTopY || yvel <= -1000) {
mViewDragHelper.settleCapturedViewAt(mAutoBackTopPos.x, mAutoBackTopPos.y);
isOpen = true;
if (mOnStateListener != null) mOnStateListener.open();
} else if (releasedChild.getY() >= mBoundTopY || yvel >= 1000) {
mViewDragHelper.settleCapturedViewAt(mAutoBackBottomPos.x, mAutoBackBottomPos.y);
isOpen = false;
if (mOnStateListener != null) mOnStateListener.close();
}
invalidate();
}
}
};
public boolean isOpen() {
return isOpen;
}
/**
* 切換底部View
*/
public void toggleBottomView() {
if (isOpen) {
mViewDragHelper.smoothSlideViewTo(mBottomView, mAutoBackBottomPos.x, mAutoBackBottomPos.y);
if (mOnStateListener != null) mOnStateListener.close();
} else {
mViewDragHelper.smoothSlideViewTo(mBottomView, mAutoBackTopPos.x, mAutoBackTopPos.y);
if (mOnStateListener != null) mOnStateListener.open();
}
invalidate();
isOpen = !isOpen;
}
private void initCustomAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PullUpDragLayout);
if (typedArray != null) {
if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_ContentView)) {
inflateContentView(typedArray.getResourceId(R.styleable.PullUpDragLayout_PullUpDrag_ContentView, 0));
}
if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_BottomView)) {
inflateBottomView(typedArray.getResourceId(R.styleable.PullUpDragLayout_PullUpDrag_BottomView, 0));
}
if (typedArray.hasValue(R.styleable.PullUpDragLayout_PullUpDrag_BottomBorderHeigth)) {
mBottomBorderHeigth = (int) typedArray.getDimension(R.styleable.PullUpDragLayout_PullUpDrag_BottomBorderHeigth, 20);
}
typedArray.recycle();
}
}
private void inflateContentView(int resourceId) {
mContentView = mLayoutInflater.inflate(resourceId, this, true);
}
private void inflateBottomView(int resourceId) {
mBottomView = mLayoutInflater.inflate(resourceId, this, true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mContentView = getChildAt(0);
mBottomView = getChildAt(1);
measureChild(mBottomView, widthMeasureSpec, heightMeasureSpec);
int bottomViewHeight = mBottomView.getMeasuredHeight();
measureChild(mContentView, widthMeasureSpec, heightMeasureSpec);
int contentHeight = mContentView.getMeasuredHeight();
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), bottomViewHeight + contentHeight + getPaddingBottom() + getPaddingTop());
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mContentView = getChildAt(0);
mBottomView = getChildAt(1);
mContentView.layout(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), mContentView.getMeasuredHeight());
mBottomView.layout(getPaddingLeft(), mContentView.getHeight() - mBottomBorderHeigth, getWidth() - getPaddingRight(), getMeasuredHeight() - mBottomBorderHeigth);
mAutoBackBottomPos.x = mBottomView.getLeft();
mAutoBackBottomPos.y = mBottomView.getTop();
mAutoBackTopPos.x = mBottomView.getLeft();
mAutoBackTopPos.y = mContentView.getHeight() - mBottomView.getHeight();
mBoundTopY = mContentView.getHeight() - mBottomView.getHeight() / 2;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
}
XML中使用:
<com.xxx.widget.PullUpDragLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/pull_up_drag_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:PullUpDrag_BottomBorderHeigth="30dp"
app:PullUpDrag_ContentView="@layout/pp_content_view"
app:PullUpDrag_BottomView="@layout/view_bottom"
>
<!--或者在這里,注意順序不能變 ContentView 再到 BottomView-->
</com.xxx.widget.PullUpDragLayout>
效果大概可以是這樣的:

展開(kāi)時(shí):

大概就是這樣的...............................