編輯:關於Android編程
前言: 通過上一篇的為何說Android ViewDragHelper是神器 (一)中我們簡單了解了ViewDragHelper的用法,然後實現了一個“view隨手指滑動而滑動”的效果,代碼很簡單,但是VDH中處理的邏輯卻很多很多,不得不說VDH真的是神器,要我們自己寫的話得寫一段時間了,接下來我們繼續往下研究研究VDH,加油吧!騷年(^__^) !!!
以下demo內容大致參考鴻陽博客中的Android ViewDragHelper解析 一文,陽神一直是我崇拜的一個偶像(^__^) 。
ViewDragHelper還能做以下的一些操作:
邊界檢測、加速度檢測(eg:DrawerLayout邊界觸發拉出)
移動到某個指定的位置(eg:點擊Button,展開/關閉Drawerlayout)
回調Drag Release(eg:DrawerLayout部分,手指抬起,自動展開/收縮)
那麼我們接下來對我們最基本的例子進行改造,包含上述的幾個操作。
我們再創建兩個view,id叫autobackview(拖動後手指一抬起返回初始位置),edgeview(滑動邊緣開始滑動的view):
ids.xml:
text_layout.xml:
我們改改DragView:
package com.cisetech.demo;
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
/**
* author:yinqingy
* date:2016-11-06 13:49
* blog:http://blog.csdn.net/vv_bug
* desc:
*/
public class DragView extends LinearLayout{
private ViewDragHelper mDragger;
private View mEdgeView,mAutoBackView;
private Point mAutoBackOriginPos=new Point();
public DragView(Context context) {
super(context);
init();
}
public DragView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mDragger=ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
//當id為edgeview的時候,不允許其滑動
return child.getId()==R.id.draggedview||child.getId()==R.id.autobackview;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
/**
* 當手指在邊緣拖動的時候回調此方法
* edgeFlags分為left、top、right、bottom
*/
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
//當在邊緣滑動的時候
mDragger.captureChildView(mEdgeView, pointerId);
}
//手指釋放的時候回調
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//mAutoBackView手指釋放時可以自動回去
if (releasedChild.getId()==R.id.autobackview) {
mDragger.settleCapturedViewAt(mAutoBackOriginPos.x, mAutoBackOriginPos.y);
invalidate();
}
}
});
//一定要加上這句代碼,不然就checkNewEdgeDrag就不會進入判斷了
mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragger.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragger.processTouchEvent(event);
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//獲取autoBackView的初始位置
mAutoBackOriginPos.x = mAutoBackView.getLeft();
mAutoBackOriginPos.y = mAutoBackView.getTop();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mEdgeView=findViewById(R.id.edgeview);
mAutoBackView=findViewById(R.id.autobackview);
}
@Override
public void computeScroll() {
if(mDragger.continueSettling(true))
invalidate();
}
}
我們來分析下代碼:
代碼中都有注釋,我就不一一解釋了,先解釋下邊緣滑動的代碼:
首先:
@Override
public boolean tryCaptureView(View child, int pointerId) {
//當id為edgeview的時候,不允許其滑動
return child.getId()==R.id.draggedview||child.getId()==R.id.autobackview;
}
不允許edgeview直接滑動,所以返回的是false,
然後:
/**
* 當手指在邊緣拖動的時候回調此方法
* edgeFlags分為left、top、right、bottom
*/
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
//當在邊緣滑動的時候
mDragger.captureChildView(mEdgeView, pointerId);
}
在當手指觸碰到ViewGroup的邊緣的時候,調用了mDragger.captureChildView方法,
最後:
//一定要加上這句代碼,不然就checkNewEdgeDrag就不會進入判斷了
mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
很少的代碼,我們的view就可以實現邊緣滑動了(不了解邊緣滑動的可以想象下側滑菜單(^__^) 嘻嘻……),那麼我們進入到VDH源碼中看看為什麼可以邊緣滑動?
首先看看onEdgeDragStarted在哪調用的?
private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
int dragsStarted = 0;
if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
dragsStarted |= EDGE_LEFT;
}
if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
dragsStarted |= EDGE_TOP;
}
if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
dragsStarted |= EDGE_RIGHT;
}
if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
dragsStarted |= EDGE_BOTTOM;
}
if (dragsStarted != 0) {
mEdgeDragsInProgress[pointerId] |= dragsStarted;
mCallback.onEdgeDragStarted(dragsStarted, pointerId);
}
}
我們可以看到是在一個叫reportNewEdgeDrags的方法中調用的,那麼reportNewEdgeDrags又是在哪調用的呢?
在VDH中的processTouchEvent方法中我們看到:
case MotionEvent.ACTION_MOVE: {
if (mDragState == STATE_DRAGGING) {
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(mActivePointerId)) break;
final int index = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(index);
final float y = ev.getY(index);
final int idx = (int) (x - mLastMotionX[mActivePointerId]);
final int idy = (int) (y - mLastMotionY[mActivePointerId]);
dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
saveLastMotion(ev);
} else {
// Check to see if any pointer is now over a draggable view.
final int pointerCount = ev.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int pointerId = ev.getPointerId(i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = ev.getX(i);
final float y = ev.getY(i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag.
break;
}
final View toCapture = findTopChildUnder((int) x, (int) y);
if (checkTouchSlop(toCapture, dx, dy)
&& tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
}
break;
}
當mDragState != STATE_DRAGGING的時候會調用reportNewEdgeDrags方法,在VDH中只有當mDragState ==STATE_DRAGGING的時候才能對view進行拖動,那麼我們看看mDragState 在什麼地方被置成STATE_DRAGGING標記的?
/**
* Capture a specific child view for dragging within the parent. The callback will be notified
* but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to
* capture this view.
*
* @param childView Child view to capture
* @param activePointerId ID of the pointer that is dragging the captured child view
*/
public void captureChildView(View childView, int activePointerId) {
if (childView.getParent() != mParentView) {
throw new IllegalArgumentException("captureChildView: parameter must be a descendant "
+ "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
}
mCapturedView = childView;
mActivePointerId = activePointerId;
mCallback.onViewCaptured(childView, activePointerId);
setDragState(STATE_DRAGGING);
}
在captureChildView中,我們很清晰的看到setDragState(STATE_DRAGGING);這麼一段代碼,當mDragg為STATE_DRAGGING狀態的時候,當進入到processTouchEvent方法的ACTION_MOVE時,就會走:
if (mDragState == STATE_DRAGGING) {
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(mActivePointerId)) break;
final int index = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(index);
final float y = ev.getY(index);
final int idx = (int) (x - mLastMotionX[mActivePointerId]);
final int idy = (int) (y - mLastMotionY[mActivePointerId]);
dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
saveLastMotion(ev);
}
調了dragTo方法就可以拖動了,dragTo在前一篇博客中有提及,我就不再說明了,到此,邊界拖動的代碼已經解析完畢了。
接下來看看松手自動返回的代碼:
}
//手指釋放的時候回調
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//mAutoBackView手指釋放時可以自動回去
if (releasedChild.getId()==R.id.autobackview) {
mDragger.settleCapturedViewAt(mAutoBackOriginPos.x, mAutoBackOriginPos.y);
invalidate();
}
}
});
在手指松開的時候會調用onViewReleased方法,然後我們調用了VDH的settleCapturedViewAt方法,我們看看settleCapturedViewAt內部:
private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
final int startLeft = mCapturedView.getLeft();
final int startTop = mCapturedView.getTop();
final int dx = finalLeft - startLeft;
final int dy = finalTop - startTop;
if (dx == 0 && dy == 0) {
// Nothing to do. Send callbacks, be done.
mScroller.abortAnimation();
setDragState(STATE_IDLE);
return false;
}
final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
mScroller.startScroll(startLeft, startTop, dx, dy, duration);
setDragState(STATE_SETTLING);
return true;
}
其內部主要調用了forceSettleCapturedViewAt方法,說到底還是調用了mScroller.startScroll(startLeft, startTop, dx, dy, duration);方法,Scroller的用法不懂的自己去腦補啊,還是很重要的一個組件的(^__^) 嘻嘻……既然有Scroller,我們就要重寫View的computeScroll方法,所以我們在DragView中有重寫:
@Override
public void computeScroll() {
if(mDragger.continueSettling(true))
invalidate();
}
}
其實其continueSettling的內部想必知道Scroller的童鞋應該猜得出干了什麼:
boolean keepGoing = mScroller.computeScrollOffset();
到此手指松開回到原來位置的代碼也分析完畢了。
細心的童鞋可以發現,我們做測試用的View都是TextView,因為TextView本身就不具備可點擊性,如果換成本身具有可點擊性的Button,那麼還會有一樣的效果嗎?
我們試試:
當我們換成Button後,我們運行發現,只有邊界移動的view可以移動,其它兩個view不管怎麼滑動都沒效果哦,為什麼呢?
主要是因為,如果子View不消耗事件,那麼整個手勢(DOWN-MOVE*-UP)都是直接進入onTouchEvent,在onTouchEvent的DOWN的時候就確定了captureView。如果消耗事件,那麼就會先走onInterceptTouchEvent方法,判斷是否可以捕獲,而在判斷的過程中會去判斷另外兩個回調的方法:getViewHorizontalDragRange和getViewVerticalDragRange,只有這兩個方法返回大於0的值才能正常的捕獲。
所以,如果你用Button測試,或者給TextView添加了clickable = true ,都記得重寫下面這兩個方法:
@Override
public int getViewHorizontalDragRange(View child){
return getMeasuredWidth()-child.getMeasuredWidth();
}
@Override
public int getViewVerticalDragRange(View child){
return getMeasuredHeight()-child.getMeasuredHeight();
}
方法的返回值應當是該childView橫向或者縱向的移動的范圍,當前如果只需要一個方向移動,可以只復寫一個。
這個時候你肯定又會問“為什麼重寫這兩個方法就可以了呢?”
(涉及到事件分發的知識,不懂的童鞋還是得腦補一下哈(^__^) 嘻嘻……)我們來看看原因:
在VDH中的shouldInterceptTouchEvent方法中我們看到這麼一段代碼:
case MotionEvent.ACTION_MOVE: {
if (mInitialMotionX == null || mInitialMotionY == null) break;
// First to cross a touch slop over a draggable view wins. Also report edge drags.
final int pointerCount = ev.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int pointerId = ev.getPointerId(i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = ev.getX(i);
final float y = ev.getY(i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
final View toCapture = findTopChildUnder((int) x, (int) y);
final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
if (pastSlop) {
// check the callback's
// getView[Horizontal|Vertical]DragRange methods to know
// if you can move at all along an axis, then see if it
// would clamp to the same value. If you can't move at
// all in every dimension with a nonzero range, bail.
final int oldLeft = toCapture.getLeft();
final int targetLeft = oldLeft + (int) dx;
final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
targetLeft, (int) dx);
final int oldTop = toCapture.getTop();
final int targetTop = oldTop + (int) dy;
final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
(int) dy);
final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
toCapture);
final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
if ((horizontalDragRange == 0 || horizontalDragRange > 0
&& newLeft == oldLeft) && (verticalDragRange == 0
|| verticalDragRange > 0 && newTop == oldTop)) {
break;
}
}
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag
break;
}
if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
break;
}
代碼有點長,我們看重點,我們看到這麼一段代碼:
final View toCapture = findTopChildUnder((int) x, (int) y);
final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
if (pastSlop) {
// check the callback's
// getView[Horizontal|Vertical]DragRange methods to know
// if you can move at all along an axis, then see if it
// would clamp to the same value. If you can't move at
// all in every dimension with a nonzero range, bail.
final int oldLeft = toCapture.getLeft();
final int targetLeft = oldLeft + (int) dx;
final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
targetLeft, (int) dx);
final int oldTop = toCapture.getTop();
final int targetTop = oldTop + (int) dy;
final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
(int) dy);
final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
toCapture);
final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
if ((horizontalDragRange == 0 || horizontalDragRange > 0
&& newLeft == oldLeft) && (verticalDragRange == 0
|| verticalDragRange > 0 && newTop == oldTop)) {
break;
}
}
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag
break;
}
if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
當pastSlop為true的時候,才會去跑:
if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
當跑了tryCaptureViewForDrag的時候就會去走captureChildView方法:
*/
public void captureChildView(View childView, int activePointerId) {
if (childView.getParent() != mParentView) {
throw new IllegalArgumentException("captureChildView: parameter must be a descendant "
+ "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
}
mCapturedView = childView;
mActivePointerId = activePointerId;
mCallback.onViewCaptured(childView, activePointerId);
setDragState(STATE_DRAGGING);
}
而這個時候setDragState(STATE_DRAGGING);會給mDragState設置成STATE_DRAGGING,當設置成了STATE_DRAGGING,在shouldInterceptTouchEvent的最後會返回true:
return mDragState == STATE_DRAGGING;
當shouldInterceptTouchEvent返回true以後,我們自定義的ViewGroup中的onInterceptTouchEvent也就返回true了,因此直接攔截了子View的事件,所以接下來才會進ViewGoup的onTouchEvent方法,所以才可以滑動。
有點復雜的感覺額,但是如果很清晰的掌握了事件分發流程,還是很好理解的。
細心的童鞋會發現,我們的View拖動的邊界沒有限制,以至於都拖到ViewGroup外面去了,好吧,我就直接貼代碼了。
左右的邊界:
左邊為getPaddingLeft(),右邊為getWidth() - mDragView.getWidth() - getPaddingRight():
public int clampViewPositionHorizontal(View child, int left, int dx)
{
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - mDragView.getWidth() - getPaddingRight();
final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
return newLeft;
}
上下的的邊界: 上邊為getPaddingTop(),下邊為 getHeight() - child.getHeight() - getPaddingBottom():
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - child.getHeight() - getPaddingBottom();
final int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
好吧!到此,VDH的基本用法就介紹到這裡了,接下來會進入到VDH的實戰部分,有興趣的童鞋可以跟我一起進入VDH實戰部分哦!!!
最後附上demo的git鏈接:
https://github.com/913453448/SwipeBackLayout
Android仿支付寶支付從底部彈窗效果
我們再用支付寶支付的時候,會從底部彈上來一個對話框,讓我們選擇支付方式等等,今天我們就來慢慢實現這個功能效果圖實現主界面很簡單,就是一個按鈕,點擊後跳到支付詳情的Frag
MyHttpUtils一個非常好用的異步網絡請求框架
一、能做什麼你只需要傳url,JavaBean就可以在回調方法裡面得到想要的結果,你會發現你的代碼裡面沒有了子線程、沒有了handle,鏈式的變成使得代碼更加清晰。1.1
下載、編譯、運行android 7.1系統詳解(ubuntu 16.0.4)
Android 7的系統版本新增的很多的新功能,比如說任務處理功能,允許用戶雙擊“最近”按鈕去快速切換到自己上一次使用的應用程序中。同時,“最近”菜單中還有一個“清除全部
Android for work總結(下)
Afw流程演示Device OwnerL平台恢復出廠設置連接翻牆Wifi直到出現如下界面點擊”Set up work device”,輸入賬號和激