第五章 Android Scroll 分析

Android 群英傳筆記
第一章Android體系與系統(tǒng)架構
第二章 Android開發(fā)工具及技巧
第三章 Android控件架構與事件攔截機制
第四章 ListView 使用技巧
第五章 Android Scroll 分析
第六章 Android 繪圖機制與屏幕適配
第七章 Android 動畫機制與使用技巧
第八章 Activity與Activity調用棧分析
第九章 Android 系統(tǒng)信息與安全機制
第十章 Android性能優(yōu)化
本文出自:
http://www.reibang.com/u/a1251e598483

滑動效果是如何產生的

滑動一個View的本質其實就是移動一個View,改變其當前所在的位置,它的原理和動畫效果十分的相似,就是通過不斷的改變View的坐標來實現這一效果害捕,動態(tài)且不斷的改變View的坐標,從而實現View跟隨用戶觸摸滑動而滑動
但是在講解滑動效果之前贵白,需要先了解一下Android中窗口坐標體系和屏幕的觸控事件——MotionEvent

Android坐標系

在物理學上,要描述一個物體的運動,就必須選定一個參考系紊搪,所謂滑動卡儒,正是相對于參考系的運動田柔,在Android,系統(tǒng)將屏幕最左上角的頂點作為Android坐標系的原點骨望,從這個點向右是X軸正方向硬爆,從這個點向下是Y軸正方向,如圖


Android 坐標系

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

視圖坐標系

Android中除了上面所說的這種坐標系之外們還有一個視圖坐標系袜蚕,他描述了子視圖在父視圖的位置關系,這兩個坐標系并不復雜也不矛盾绢涡,他們的作用是相輔相成的牲剃,與Android坐標系類似,視圖坐標系同樣的以原點向右為X正方向垂寥,以原點向下為Y方法颠黎,只不過在視圖坐標系中另锋,原點不再是Android坐標系中的屏幕左上角,而是以父視圖左上角為坐標原點狭归,看圖

視圖坐標系

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

觸控事件——MotionEvent

觸控事件MotionEvent在用戶的交互中夭坪,占著舉足輕重的位置,學好觸控事件是掌握后續(xù)內容的基礎过椎,首先室梅,我們來看看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中,系統(tǒng)提供了非常多的方法來獲取坐標值举哟,相對距離等思劳,方法豐富固然好桩皿,但也給初學者帶來了很多的困擾雇逞,不知道在什么情況下使用下圖總結人一下一些常用的API

獲取坐標值的各種方法

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

View提供的獲取坐標方法
getTop():獲取到的是View自身的頂部到其父布局頂部的距離
getLeft():獲取到的是View自身的左邊到其父布局左邊的距離
getRight():獲取到的是View自身的右邊到其父布局右邊的距離
getBottom():獲取到的是View自身的底部到其父布局底部的距離

MotionEvent提供的方法
getX():獲取點擊事件距離控件左邊的距離粗悯,即視圖坐標
getY():獲取點擊事件距離控件頂部的距離魁兼,即視圖坐標
getRawX:獲取點擊事件整個屏幕左邊的距離斜做,即絕對坐標
getRawY:獲取點擊事件整個屏幕頂部的距離膘融,即絕對坐標

實現滑動的七種方法

當了解了Android坐標系和觸控事件之后坑夯,我們再來看一看如何使用系統(tǒng)提供的API來實現動態(tài)的修改一個View的坐標喂窟,即滑動效果森瘪,而不管采用哪種方式牡属,其實現的思路基本上是一樣的,當觸摸View的時候扼睬,系統(tǒng)幾下View的坐標逮栅,從而獲得到相對于之前坐標的偏移量,并通過偏移量來修改View的坐標窗宇,這樣不斷的重復就實現了滑動的過程措伐。

