Android Scroll 滑動分析

導(dǎo)語

滑動算是Android比較常用的效果了抬闯,滑動的操作具有很好的用戶體驗性拓颓。

主要內(nèi)容

  • 滑動效果是如何產(chǎn)生的
  • 實現(xiàn)滑動的七種常用方法

具體內(nèi)容

滑動效果是如何產(chǎn)生的

滑動一個View的本質(zhì)其實就是移動一個View,改變其當前所在的位置语婴,它的原理和動畫效果十分的相似,就是通過不斷的改變View的坐標來實現(xiàn)這一效果录粱,動態(tài)且不斷的改變View的坐標腻格,從而實現(xiàn)View跟隨用戶觸摸滑動而滑動。

但是在講解滑動效果之前啥繁,需要先了解一下Android中窗口坐標體系和屏幕的觸控事件——MotionEvent菜职。

Android坐標系

在物理學上,要描述一個物體的運動旗闽,就必須選定一個參考系酬核,所謂滑動,正是相對于參考系的運動适室,在Android嫡意,系統(tǒng)將屏幕最左上角的頂點作為Android坐標系的原點,從這個點向右是X軸正方向捣辆,從這個點向下是Y軸正方向蔬螟,如下圖所示。

Android坐標系

系統(tǒng)提供了getLocationOnScreen(intlocation[])來獲取Android坐標中的位置汽畴,即該視圖左上角Android的坐標旧巾,另外耸序,在觸摸事件中使用getRawX(),getRawY()方法來獲取坐標同樣是Android坐標系中的坐標。

視圖坐標系

Android中除了上面所說的這種坐標系之外們還有一個視圖坐標系鲁猩,他描述了子視圖在父視圖的位置關(guān)系坎怪,這兩個坐標系并不復(fù)雜也不矛盾,他們的作用是相輔相成的廓握,與Android坐標系類似搅窿,視圖坐標系同樣的以原點向右為X正方向,以原點向下為Y方法隙券,只不過在視圖坐標系中男应,原點不再是Android坐標系中的屏幕左上角,而是以父視圖左上角為坐標原點是尔,如下圖所示殉了。

視圖坐標系

在觸控事件中通過getX,getY來獲取的坐標就是視圖坐標中的坐標。

觸控事件——MotionEvent

觸控事件MotionEvent在用戶的交互中拟枚,占著舉足輕重的位置薪铜,學好觸控事件是掌握后續(xù)內(nèi)容的基礎(chǔ),首先恩溅,我們來看看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_CANCEL = 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 event)方法中通過event.getAction()來獲取觸摸事件的類型蜒滩,并使用switch來判斷,這個代碼模塊是固定的奶稠。

@Override
public boolean onTouchEvent(MotionEvent event) {
    //獲取當前輸入點的X,Y坐標(視圖坐標)
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //處理輸入的按下動作
            break;
        case MotionEvent.ACTION_MOVE:
            //處理輸入的移動動作
            break;
        case MotionEvent.ACTION_UP:
            //處理輸入的離開動作
            break;
    }
    return true;
}

在不涉及多點觸控的前提下俯艰,通常可以使用以上代碼來完成觸摸事件的監(jiān)聽锌订,不過這里只是一個代碼模塊竹握,后面我們會講具體的邏輯的。

Android獲取坐標的方法

在Android中辆飘,系統(tǒng)提供了非常多的方法來獲取坐標值啦辐,相對距離等,方法豐富固然好蜈项,但也給初學者帶來了很多的困擾芹关,不知道在什么情況下使用下圖總結(jié)人一下一些常用的API。

獲取坐標值的各種方法

這些方法可以分成兩個類別:

  • View提供的獲取坐標方法:
    • getTop():獲取到的是View自身的頂部到其父布局頂部的距離紧卒。
    • getLeft():獲取到的是View自身的左邊到其父布局左邊的距離侥衬。
    • getRight():獲取到的是View自身的右邊到其父布局左邊的距離。
    • getBottom():獲取到的是View自身的底部到其父布局頂部的距離。
  • MotionEvent提供的方法:
    • getX():獲取點擊事件距離控件左邊的距離轴总,即視圖坐標贬媒。
    • getY():獲取點擊事件距離控件頂部的距離,即視圖坐標肘习。
    • getRawX:獲取點擊事件整個屏幕左邊的距離,即絕對坐標坡倔。
    • getRawY:獲取點擊事件整個屏幕頂部的距離漂佩,即絕對坐標。

實現(xiàn)滑動的七種方法

