Android群英傳讀書筆記——Android Scroll分析
5.1 滑動效果是如何產生的
滑動的本質是改變View的座標
5.1.1 Android座標系
螢幕左上角為原點,向右為x軸正方形,向下為y軸正方向
系統提供了View.getLocationOnScreen(int location[])獲取View左上角在整個螢幕中的座標。View.getLocationInWindow(int location[])獲取View左上角相對於父佈局的位置。詳見https://blog.csdn.net/ouyang_peng/article/details/46902957。
獲取各種XY座標圖示
5.1.2 檢視座標系
以父檢視的左上角座標為原點的相對座標系,通過getX()getY()獲取相對座標
5.1.3 觸控事件
觸控事件的不同型別
//MotionEvent類中封裝的事件常量 //單點觸控按下動作 public static final int ACTION_DOWN=0; //單點觸控離開動作 public static final int ACTION_UP=1; //觸控點移動操作 public static final int ACTION_MOVE=2; //觸控動作取消 public static final int ACTION_CANCLE=3; //觸控動作超出邊界 public static final int ACTION_OUTSIDE=4; //多點觸控按下動作 public static final int ACTION_POINTER_DOWN=5; //多點離開動作 public static final int ACTION_POINTER_UP=6;
通常通過在onTouchEvent(MotionEvent ev)中使用ev.getAction()來獲取觸控事件型別
@Override
public boolean onTouchEvent(MotionEvent ev){
switch(ev.getAction()){
case ACTION_DOWN:
...
break;
case ACTION_UP:
...
break;
}
return true;
}
Android獲取座標值,距離的方法。
1、View提供的方法:getTop,getBottom,getLeft,getRight
2、MotionEvent提供的方法:getX,getY(獲取相對座標),getRawX,getRawY(獲取絕對座標)
5.2 實現滑動的7種方法
滑動的基本思想是觸控View時,記下座標,移動後,記下座標,算出位移,移動View。
我們在LinearLayout(ViewGroup)中方一個View
5.2.1 layout方法
ViewGroup.setOnTouchListener((event)->{
int x=(int)event.getX();
int y=(int)event.getY();
switch(event.getAction){
case ACTION_DOWN:
lastX=x;
lastY=y;
case ACTION_MOVE:
int offsetX=x-lastX;
int offsetY=y=lastY;
//在當前left,right,top,bottom增加偏移量
layout(getLeft()+offsetX,
getTop()+offsetY,
getRight()+offsetX
getBottom()+offsetY);
break;
}
return true;
});
同樣,可以使用getRawX和getRawY計算偏移量。
5.2.2 offsetLeftAndRight()和offsetTopAndBottom()
此方法為系統提供的上下左右偏移API,引數為XY偏移量
//同時對Left和Right偏移
offsetLeftAndRight(offsetX);
//同時對Top和Bottom偏移
offsetTopandBottom(offsetY);
5.2.3 LayoutParams
LayoutParams儲存了一個View的佈局引數,可以通過修改LayoutParams動態的修改View的位置
LinearLayout.LayoutParams params=(LinearLayout.LayoutParams)getLayoutParams();
params.leftMargin=getLeft()+offsetX();
params.topMargin=getTop()+offsetY();
setLayoutParams(params);
需要注意,獲取LayoutParams,需指定父佈局的型別(如LinearLayout,RelativeLayout),不然獲取不到
也可以通過MarginLayoutParams修改座標,原理相同,需要指定ViewGroup。
ViewGroup.MarginLayoutParams params=(ViewGroup.MarginLayoutParams)getLayoutParams();
params.leftMargin=getLeft()+offsetX;
params.rightMargin=getTop()+offsetY;
setParams(params);
5.2.4 scrollTo()和scrollBy()
scrollTo(x,y)移動到指定座標,scrollBy(dx,dy)移動偏移量。在ViewGroup中使用,移動的是子View,在View中使用,移動的是內容,如TextView的文字,ImageView的影象。
scrollby(dx,dy)的移動方式相當於固定canvas,移動螢幕玻璃。所以比如dx=-5,就是畫布不動,螢幕玻璃左移5,其實就相當於玻璃不動,畫布向右移5。所以scrollby用法如下:
int offsetX=x-lastX;
int offsetY=y-lastY;
((View)getParent()).scrollby(-offsetX,-offsetY);
5.2.5 Scroller
Scroller與scrollTo與scrollBy相比,實現了平滑移動的效果,而不是瞬間移動。實現原理是在ACTION_MOVE中不斷移動微笑的偏移量,獲得平滑移動的效果。
例:手指滑動時View跟著滑動,手指鬆開時View返回左上角
//1、初始化Scroller
mScroller=new Scroller();
//2、重寫computeScroll()方法,系統繪製View的時候會在draw()方法中呼叫該方法
@Override
public void computeScroll(){
super.computeScroll();
if(mScroller.computeScrollOffset()){
((View)getParent).scrollerTo(mScroll.getCurrX,mScroller.getCurrY);
//通過重繪不斷呼叫computeScroll()
invalidate();
}
}
//3、startScroll
//public void startScroll(int x,inty,int dx,int dy,int duration);//duration類似於動畫持續時長
//public void startScroll(int x,inty,int dx,int dy);
case MotionEvent.ACTION_UP:
//手指離開時,返回左上角
View viewGroup=((View)getParent());
mScroller.startScroll(viewGroup.getScrollX(),
viewGroup.getScrollY(),
-viewGroup.getScrollX(),
-viewGroup.getScrollY()
);
invalidate();
break;
computeScrollOffset()判斷是否滑動完成。只能在computeScroll中獲取XY,但computeScroll是不會自動呼叫的。只能通過
invalidate()->draw()->computeScroll()來呼叫。
5.2.6 屬性動畫
(挖坑,相減動畫章節)
5.2.7 ViewDragHelper
Google在其support庫中提供了DrawerLayout和SlidingPaneLayout兩個佈局來幫助開發者實現側邊欄。佈局背後有一個強大的類——ViewDragHelper。(drug 拖拽)
例:實現仿QQ側邊欄,拖拽超過一定距離,側滑顯示選單。
1、初始化ViewDrugHelper
ViewDrugHelper通常定義在ViewGroup內部,使用靜態工廠初始化。
//第一個引數是ViewGroup,用來監聽View,第二個引數是callback回撥,是邏輯核心
mViewDrugHelper=ViewDrugHelper.create(this,callback);
2、攔截事件
重寫攔截事件,將事件傳給ViewDrugHelper處理。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
return mViewDrugHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev){
//將觸控事件傳給ViewDrugHelper,此操作必不可少
mViewDrugHelper.processTouchEvent(ev);
return true;
}
3、處理computeScroll()
ViewDrugHelper內部是通過Scroller實現滑動的
@Override
public coid computeScroll(){
if(mViewDrugHelper.continueSetting(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
4、處理回撥Callback
private ViewDragHelper.Callback callback=new ViewDragHelper.Callback(){
//控制那個View可以被移動
@Override
public boolean tryCaptureView(View child,int pointId){
//觸控的是mainView開始滑動,觸控的是menuView不滑動
return mMainView==child;
}
//第二個引數為child移動的距離,第三個引數表示比較前一次的增量
@Override
public int clampViewPositionVertical(View child ,int top,int dy){
return 0;
}
@Override
public int clampViewPositionHorizontal(View child ,int left,int dx){
return left;
}
//拖拽結束後呼叫
@Override
public void onViewReleased(View releaseChild,int xvel,int yvel){
super.onViewReleased(releaseChild,xvel,yvel);
//手指離開後緩慢移動到指定位置
if(mMainView.getLeft()<500){
//關閉選單
//相當於scroller的startScroll方法
mViewDragHelper.smoothSlideViewTo(mMainView,0,0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}else{
//開啟選單
mViewDragHelper.smoothSlideViewTo(mMainView,300,0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
重寫onFinishInflate()和onSizeChanged()
@Override
protected void onFinishInflated(){
super.onFinishInflated();
mMainView=getChildAt(0);
mMenuView=getChildAt(1);
}
@Override
protected void onSizeChanged(int w,int h,int oldw,int oldh){
super.onSizeChanged(w,h,pldw,oldh);
mWidth=mMenuView.getMeasureWidth();
}