下面我們通過實例來看看Android中如何實現滑動的效果沒定義一個View,簡單的實現一個布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.younger.youngertest.MainActivity">

    <com.example.younger.youngertest.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@android:color/holo_orange_dark"
        >
    </com.example.younger.youngertest.MyView>
    
</LinearLayout>

默認的顯示樣子


示例布局
layout方法

我們都知道军俊,在View的繪制上侥加,會調用onLayout()方法來設置顯示的位置,同樣可以修改View的left,top,right,bottom四個屬性來控制View的坐標粪躬,與前面提供的模板代碼一樣担败,每次調用onTouchEvent()的時候昔穴,我們來獲取點的坐標,這里的邏輯很清楚,真的提前,我們來看看

    //觸摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        int lastX = 0;
        int lastY = 0;
        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基礎上加上偏移量
                layout(getLeft()+officeX,getTop()+officeY,getRight()+officeX,getBottom()+officeY);
                //重新設置初始值
                lastX = rawX;
                lastY = rawY;
                break;
            case MotionEvent.ACTION_UP:
                //處理輸入的離開動作
                break;
        }
        return true;
    }
offsetLeftAndRight()與offsetTopAndBottom()

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

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

LayoutParams

LayoutParams保留了一個View的布局參數,因此可以在程序中拓哺,通過改變LayoutParams來動態(tài)改變一個布局的位置參數勇垛,從而改變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時鸽凶,需要根據View所在的跟布局的類型來設置不同的類型,建峭,比如View放在LinearLayout里那就是 LinearLayout.LayoutParams玻侥,比如在RelativeLayout里就是 RelativeLayout.LayoutParams,不然系統(tǒng)是無法獲取到layoutParams的.

在通過一個layoutParams來改變一個View的位置時亿蒸,通常改變的是這個view的Margin屬性凑兰,所以除了使用布局的layoutParams屬性外,還需要 ViewGroup.MarginLayoutParams來實現這樣的功能

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

我們可以發(fā)現边锁,用ViewGroup更好姑食,都不用去管父布局是什么

scrollTo與scrollBy

在一個View當中,系統(tǒng)提供了scrollTo與scrollBy這兩種方式來實現移動一個View的位置茅坛,這兩種方法的區(qū)別也很好理解音半,to和by, scrollTo(x,y);表示移動到一個具體的 點贡蓖,scrollBy(dx,dy);表示移動的增量

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

但是曹鸠,當我們拖動View的時候,你會發(fā)現View并沒有移動斥铺,難道我們寫錯了彻桃?方法沒有寫錯,View也的確移動了晾蜘,但是他移動的并不是我們想要的東西邻眷,他只是移動了view的content眠屎,即讓View的內容移動了,如果用ViewGroup使用to和by的話肆饶,那所有的子View都將移動改衩,要是在View中使用的話,抖拴,那么移動的就是View的內容了燎字,我們舉個例子,比如TextView阿宅,content就是他的文本候衍,ImageView,drawable就是對象

相信通過上面的分析洒放,你也應該知道為什么不能再View里面使用這個兩個方法來拖動這個view了蛉鹿,那么我們就該View所在的ViewGroup中使用scrollBy方法來移動這個view

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

但是,當再次拖動View的時候往湿,你會發(fā)現View雖然移動了妖异,但卻在亂動,并不是我們想要的跟隨觸摸點的移動而移動领追,這里需要先了解一下視圖移動的一些知識他膳,大家在理解這個問題的時候,不妨想象一下手機是一個中空的蓋板绒窑,蓋板下面是一個巨大的畫布棕孙,也就是我們想要顯示的視圖,當把這個蓋板蓋在畫布的某處時些膨,透過中間空著的矩形蟀俊,我們看見了手機屏幕上顯示的視圖,而畫布上其他的視圖订雾,則被蓋板蓋住了無法看見肢预,我們的視圖和這個例子事項相似,我們沒有看見視圖洼哎,但并不代表它不存在烫映,有可能只是在屏幕外面而已,當調用scrollBy的方法時谱净,可以想象外面的蓋板在移動窑邦,這么說比較抽象,我們來看一下具體的例子