當了解了Android坐標系和觸控事件之后罪塔,我們再來看一看如何使用系統(tǒng)提供的API來實現(xiàn)動態(tài)的修改一個View的坐標投蝉,即滑動效果,而不管采用哪種方式征堪,其實現(xiàn)的思路基本上是一樣的瘩缆,當觸摸View的時候,系統(tǒng)幾下View的坐標佃蚜,從而獲得到相對于之前坐標的偏移量庸娱,并通過偏移量來修改View的坐標,這樣不斷的重復(fù)就實現(xiàn)了滑動的過程谐算。

下面通過實例來看看Android中該如何實現(xiàn)滑動效果熟尉。定義一個View,并置于一個LinearLayout中洲脂,實現(xiàn)一個簡單的布局斤儿,代碼如下所示。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.lgl.scrollviewdemo.DragView
        android:layout_width="100dp"
        android:layout_height="100dp" />
</RelativeLayout>

默認的顯示樣子如下圖所示恐锦。

示例布局
layout方法

我們都知道往果,在View的繪制上,會調(diào)用onLayout()方法來設(shè)置顯示的位置一铅,同樣可以修改View的left陕贮,top,right馅闽,bottom四個屬性來控制View的坐標飘蚯,與前面提供的模板代碼一樣,每次調(diào)用onTouchEvent()的時候福也,我們來獲取點的坐標局骤,這里的邏輯很清楚,代碼如下所示暴凑。

 //觸摸事件
private int lastX = 0;
private int lastY = 0;

@Override
public boolean onTouchEvent(MotionEvent event) {
    int rawX = (int) event.getRawX();
    int rawY = (int) event.getRawY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //記錄觸摸點的坐標
            lastX = rawX;
            lastY = rawY;
            break;
        case MotionEvent.ACTION_MOVE:
            //計算偏移量
            int officeX = rawX - lastX;
            int officeY = rawY - lastY;
            //在當前的left,top,right,bottom基礎(chǔ)上加上偏移量
            layout(getLeft() + officeX, getTop() + officeY, getRight() + officeX, getBottom() + officeY);
            //重新設(shè)置初始值
            lastX = rawX;
            lastY = rawY;
            break;
        case MotionEvent.ACTION_UP:
            //處理輸入的離開動作
            break;
    }
    return true;
}

這里我們就可以移動這個View了峦甩。

offsetLeftAndRight()與offsetTopAndBottom()

這個方法相當于系統(tǒng)提供的一個對左右,上下移動的封裝,當計算出偏移量的時候凯傲,只需要使用如下的代碼就可以完成View的重新布局犬辰,效果和使用Layout()方法是一樣的。

//同時對左右偏移
offsetLeftAndRight(officeX);
//同時對上下偏移
offsetTopAndBottom(officeY);
LayoutParams

LayoutParams保留了一個View的布局參數(shù)冰单,因此可以在程序中幌缝,通過改變LayoutParams來動態(tài)改變一個布局的位置參數(shù),從而改變View位置的效果诫欠,我們可以很方便的在程序中使用getLayoutParams()來獲取一個View的LayoutParams涵卵,當然,在計算偏移量的方法和Layout方法中計算offset是一樣的荒叼,當獲取到偏移量之后轿偎,可以通過setLayoutParams來改變LayoutParams,代碼如下所示被廓。

LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft()+officeX;
                layoutParams.topMargin = getTop()+officeY;
                setLayoutParams(layoutParams);

不過這里需要注意的是坏晦,通過getLayoutParams()獲取layoutParams時,需要根據(jù)View所在的跟布局的類型來設(shè)置不同的類型嫁乘,比如View放在LinearLayout里那就是 LinearLayout.LayoutParams昆婿,比如在RelativeLayout里就是 RelativeLayout.LayoutParams,不然系統(tǒng)是無法獲取到layoutParams的亦渗。

在通過一個layoutParams來改變一個View的位置時挖诸,通常改變的是這個view的Margin屬性,所以除了使用布局的layoutParams屬性外法精,還需要 ViewGroup.MarginLayoutParams來實現(xiàn)這樣的功能多律。

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft()+officeX;
                layoutParams.topMargin = getTop()+officeY;
                setLayoutParams(layoutParams);

我們可以發(fā)現(xiàn),用ViewGroup更好搂蜓,都不用去管父布局是什么狼荞。

scrollTo與scrollBy

在一個View當中,系統(tǒng)提供了scrollTo與scrollBy這兩種方式來實現(xiàn)移動一個View的位置帮碰,這兩種方法的區(qū)別也很好理解相味,to和by,scrollTo(x,y)表示移動到一個具體的點殉挽,scrollBy(dx,dy)表示移動的增量丰涉。

