android自定義View之自定義可置頂ScrollView,View滑動原理簡析

  使用各種App的時候,發現一個特點,現在的App在可以滑動的頁面中,基本上都添加了一個置頂按鈕的效果,使的用戶可以很方便的回到頂部,就像網頁中幾乎所有的頁面都有置頂效果按鈕一樣,覺得這個交互不錯,以後自己的項目裏也肯定會用到的,就抽空把這個小功能實現了一下,現在把實現過程記錄下,以方便以後使用。

  先看下效果圖:

 

  當ScrollView向上滑動超過一定距離後,就漸變的出現一個置頂的按鈕,當滑動距離小於我們指定的距離時,按鈕又以一個漸變的方式消失。

   實現原理:

   這個小案例的實現原理很簡單:1.就是監聽View的onScrollChanged()方法,獲取到ScrolView滑動的距離,如果大於我們的距離,則出現置頂按鈕,否則,隱藏

    2.按鈕點擊事件的處理,點擊按鈕讓整個ScrollView滑動到(0,0)位置即可。

   雖然這個案例很簡單,但是卻涉及到了android的滑動原理,從onScrollViewChanged這個方法屬於View這個類也可以看出,android的任何View都是可以滑動的,在這個案例後,我會說一下我對android滑動原理的理解。

    自定義ScrollView代碼:

<span style="font-size:14px;">
/**********************************************************
 * @文件名稱:GoTopScrollView.java
 * @文件作者:rzq
 * @文件描述:滑動超過一定距離後,出現置頂按鈕
 * @修改歷史:2015年3月26日創建初始版本
 **********************************************************/
public class GoTopScrollView extends ScrollView implements OnClickListener
{
	private ImageView goTopBtn;
	private int screenHeight;

	public GoTopScrollView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
	}

	public void setScrollListener(ImageView goTopBtn)
	{
		this.goTopBtn = goTopBtn;
		this.goTopBtn.setOnClickListener(this);
	}

	@Override
	protected void onScrollChanged(int l, int t, int oldl, int oldt)
	{
		super.onScrollChanged(l, t, oldl, oldt);
		/**
		 * 滑動距離超過300px,出現向上按鈕,可以做爲自定義屬性
		 */
		if (t >= 300)
		{
			goTopBtn.setVisibility(View.VISIBLE);
		}
		else
		{
			goTopBtn.setVisibility(View.GONE);
		}
	}

	@Override
	public void onClick(View v)
	{
		if (v.getId() == R.id.go_top_btn)
		{
			this.smoothScrollTo(0, 0);
		}
	}
}</span>
 Activity使用代碼:

<span style="font-size:14px;">private void initView()
	{
		container = (RelativeLayout) findViewById(R.id.container);
		container.setLayoutTransition(new LayoutTransition());
		
		goTopBtn = (ImageView) findViewById(R.id.go_top_btn);
		scrollView = (GoTopScrollView) findViewById(R.id.scroll_view);
		scrollView.setScrollListener(goTopBtn);
	}</span>
    這樣就很輕鬆的實現了這個置頂的效果,但僅僅這樣是不夠的,現在我們來分析一下android的滑動原理到底是怎麼回事,是不是隻有ScrollView纔可以滑動。

  1.首先,android中任意的View,都是可以滑動的,爲什麼呢?請看View中的關鍵源碼,只挑出和滑動有關的代碼:

 <span style="font-size:14px;"> /**
     * 指定X軸的滑動,Y軸不動
     * @param value the x position to scroll to
     */
    public void setScrollX(int value) {
        scrollTo(value, mScrollY);
    }

    /**
     * 指定Y軸的滑動,X軸不動
     * @param value the y position to scroll to
     */
    public void setScrollY(int value) {
        scrollTo(mScrollX, value);
    }

     /**
     * 真正處理滑動的方法
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

    /**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

    /**
     * 觸發滑動後的監聽
     * @param l Current horizontal scroll origin.
     * @param t Current vertical scroll origin.
     * @param oldl Previous horizontal scroll origin.
     * @param oldt Previous vertical scroll origin.
     */
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            postSendViewScrolledAccessibilityEventCallback();
        }

        mBackgroundSizeChanged = true;

        final AttachInfo ai = mAttachInfo;
        if (ai != null) {
            ai.mViewScrollChanged = true;
        }
    }</span><span style="font-size:14px;"> 