理解ScrollBy

上圖壕探,中間的矩形相當于屏幕冈钦,即可是區(qū)域,后面的content相當于畫布李请,代表視圖瞧筛,大家可以看到厉熟,只有視圖的中間部分目前是可視的,其他部分都不可見较幌,可見區(qū)域中設置一個button,他的坐標是(20.10)揍瑟,下面我們使用scrollBy方法來進行移動后如圖


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

我們會發(fā)現,雖然設置scrollBy(20.10)乍炉,偏移量均為XY的正方向绢片,但是屏幕的可視區(qū)域,Button卻向反方向移動了岛琼,這就是參考系選擇的不同底循,而產生的不同效果。

通過上面的分析槐瑞,可以發(fā)現熙涤,我們將scrollBy的參數dx,dy設置成正數,那么content將向坐標軸負方向移動困檩,反之祠挫,則正方向

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

可以發(fā)現,效果和前面的幾種方法相同了悼沿,類似的等舔,我們使用scrollTo也是可以實現的

Scroller

既然提到scrollTo與scrollBy,那就不得不提一下Scroller類了糟趾,Scroller和scrollTo與scrollBy十分的相似软瞎,有著千絲萬縷的關系,那么他們有什么具體的區(qū)別尼拉讯?要解答這個問題,首先我們來看一個小栗子鳖藕,假設要完成這樣的一個效果:點擊button,讓一個viewgroup的子View移動100像素魔慷,問題看似很簡單,只要使用scrollBy的方法就可以著恩,的確院尔,用這個方法確實可以,可是那都是一瞬間完成的事情喉誊,很突兀邀摆,而Scroller就可以實現平滑的效果,而不再是一瞬間的事情.

說道Scroller的原理伍茄,其實他與前面使用scrollTo與scrollBy的方法原理是一樣的栋盹,下面我們通過一個小栗子來演示一下

  • 初始化scroller

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

//初始化mScroller
mScroller = new Scroller(context);

  • 重寫computeScroll敷矫,實現模擬滑動

下面我們需要重寫computeScroll這個方法例获,他是使用Scroller的核心汉额,系統(tǒng)在繪制View的同時,會在onDraw()方法中調用這個方法榨汤,這個方法實際上就是使用了ScrollTo()方法再結合Scroller對象蠕搜,幫助獲取到當前的滾動值,我們可以通過不斷的瞬息移動一個小的距離來實現整體上的平滑移動效果收壕,通常情況下妓灌,computeScroll的代碼可以利用標準的寫法:

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

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

Scroller類提供了computeScrollOffset()來判斷是否完成了整個頁面的滑動,蜜宪,同時也提供了getCurrX()虫埂,getCurrY()來獲取當前滑動坐標,上面唯一要注意的就是invalidate()了端壳,因為只能在computeScroll中獲得模擬過程中的scrollX,scrollY,坐標告丢,但computeScroll方法是不會自動調用的,只能通過invalidate——》OnDraw——》computeScroll來簡介調用损谦,所以需要這個invalidate岖免,而當模擬過程結束的時候,computeScrollOffset返回的是false照捡,從而結束循環(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ù)時長,而另一個沒有悲立,這個非常好理解鹿寨,與在動畫中的設置時間是一樣的,而其他四個坐標薪夕,就是起始和偏移量脚草,通過上述的步驟,就可以完成一個平移的效果了原献,下面我們回到實例馏慨,在構造分鐘初始化Scroller對象,然后重寫computeScroll方法姑隅,最后需要監(jiān)聽手指離開屏幕的事件写隶,并在該事件之后調用startScroll()完成平移,所以我們在ACTION_UP中

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

在本書中第七章將會詳細介紹讲仰,這里就不重復了

