1. 程式人生 > >Android群英傳讀書筆記——Android Scroll分析

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