</span>

    由這幾個方法可以看出,android中所有View都是可以滑動的,其實所有的View都有兩套座標系,一個是指定View位置的X,Y,另一對則是指定View滑動座標系,mScrollX和mScrollY,兩對座標系統是完全獨立的,即X,Y座標的改變不會影響mScrollX和mScrollY,反之也成立。下面,我們則重點分析一下scrollTo這個方法。

  

  scrollTo(int x, int y) 是將View中內容滑動到相應的位置,參考的座標系原點爲parent View的左上角。

       調用scrollTo(100, 0)表示將View中的內容移動到x = 100, y = 0的位置,如下圖所示。注意,圖中黃色矩形區域表示的是一個parent View,綠色虛線矩形爲parent view中的內容。一般情況下兩者的大小一致,本文爲了顯示方便,將虛線框畫小了一點。圖中的黃色區域的位置始終不變,發生位置變化的是顯示的內容,如圖所示:


     mScrollX和mScrollY是View類中專門用於記錄滑動位置的變量。這兩個函數最終調用onScrollChanged()函數。

      先總結一下,android的滑動原理就是:通過調用scrollTo(),使的View的滑動座標系發生改變,並保存在mScrollX和mScrollY這兩個成員變量中,我們可以通過getScrollX()和getScrollY(),獲取滑動座標值。

     由圖中我們可以看到,當我們調用scrollTo(100,0);時,發現View是向左滑動了,傳入一個正數滑動的方向卻是向左,這與我們平常理解的座標軸是相反的,其實並不矛盾,因爲滑動座標系本來就與我們平常的座標系統不一樣,調用scrollTo方法,最終會執行到以下代碼:

     

public void invalidate(int l, int t, int r, int b) {  
    if (ViewDebug.TRACE_HIERARCHY) {  
        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);  
    }  
  
    if (skipInvalidate()) {  
        return;  
    }  
    if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||  
            (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID ||  
            (mPrivateFlags & INVALIDATED) != INVALIDATED) {  
        mPrivateFlags &= ~DRAWING_CACHE_VALID;  
        mPrivateFlags |= INVALIDATED;  
        mPrivateFlags |= DIRTY;  
        final ViewParent p = mParent;  
        final AttachInfo ai = mAttachInfo;  
        //noinspection PointlessBooleanExpression,ConstantConditions  
        if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {  
            if (p != null && ai != null && ai.mHardwareAccelerated) {  
                // fast-track for GL-enabled applications; just invalidate the whole hierarchy  
                // with a null dirty rect, which tells the ViewAncestor to redraw everything  
                p.invalidateChild(this, null);  
                return;  
            }  
        }  
        if (p != null && ai != null && l < r && t < b) {  
            final int scrollX = mScrollX;  
            final int scrollY = mScrollY;  
            final Rect tmpr = ai.mTmpInvalRect;  
            tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);  
            p.invalidateChild(this, tmpr);  
        }  
    }  
}  
  看倒數第五行代碼,更新滑動座標系時,走的是 tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);傳入一個正數後一減,就成了負數,自然要向左移動了,這樣就又統一起來了。

   總結:以上差不多就是View自帶的滑動原理,但View中的滑動都是瞬時滑動。沒有任何的過渡效果,結合Scroller是可以做出各種各樣的過渡效果的,下篇文章,總結一下如何結合Scroller使View更加炫的滑動。

   參考文章: 圖解android View的scroll原理

                     


 

  

發佈了66 篇原創文章 · 獲贊 59 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章