ViewDragHelper

Google在其support庫中為我們提供了一個DrawerLayout和SlidingPaneLayout兩個布局來幫助開發(fā)者實現策劃效果慕趴,這兩個布局,大大的方便了我們自己創(chuàng)建自己的滑動布局,然而秩贰,這兩個強大的布局背后霹俺,卻隱藏著一個鮮為人知,卻功能強大的類——ViewDragHelper毒费,通過ViewDragHelper丙唧,基本可以實現各種不同的側滑,拖放需求觅玻,因此這個方法也是各種滑動解決方案的終極絕招

ViewDragHelper雖然很強大想际,但是使用也是本章節(jié)最復雜 的,我們需要了解ViewDragHelper的基本使用方法的基礎上溪厘,通過不斷的練習去掌握它胡本,我們這里就實現一個 QQ滑動側邊欄的布局,我么來看看具體怎么實現的

  • 初始化ViewDragHelper

ViewDragHelper通常定義在一個ViewGroup中畸悬,通過其靜態(tài)方法初始化

mViewDragHelper = ViewDragHelper.create(this,callback);

他的第一個參數是要監(jiān)聽的View,第二個參數是一個Callback回調

  • 攔截事件

要重寫攔截事件侧甫,將事件傳遞給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內部也是通過Scroller來實現平移的蹋宦,我們可以這樣使用

@Override
  public void computeScroll() {
      if(mViewDragHelper.continueSettling(true)){
          ViewCompat.postInvalidateOnAnimation(this);
      }
  }
  • 處理回調Callback
    //側滑回調
    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;
        }

        //拖動結束后調用
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            //手指抬起后緩慢的移動到指定位置
            if(mMainView.getLeft() <500){
                //關閉菜單
                mViewDragHelper.smoothSlideViewTo(mMainView,0,0);
                ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
            }else{
                //打開菜單
                mViewDragHelper.smoothSlideViewTo(mMainView,300,0);
                ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
            }
        }
    };

通過一步步的分析披粟,現在要實現QQ的側滑,是不是非常簡單了尼冷冗,下面自定義一個viewGroup來完成整個編碼的實例

 //XML加載組建后回調
    @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();
    }

最后 整個view 的代碼

package com.example.younger.youngertest;

import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
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;

/**
 * Created by Younger on 2018/3/6.
 */
public class DragView extends FrameLayout {
    private ViewDragHelper mViewDragHelper;
    private View mMenuView, mMainView;
    private int mWidth;
    public DragView(@NonNull Context context) {
        super(context);
        initView();
    }

    public DragView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public DragView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public DragView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        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;
    }

    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    private void initView() {

        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    private ViewDragHelper.Callback callback =
            new ViewDragHelper.Callback() {

                // 何時開始檢測觸摸事件
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    //如果當前觸摸的child是mMainView時開始檢測
                    return mMainView == child;
                }

                // 觸摸到View后回調
                @Override
                public void onViewCaptured(View capturedChild,
                                           int activePointerId) {
                    super.onViewCaptured(capturedChild, activePointerId);
                }

                // 當拖拽狀態(tài)改變守屉,比如idle,dragging
                @Override
                public void onViewDragStateChanged(int state) {
                    super.onViewDragStateChanged(state);
                }

                // 當位置改變的時候調用,常用與滑動時更改scale等
                @Override
                public void onViewPositionChanged(View changedView,
                                                  int left, int top, int dx, int dy) {
                    super.onViewPositionChanged(changedView, left, top, dx, dy);
                }

                // 處理垂直滑動
                @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 releasedChild, float xvel, float yvel) {
                    super.onViewReleased(releasedChild, xvel, yvel);
                    //手指抬起后緩慢移動到指定位置
                    if (mMainView.getLeft() < 500) {
                        //關閉菜單
                        //相當于Scroller的startScroll方法
                        mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
                        ViewCompat.postInvalidateOnAnimation(DragView.this);
                    } else {
                        //打開菜單
                        mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
                        ViewCompat.postInvalidateOnAnimation(DragView.this);
                    }
                }
            };
}

