書(shū)中的示例代碼:github
1.Android的坐標(biāo)系是以左上角為頂點(diǎn),向右為x軸正方向,向下是y軸正方向。在觸控事件中通過(guò)getRawX()和getRawY()獲取Android坐標(biāo)系中的坐標(biāo)。在View中通過(guò)getLocationOnScreen(intlocation[])獲取。
2.視圖坐標(biāo)系描述的是子視圖在父視圖中的位置關(guān)系,原點(diǎn)為父視圖的右上角,x、y軸方向與Android坐標(biāo)系一致。觸控事件中通過(guò)getX(),getY()獲取。還可以通過(guò)getTop(),getLeft(),getBottom(),getRight()來(lái)獲取到父視圖的距離。
3.MotionEvent常用事件常量:
MotionEvent.ACTION_DOWN//單點(diǎn)觸摸按下動(dòng)作
MotionEvent.ACTION_UP//單點(diǎn)觸摸離開(kāi)動(dòng)作
MotionEvent.ACTION_MOVE//觸摸點(diǎn)移動(dòng)動(dòng)作
MotionEvent.ACTION_CANCEL//觸摸動(dòng)作取消
MotionEvent.ACTION_OUTSIDE//觸摸動(dòng)作超出邊界
MotionEvent.ACTION_POINTER_DOWN//多點(diǎn)觸摸按下動(dòng)作
MotionEvent.ACTION_POINTER_UP//多點(diǎn)離開(kāi)動(dòng)作
4.實(shí)現(xiàn)滑動(dòng)的七種方法:
-
layout()方法:
private int lastX;
private int lastY;
private int offsetX;
private int offsetY;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
offsetX = x - lastX;
offsetY = y - lastY;
layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
lastX = x;
lastY = y;
break;
}
return true;
}
-
offsetLeftAndRight()和offsetTopAndBottom()方法:
//替換上面的layout方法即可
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
-
LayoutParams方法:
//替換上面的layout方法即可
ViewGroup.MarginLayoutParams layoutParams=(MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin=getLeft()+offsetX;
layoutParams.topMargin=getTop()+offsetY;
setLayoutParams(layoutParams);
-
scrollTo和scrollBy:
//scrollTo和scrollBy移動(dòng)的是view的內(nèi)容而不是view本身
//如果在viewgroup中使用就是移動(dòng)所有子view。
View view=(View) getParent();
//scrollTo和scrollBy參考的坐標(biāo)系正好與視圖坐標(biāo)系相反,所以offset需為負(fù)
view.scrollBy(-offsetX, -offsetY);
- Scroller:
使用Scroller主要有三個(gè)步驟:
1.初始化Scroller對(duì)象,一般在view初始化的時(shí)候同時(shí)初始化scroller;
2.重寫(xiě)view的computeScroll方法,computeScroll方法是不會(huì)自動(dòng)調(diào)用的,只能通過(guò)invalidate來(lái)間接調(diào)用,實(shí)現(xiàn)循環(huán)獲取scrollX和scrollY的目的,當(dāng)移動(dòng)過(guò)程結(jié)束之后,Scroller.computeScrollOffset方法會(huì)返回false,從而中斷循環(huán);
3.調(diào)用Scroller.startScroll方法,將起始位置、偏移量以及移動(dòng)時(shí)間(可選)作為參數(shù)傳遞給startScroll方法。
這個(gè)例子中,要實(shí)現(xiàn)的是view跟著手指滑動(dòng) 松手后平滑移動(dòng)到原位置。
先初始化Scroller
mScroller=new Scroller(getContext());
然后重寫(xiě)View的computeScroll()方法
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
最后在onTouchEvent的MotionEvent.ACTION_UP時(shí)開(kāi)啟移動(dòng)
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
case MotionEvent.ACTION_UP:
// 手指離開(kāi)時(shí),執(zhí)行滑動(dòng)過(guò)程
View viewGroup = ((View) getParent());
mScroller.startScroll( viewGroup.getScrollX(), viewGroup.getScrollY(),
-viewGroup.getScrollX(), -viewGroup.getScrollY(),1000);
invalidate();
break;
}
return true;
}
Scroller的實(shí)現(xiàn)原理就是不斷調(diào)用scrollTo或者scrollBy。
- 屬性動(dòng)畫(huà)(以后章節(jié)會(huì)詳細(xì)介紹)
-
ViewDragHelper:
ViewDragHelper基本可以實(shí)現(xiàn)各種不同滑動(dòng)需求,但使用稍微復(fù)雜。
public class DragViewGroup extends FrameLayout {
private ViewDragHelper mViewDragHelper;
private View mMenuView, mMainView;
private int mWidth;
public DragViewGroup(Context context) {
super(context);
initView();
}
public DragViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = mMenuView.getMeasuredWidth();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//將觸摸事件傳遞給ViewDragHelper,此操作必不可少
mViewDragHelper.processTouchEvent(event);
return true;
}
private void initView() {
mViewDragHelper = ViewDragHelper.create(this, callback);
}
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
// 何時(shí)開(kāi)始檢測(cè)觸摸事件
@Override
public boolean tryCaptureView(View child, int pointerId) {
//如果當(dāng)前觸摸的child是mMainView時(shí)開(kāi)始檢測(cè)
return mMainView == child;
}
// 觸摸到View后回調(diào)
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
// 當(dāng)拖拽狀態(tài)改變,比如idle,dragging
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
// 當(dāng)位置改變的時(shí)候調(diào)用,常用與滑動(dòng)時(shí)更改scale等
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
// 處理垂直滑動(dòng)
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
// 處理水平滑動(dòng)
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
// 拖動(dòng)結(jié)束后調(diào)用
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
//手指抬起后緩慢移動(dòng)到指定位置
if (mMainView.getLeft() < 500) {
//關(guān)閉菜單,相當(dāng)于Scroller的startScroll方法
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
} else {
//打開(kāi)菜單
mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}