Android View體系(二)實現View滑動的六種方法

http://blog.csdn.net/itachi85/article/details/50724558

相關文章:
Android View體系(一)視圖座標系

1.View的滑動簡介

View的滑動是Android實現自定義控件的基礎,同時在開發中我們也難免會遇到View的滑動的處理。其實不管是那種滑動的方式基本思想都是類似的:當觸摸事件傳到View時,系統記下觸摸點的座標,手指移動時系統記下移動後的觸摸的座標並算出偏移量,並通過偏移量來修改View的座標。
實現View滑動有很多種方法,這篇文章主要講解六種滑動的方法,分別是:layout()、offsetLeftAndRight()與offsetTopAndBottom()、LayoutParams、動畫、scollTo與scollBy和Scroller;在下一篇文章我們會詳細介紹屬性動畫。

2.實現View滑動的六種方法

layout()

view進行繪製的時候會調用onLayout()方法來設置顯示的位置,因此我們同樣也可以通過修改View的left、top、right、bottom這四種屬性來控制View的座標。首先我們要自定義一個View,在onTouchEvent()方法中獲取觸摸點的座標:

<code class="hljs cs has-numbering">   <span class="hljs-keyword">public</span> boolean <span class="hljs-title">onTouchEvent</span>(MotionEvent <span class="hljs-keyword">event</span>) {
        <span class="hljs-comment">//獲取到手指處的橫座標和縱座標</span>
        <span class="hljs-keyword">int</span> x = (<span class="hljs-keyword">int</span>) <span class="hljs-keyword">event</span>.getX();
        <span class="hljs-keyword">int</span> y = (<span class="hljs-keyword">int</span>) <span class="hljs-keyword">event</span>.getY();

        <span class="hljs-keyword">switch</span> (<span class="hljs-keyword">event</span>.getAction()) {
            <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                <span class="hljs-keyword">break</span>;

...</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul>

接下來我們在ACTION_MOVE事件中計算偏移量,再調用layout()方法重新放置這個自定義View的位置就好了:

<code class="hljs glsl has-numbering">            <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE:
                <span class="hljs-comment">//計算移動的距離</span>
                <span class="hljs-keyword">int</span> offsetX = x - lastX;
                <span class="hljs-keyword">int</span> offsetY = y - lastY;
                <span class="hljs-comment">//調用layout方法來重新放置它的位置</span>
                <span class="hljs-keyword">layout</span>(getLeft()+offsetX, getTop()+offsetY,
                        getRight()+offsetX , getBottom()+offsetY);
                <span class="hljs-keyword">break</span>;</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul>

當我們每次移動時都會調用layout()方法來對自己重新佈局,從而達到移動View的效果。

自定義View的全部代碼(CustomView.java):

<code class="hljs java has-numbering"><span class="hljs-keyword">package</span> com.example.liuwangshu.moonviewslide;
<span class="hljs-keyword">import</span> android.content.Context;
<span class="hljs-keyword">import</span> android.util.AttributeSet;
<span class="hljs-keyword">import</span> android.view.MotionEvent;
<span class="hljs-keyword">import</span> android.view.View;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">View</span> {</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> lastX;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> lastY;

    <span class="hljs-keyword">public</span> <span class="hljs-title">CustomView</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr) {
        <span class="hljs-keyword">super</span>(context, attrs, defStyleAttr);
    }
    <span class="hljs-keyword">public</span> <span class="hljs-title">CustomView</span>(Context context, AttributeSet attrs) {
        <span class="hljs-keyword">super</span>(context, attrs);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-title">CustomView</span>(Context context) {
        <span class="hljs-keyword">super</span>(context);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span>(MotionEvent event) {
        <span class="hljs-comment">//獲取到手指處的橫座標和縱座標</span>
        <span class="hljs-keyword">int</span> x = (<span class="hljs-keyword">int</span>) event.getX();
        <span class="hljs-keyword">int</span> y = (<span class="hljs-keyword">int</span>) event.getY();

        <span class="hljs-keyword">switch</span> (event.getAction()) {
            <span class="hljs-keyword">case</span> MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                <span class="hljs-keyword">break</span>;

            <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE:
                <span class="hljs-comment">//計算移動的距離</span>
                <span class="hljs-keyword">int</span> offsetX = x - lastX;
                <span class="hljs-keyword">int</span> offsetY = y - lastY;
                <span class="hljs-comment">//調用layout方法來重新放置它的位置</span>
                layout(getLeft()+offsetX, getTop()+offsetY,
                        getRight()+offsetX , getBottom()+offsetY);
                <span class="hljs-keyword">break</span>;
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
    }
}
</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li></ul>

佈局中引用自定義View:

<code class="hljs xml has-numbering"><span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>
<span class="hljs-tag"><<span class="hljs-title">LinearLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attribute">xmlns:tools</span>=<span class="hljs-value">"http://schemas.android.com/tools"</span>
    <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
    <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span>
    <span class="hljs-attribute">android:orientation</span>=<span class="hljs-value">"vertical"</span>></span>

    <span class="hljs-tag"><<span class="hljs-title">com.example.liuwangshu.moonviewslide.CustomView
</span>        <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/customview"</span>
        <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"80dp"</span>
        <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"80dp"</span>
        <span class="hljs-attribute">android:layout_margin</span>=<span class="hljs-value">"50dp"</span>
        <span class="hljs-attribute">android:background</span>=<span class="hljs-value">"@android:color/holo_red_light"</span> /></span>
<span class="hljs-tag"></<span class="hljs-title">LinearLayout</span>></span>
</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul>

offsetLeftAndRight()與offsetTopAndBottom()

這兩種方法和layout()方法效果方法差不多,使用也差不多,我們將ACTION_MOVE中的代碼替換成如下代碼:

<code class="hljs cs has-numbering">            <span class="hljs-keyword">case</span> MotionEvent.ACTION_MOVE:
                <span class="hljs-comment">//計算移動的距離</span>
                <span class="hljs-keyword">int</span> offsetX = x - lastX;
                <span class="hljs-keyword">int</span> offsetY = y - lastY;
                <span class="hljs-comment">//對left和right進行偏移</span>
                offsetLeftAndRight(offsetX);
                <span class="hljs-comment">//對top和bottom進行偏移</span>
                offsetTopAndBottom(offsetY);
                <span class="hljs-keyword">break</span>;</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>

LayoutParams(改變佈局參數)

LayoutParams主要保存了一個View的佈局參數,因此我們可以通過LayoutParams來改變View的佈局的參數從而達到了改變View的位置的效果。同樣的我們將ACTION_MOVE中的代碼替換成如下代碼:

<code class="hljs avrasm has-numbering">  LinearLayout<span class="hljs-preprocessor">.LayoutParams</span> layoutParams= (LinearLayout<span class="hljs-preprocessor">.LayoutParams</span>) getLayoutParams()<span class="hljs-comment">;</span>
                layoutParams<span class="hljs-preprocessor">.leftMargin</span> = getLeft() + offsetX<span class="hljs-comment">;</span>
                layoutParams<span class="hljs-preprocessor">.topMargin</span> = getTop() + offsetY<span class="hljs-comment">;</span>
                setLayoutParams(layoutParams)<span class="hljs-comment">;</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>

因爲父控件是LinearLayout,所以我們用了LinearLayout.LayoutParams,如果父控件是RelativeLayout則要使用RelativeLayout.LayoutParams。除了使用佈局的LayoutParams外,我們還可以用ViewGroup.MarginLayoutParams來實現:

<code class="hljs avrasm has-numbering">                ViewGroup<span class="hljs-preprocessor">.MarginLayoutParams</span> layoutParams = (ViewGroup<span class="hljs-preprocessor">.MarginLayoutParams</span>) getLayoutParams()<span class="hljs-comment">;</span>
                layoutParams<span class="hljs-preprocessor">.leftMargin</span> = getLeft() + offsetX<span class="hljs-comment">;</span>
                layoutParams<span class="hljs-preprocessor">.topMargin</span> = getTop() + offsetY<span class="hljs-comment">;</span>
                setLayoutParams(layoutParams)<span class="hljs-comment">;</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>

動畫

可以採用View動畫來移動,在res目錄新建anim文件夾並創建translate.xml:

<code class="hljs xml has-numbering"><span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>
<span class="hljs-tag"><<span class="hljs-title">set</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>></span>
    <span class="hljs-tag"><<span class="hljs-title">translate</span> <span class="hljs-attribute">android:fromXDelta</span>=<span class="hljs-value">"0"</span> <span class="hljs-attribute">android:toXDelta</span>=<span class="hljs-value">"300"</span> <span class="hljs-attribute">android:duration</span>=<span class="hljs-value">"1000"</span>/></span>
<span class="hljs-tag"></<span class="hljs-title">set</span>></span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>

在Java代碼中引用:

<code class="hljs avrasm has-numbering">  mCustomView<span class="hljs-preprocessor">.setAnimation</span>(AnimationUtils<span class="hljs-preprocessor">.loadAnimation</span>(this, R<span class="hljs-preprocessor">.anim</span><span class="hljs-preprocessor">.translate</span>))<span class="hljs-comment">;</span></code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>

當然使用屬性動畫移動那就更簡單了,我們讓CustomView在1000毫秒內沿着X軸像右平移300像素:

<code class="hljs avrasm has-numbering">ObjectAnimator<span class="hljs-preprocessor">.ofFloat</span>(mCustomView,<span class="hljs-string">"translationX"</span>,<span class="hljs-number">0</span>,<span class="hljs-number">300</span>)<span class="hljs-preprocessor">.setDuration</span>(<span class="hljs-number">1000</span>)<span class="hljs-preprocessor">.start</span>()<span class="hljs-comment">;</span></code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>

scollTo與scollBy

scollTo(x,y)表示移動到一個具體的座標點,而scollBy(dx,dy)則表示移動的增量爲dx、dy。其中scollBy最終也是要調用scollTo的。scollTo、scollBy移動的是View的內容,如果在ViewGroup中使用則是移動他所有的子View。我們將ACTION_MOVE中的代碼替換成如下代碼:

<code class="hljs lasso has-numbering"> ((View)getParent())<span class="hljs-built_in">.</span>scrollBy(<span class="hljs-attribute">-offsetX</span>,<span class="hljs-attribute">-offsetY</span>);</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>

這裏要實現CustomView隨着我們手指移動的效果的話,我們就需要將偏移量設置爲負值。

Scroller

我們用scollTo/scollBy方法來進行滑動時,這個過程是瞬間完成的,所以用戶體驗不大好。這裏我們可以使用Scroller來實現有過度效果的滑動,這個過程不是瞬間完成的,而是在一定的時間間隔完成的。Scroller本身是不能實現View的滑動的,它需要配合View的computeScroll()方法才能彈性滑動的效果。
在這裏我們實現CustomView平滑的向右移動。

  • 首先我們要初始化Scroller:
<code class="hljs java has-numbering">  <span class="hljs-keyword">public</span> <span class="hljs-title">CustomView</span>(Context context, AttributeSet attrs) {
        <span class="hljs-keyword">super</span>(context, attrs);
        mScroller = <span class="hljs-keyword">new</span> Scroller(context);
    }</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>
  • 接下來重寫computeScroll()方法,系統會在繪製View的時候在draw()方法中調用該方法,這個方法中我們調用父類的scrollTo()方法並通過Scroller來不斷獲取當前的滾動值,每滑動一小段距離我們就調用invalidate()方法不斷的進行重繪,重繪就會調用computeScroll()方法,這樣我們就通過不斷的移動一個小的距離並連貫起來就實現了平滑移動的效果:
<code class="hljs java has-numbering">    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">computeScroll</span>() {
        <span class="hljs-keyword">super</span>.computeScroll();
        <span class="hljs-keyword">if</span>(mScroller.computeScrollOffset()){
            ((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
             <span class="hljs-comment">//通過不斷的重繪不斷的調用computeScroll方法</span>
             invalidate();
        }  
    }</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>
  • 調用Scroller.startScroll()方法。我們在CustomView中寫一個smoothScrollTo()方法,調用Scroller.startScroll()方法,在2000毫秒內沿X軸平移delta像素:
<code class="hljs cs has-numbering">  <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">smoothScrollTo</span>(<span class="hljs-keyword">int</span> destX,<span class="hljs-keyword">int</span> destY){
        <span class="hljs-keyword">int</span> scrollX=getScrollX();
        <span class="hljs-keyword">int</span> delta=destX-scrollX;
        <span class="hljs-comment">//1000秒內滑向destX</span>
        mScroller.startScroll(scrollX,<span class="hljs-number">0</span>,delta,<span class="hljs-number">0</span>,<span class="hljs-number">2000</span>);
        invalidate();
    }</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>
  • 最後我們在ViewSlideActivity.java中調用CustomView的smoothScrollTo()方法:
<code class="hljs cs has-numbering">          <span class="hljs-comment">//使用Scroll來進行平滑移動</span>
          mCustomView.smoothScrollTo(-<span class="hljs-number">400</span>,<span class="hljs-number">0</span>);</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li></ul>

這裏我們是設定CustomView沿着X軸向右平移400像素。

github源碼下載


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章