int officeX = rawX - lastX;
int officeY = rawY - lastY;
scrollBy(officeX,officeY);

但是,當我們拖動View的時候斯碌,你會發(fā)現(xiàn)View并沒有移動一死,難道我們寫錯了?方法沒有寫錯傻唾,View也的確移動了投慈,但是他移動的并不是我們想要的東西承耿,他只是移動了view的content,即讓View的內(nèi)容移動了伪煤,如果用ViewGroup使用to和by的話加袋,那所有的子View都將移動,要是在View中使用的話抱既,职烧,那么移動的就是View的內(nèi)容了,我們舉個例子防泵,比如TextView阳堕,content就是他的文本,ImageView择克,drawable就是對象。

相信通過上面的分析前普,你也應(yīng)該知道為什么不能再View里面使用這個兩個方法來拖動這個view了肚邢,那么我們就該View所在的ViewGroup中使用scrollBy方法來移動這個view。

((View)getParent()).scrollBy(officeX,officeY);

但是拭卿,當再次拖動View的時候骡湖,你會發(fā)現(xiàn)View雖然移動了,但卻在亂動峻厚,并不是我們想要的跟隨觸摸點的移動而移動响蕴,這里需要先了解一下視圖移動的一些知識,大家在理解這個問題的時候惠桃,不妨想象一下手機是一個中空的蓋板浦夷,蓋板下面是一個巨大的畫布,也就是我們想要顯示的視圖辜王,當把這個蓋板蓋在畫布的某處時劈狐,透過中間空著的矩形,我們看見了手機屏幕上顯示的視圖呐馆,而畫布上其他的視圖肥缔,則被蓋板蓋住了無法看見,我們的視圖和這個例子事項相似汹来,我們沒有看見視圖续膳,但并不代表它不存在,有可能只是在屏幕外面而已收班,當調(diào)用scrollBy的方法時坟岔,可以想象外面的蓋板在移動,這么說比較抽象闺阱,我們來看一下具體的例子炮车。

理解scrollBy

上圖,中間的矩形相當于屏幕,即可是區(qū)域瘦穆,后面的content相當于畫布纪隙,代表視圖,大家可以看到扛或,只有視圖的中間部分目前是可視的绵咱,其他部分都不可見,可見區(qū)域中設(shè)置一個button,他的坐標是(20.10)熙兔,下面我們使用scrollBy方法來進行移動后如圖悲伶。

移動之后的可視區(qū)域

我們會發(fā)現(xiàn),雖然設(shè)置scrollBy(20.10)住涉,偏移量均為XY的正方向麸锉,但是屏幕的可視區(qū)域,Button卻向反方向移動了舆声,這就是參考系選擇的不同花沉,而產(chǎn)生的不同效果。

通過上面的分析媳握,可以發(fā)現(xiàn)碱屁,我們將scrollBy的參數(shù)dx,dy設(shè)置成正數(shù),那么content將向坐標軸負方向移動蛾找,反之娩脾,則正方向。

int officeX = rawX - lastX;
int officeY = rawY - lastY;
scrollBy(-officeX,-officeY);

再去試驗一下打毛,大家可以發(fā)現(xiàn)柿赊,效果和前面的幾種方法相同了,類似的幻枉,我們使用scrollTo也是可以實現(xiàn)的闹瞧。

Scroller

scrollTo與scrollBy是一瞬間完成的事情,很突兀展辞,而Scroller就可以實現(xiàn)平滑的效果奥邮。Scroller的原理與前面使用scrollTo與scrollBy的方法原理是一樣的。代碼如下所示罗珍。

  • 初始化scroller :

首先洽腺,通過他的構(gòu)造方法來創(chuàng)建一個scroller對象。

//初始化mScroller
mScroller = new Scroller(context);
  • 重寫computeScroll覆旱,實現(xiàn)模擬滑動:

下面我們需要重寫computeScroll這個方法蘸朋,他是使用Scroller的核心,系統(tǒng)在繪制View的同時扣唱,會在onDraw()方法中調(diào)用這個方法藕坯,這個方法實際上就是使用了ScrollTo()方法再結(jié)合Scroller對象团南,幫助獲取到當前的滾動值,我們可以通過不斷的瞬息移動一個小的距離來實現(xiàn)整體上的平滑移動效果炼彪,通常情況下吐根,computeScroll的代碼可以利用標準的寫法。

/**
 * 模擬滑動
 */
@Override
public void computeScroll() {
    super.computeScroll();

    //判斷Scroller是否執(zhí)行完畢
    if (mScroller.computeScrollOffset()) {
        ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
    }
    //通過重繪來不斷調(diào)用computeScroll
    invalidate();
}