XML 的寫法

<?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.example.younger.youngertest.DragView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/view"
       >
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_blue_light">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Menu" />
        </FrameLayout>

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_dark">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Main" />
        </FrameLayout>
    </com.example.younger.youngertest.DragView>
</RelativeLayout>
  • 這里只是非常簡單的模擬了一下
       //用戶觸摸到view回調
       @Override
       public void onViewCaptured(View capturedChild, int activePointerId) {
           super.onViewCaptured(capturedChild, activePointerId);
       }
  • onViewDragStateChanged
       //拖拽狀態(tài)改變時蒿辙,比如idle,dragging
        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
        }
  • onViewPositionChanged
        //位置發(fā)生改變拇泛,常用于滑動scale效果
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
        }

好了 ,Scroll 分析到此結束.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市思灌,隨后出現的幾起案子俺叭,更是在濱河造成了極大的恐慌,老刑警劉巖泰偿,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绪颖,死亡現場離奇詭異,居然都是意外死亡甜奄,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門窃款,熙熙樓的掌柜王于貴愁眉苦臉地迎上來课兄,“玉大人,你說我怎么就攤上這事晨继⊙滩” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蜒茄。 經常有香客問我唉擂,道長,這世上最難降的妖魔是什么檀葛? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任玩祟,我火速辦了婚禮,結果婚禮上屿聋,老公的妹妹穿的比我還像新娘空扎。我一直安慰自己,他們只是感情好润讥,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布转锈。 她就那樣靜靜地躺著,像睡著了一般楚殿。 火紅的嫁衣襯著肌膚如雪撮慨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天脆粥,我揣著相機與錄音砌溺,去河邊找鬼。 笑死冠绢,一個胖子當著我的面吹牛抚吠,可吹牛的內容都是我干的。 我是一名探鬼主播弟胀,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼楷力,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了孵户?” 一聲冷哼從身側響起萧朝,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夏哭,沒想到半個月后检柬,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡竖配,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年何址,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片进胯。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡用爪,死狀恐怖,靈堂內的尸體忽然破棺而出胁镐,到底是詐尸還是另有隱情偎血,我是刑警寧澤诸衔,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站颇玷,受9級特大地震影響笨农,放射性物質發(fā)生泄漏。R本人自食惡果不足惜帖渠,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一谒亦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧阿弃,春花似錦诊霹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至入愧,卻和暖如春鄙漏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棺蛛。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工怔蚌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旁赊。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓桦踊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親终畅。 傳聞我的和親對象是個殘疾皇子籍胯,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內容

  • 概念 滑動是如何產生的 滑動一個VIew,本質上是移動一個View离福。移動一個View需要改變他的坐標杖狼,所以滑動一個...
    Reiser實驗室閱讀 291評論 0 0
  • 導語 滑動算是Android比較常用的效果了,滑動的操作具有很好的用戶體驗性妖爷。 主要內容 滑動效果是如何產生的 實...
    一個有故事的程序員閱讀 6,452評論 3 11
  • 內容是博主照著書敲出來的蝶涩,博主碼字挺辛苦的,轉載請注明出處絮识,后序內容陸續(xù)會碼出绿聘。 當了解了Android坐標系和觸...
    Blankj閱讀 6,641評論 3 61
  • 前言 本篇談論Android Scroll的應用以及如何在應用中添加滑動效果。你可以學到: 發(fā)生滑動效果的原因 如...
    張文靖同學閱讀 569評論 0 1
  • 文:舊城.xu 日光傾城從什么時候開始次舌,成了想想就被歲月迷了眼的四個字熄攘。是的“日光傾城” 如果歲月是老友,你打算讓...
    舊城xu閱讀 275評論 0 0