Scroller類提供了computeScrollOffset()來判斷是否完成了整個頁面的滑動辐马,同時也提供了getCurrX()拷橘,getCurrY()來獲取當前滑動坐標,上面唯一要注意的就是invalidate()了喜爷,因為只能在computeScroll中獲得模擬過程中的scrollX和scrollY坐標冗疮,但computeScroll方法是不會自動調(diào)用的,只能通過invalidate→OnDraw→computeScroll()來簡介調(diào)用檩帐,所以需要這個invalidate术幔,而當模擬過程結(jié)束的時候,computeScrollOffset返回的是false湃密,從而結(jié)束循環(huán)特愿。

  • startScroll開啟模擬過程:

最后,萬事俱備只欠東風了勾缭,我們需要使用平滑移動事件,使用Scroller類的startScroll()方法來開啟平滑過程目养,startScroll有兩個重載的方法俩由。

public void startScroll(int startX, int startY, int dx, int dy, int duration) {}
public void startScroll(int startX, int startY, int dx, int dy) {}

可以看到他們的區(qū)別分別是具有指定的持續(xù)時長,而另一個每有癌蚁,與在動畫中的設(shè)置時間是一樣的幻梯,而其他四個坐標,就是起始和偏移量努释,通過上述的步驟碘梢,就可以完成一個平移的效果了。

下面我們回到實例伐蒂,在構(gòu)造分鐘初始化Scroller對象煞躬,然后重寫computeScroll方法,最后需要監(jiān)聽手指離開屏幕的事件逸邦,并在該事件之后調(diào)用startScroll()完成平移恩沛,所以我們在ACTION_UP中。

case MotionEvent.ACTION_UP:
    //處理輸入的離開動作
    View view = ((View)getParent());
    mScroller.startScroll(view.getScrollX(),view.getScrollY(),view.getScrollX(),view.getScrollY());
    invalidate();
    break;
屬性動畫

這一內(nèi)容比較多缕减,放到以后再詳細記錄雷客。

ViewDragHelper

Google在其support庫中為我們提供了一個DrawerLayout和SlidingPaneLayout兩個布局來幫助開發(fā)者實現(xiàn)側(cè)劃效果,這兩個布局桥狡,大大的方便了我們自己創(chuàng)建自己的滑動布局搅裙,然而皱卓,這兩個強大的布局背后,卻隱藏著一個鮮為人知部逮,卻功能強大的類——ViewDragHelper娜汁,通過ViewDragHelper,基本可以實現(xiàn)各種不同的側(cè)滑甥啄,拖放需求存炮,因此這個方法也是各種滑動解決方案的終極絕招。

ViewDragHelper雖然很強大蜈漓,但是使用也是本章節(jié)最復(fù)雜 的穆桂,我們需要了解ViewDragHelper的基本使用方法的基礎(chǔ)上,通過不斷的練習去掌握它融虽,我們這里就實現(xiàn)一個 QQ滑動側(cè)邊欄的布局享完,我么來看看具體怎么實現(xiàn)的。

  • 初始化ViewDragHelper:

首先有额,自認是初始化ViewDragHelper般又,ViewDragHelper通常定義在一個ViewGroup中,通過其靜態(tài)方法初始化巍佑。

mViewDragHelper = ViewDragHelper.create(this,callback);

他的第一個參數(shù)是要監(jiān)聽的View茴迁,第二個參數(shù)是一個Callback回調(diào),這個回調(diào)是整個業(yè)務(wù)的核心萤衰。

  • 攔截事件:

接下來堕义,要重寫攔截事件,將事件傳遞給ViewDragHelper進行處理脆栋。

//事件攔截
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
//觸摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
    //將觸摸事件傳遞給ViewDragHelper
    mViewDragHelper.processTouchEvent(event);
    return true;
}
  • 處理computeScroll():

沒錯倦卖,使用ViewDragHelper也是需要重寫computeScroll的,因為ViewDragHelper內(nèi)部也是通過Scroller來實現(xiàn)平移的椿争,我們可以這樣使用怕膛。

@Override
public void computeScroll() {
    if(mViewDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}
  • 處理回調(diào)Cakkback:

我們可以直接的new出來。

//側(cè)滑回調(diào)
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
    //何時開始觸摸
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        //如果當前觸摸的child是mMainView開始檢測
        return mMainView == child;
    }
    //處理水平滑動
    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        return left;
    }
    //處理垂直滑動
    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return 0;
    }
    //拖動結(jié)束后調(diào)用
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        super.onViewReleased(releasedChild, xvel, yvel);
        //手指抬起后緩慢的移動到指定位置
        if (mMainView.getLeft() < 500) {
            //關(guān)閉菜單
            mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
            ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
        } else {
            //打開菜單
            mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
            ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
        }
    }
};
  • 定義ViewGroup的onFinishInflate()方法

在onFinishInflate()方法中秦踪,按順序?qū)⒆覸iew分別定義為MenuView和MainView,并在onSizeChanged()方法中獲得View的寬度椅邓。如果你根據(jù)View的寬度來滑動后的效果希坚,就可以使用這個值來判斷个束。

//XML加載組建后回調(diào)
@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    mMenuView = getChildAt(0);
    mMainView = getChildAt(1);
}
//組件大小改變時回調(diào)
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = mMenuView.getMeasuredWidth();
}
  • 最后通過整個ViewDragHelper來實現(xiàn)側(cè)滑的代碼如下:
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

/**
 * QQ側(cè)滑
 * Created by lgl on 16/3/22.
 */
public class DragViewGroup extends FrameLayout {

    //側(cè)滑類
    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();
    }

    //初始化數(shù)據(jù)
    private void initView() {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    //XML加載組建后回調(diào)
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMenuView = getChildAt(0);
        mMainView = getChildAt(1);
    }


    //組件大小改變時回調(diào)
    @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;
    }

    //側(cè)滑回調(diào)
    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        //何時開始觸摸
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            //如果當前觸摸的child是mMainView開始檢測
            return mMainView == child;
        }

        //處理水平滑動
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }

        //處理垂直滑動
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return 0;
        }

        //拖動結(jié)束后調(diào)用
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            //手指抬起后緩慢的移動到指定位置
            if (mMainView.getLeft() < 500) {
                //關(guān)閉菜單
                mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
                ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
            } else {
                //打開菜單
//                mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);  // 側(cè)邊欄寬度為300
                mViewDragHelper.smoothSlideViewTo(mMainView, mWidth, 0);  // 側(cè)邊欄寬度為布局定義寬度
                ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
            }
        }
    };

    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}
  • 最后在xml布局中使用
<?xml version="1.0" encoding="utf-8"?>
<com.example.cc.myapplication.DragViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:windowSoftInputMode="adjustPan|stateHidden">


    <LinearLayout
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:background="@android:color/black"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="24sp"
            android:textColor="@android:color/white"
            android:text="側(cè)邊欄"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/darker_gray"
        android:orientation="vertical">

        <View
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:textSize="24sp"
            android:textColor="@android:color/white"
            android:text="主界面"/>

    </LinearLayout>

</com.example.cc.myapplication.DragViewGroup>

這只是簡單模擬QQ側(cè)滑菜單這個功能。ViewDragHelper還有很多強大的功能涉馁。

  • ViewDragHelper.Callback的其它監(jiān)聽事件

系統(tǒng)定義了大量的監(jiān)聽事件幫助我們處理各種事件爱致,下面就列舉幾個帮坚。

事件 作用
onViewCaptured() 在用戶觸摸到View后回調(diào)试和。
onViewDragStateChanged() 在拖拽狀態(tài)改變時回調(diào)(idle阅悍,draggin等狀態(tài))。
onViewPositionChanged() 在位置改變時回調(diào)(常用于滑動時更改scale進行縮放等效果)悦昵。

更多內(nèi)容戳這里(整理好的各種文集)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市棋凳,隨后出現(xiàn)的幾起案子剩岳,更是在濱河造成了極大的恐慌拍棕,老刑警劉巖骄噪,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件链蕊,死亡現(xiàn)場離奇詭異滔韵,居然都是意外死亡奏属,警方通過查閱死者的電腦和手機囱皿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來齿兔,“玉大人,你說我怎么就攤上這事医寿【钢龋” “怎么了沟突?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我耘沼,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任气筋,我火速辦了婚禮宠默,結(jié)果婚禮上抹沪,老公的妹妹穿的比我還像新娘融欧。我一直安慰自己噪馏,他們只是感情好,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著虏肾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疮装,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天刷袍,我揣著相機與錄音,去河邊找鬼雷酪。 笑死,一個胖子當著我的面吹牛吩跋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轧粟,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茂翔,長吁一口氣:“原來是場噩夢啊……” “哼珊燎!你這毒婦竟也來了晚吞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挺智,沒想到半個月后窗宦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迫摔,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年擂啥,在試婚紗的時候發(fā)現(xiàn)自己被綠了屋吨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡绷杜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懊缺,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弃秆,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一盼樟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锈至,春花似錦晨缴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至们拙,卻和暖如春稍途,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砚婆。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工械拍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留突勇,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓坷虑,卻偏偏與公主長得像甲馋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子迄损,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容