Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> 關於Android滑動沖突的解決方法(二)

關於Android滑動沖突的解決方法(二)

編輯:關於android開發

關於Android滑動沖突的解決方法(二)


之前的一遍學習筆記主要就Android滑動沖突中,在不同方向的滑動所造成沖突進行了了解,這種沖突很容易理解,當然也很容易解決。今天,就同方向的滑動所造成的沖突進行一下了解,這裡就先以垂直方向的滑動沖突為背景,這也是日常開發中最常見的一種情況。

這裡先看一張效果圖

查看圖文詳情

由於GIF 圖片大小的限制,截圖效果不是很好

上圖是在購物軟件上常見的上拉查看圖文詳情,關於這中動畫效果的實現,其實實現整體的效果,辦法是有很多的,網上有很多相關的例子,但是對某些細節的處理不是很清晰,比如,下拉之後顯示的部分(例如底部的圖文詳情)又是一個類ScrollView的控件(比如WebView)的話,又會產生新的問題。這裡就以下拉查看圖文詳情為背景做一下同方向滑動沖突的分析。

整體思路

這裡看下圖

多個ScrollView嵌套示意圖

首先,關於這張圖做一些設定:

黑色的框代表手機屏幕 綠色的框代表一個外層的ScrollView 兩個紅色的框代表嵌套在裡面的兩個類ScrollView控件1,這裡我們就暫時簡稱為 SUp,SDown

好了,接下來就分析一下實現整個流程的過程。

這裡必須明確的一點,無論何時,SUp和SDown可見的部分始終是手機屏幕的高度。知道了這一點,我們就可以按以下步驟展開

首先,我們確保外部的ScrollView不攔截滑動事件,這樣SUp必然獲得此次事件,然後根據其Action_Move事件,當其為向下滑動自身滑動距離+屏幕高度=其自身高度 時,即可認為SUp滑動到了底部,此時外部ScrollView可攔截滑動事件,從而保證能夠繼續向下滑動,這個時候底部SDown就顯示出來了。

同理,這時候外部ScrollView不允許外部ScrollView攔截滑動事件,由SDown處理,根據其Action_move事件,當其為向上滑動,且自身可滑動距離為0時,就說明SDown已經滑動到了頂部,這時外部ScrollView又可以獲得攔截滑動事件的權利,從而保證整個視圖能夠向上繼續滑動,此時SUp再次顯示,有開始新一輪循環攔截。

這樣整體的一個流程就可以實現動圖中的效果。好了,說完原理,還是看代碼。

代碼實現

SUp實現

public class UpScrollView extends ScrollView {
    /**
     * 屏幕高度
     */
    private int mScreenHeight;
    /**
     * 上一次的坐標
     */
    private float mLastY;
    /**
     * 當前View滑動距離
     */
    private int mScrollY;
    /**
     * 當前View內子控件高度
     */
    private int mChildH;


    public UpScrollView(Context context) {
        super(context);
        init(context);
    }

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

    public UpScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);

    }

    private void init(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        mScreenHeight = dm.heightPixels;
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //默認設定頂層View不攔截

        getParent().getParent().requestDisallowInterceptTouchEvent(true);

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastY = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float y = ev.getY();
                float deltaY = y - mLastY;

                mChildH = this.getChildAt(0).getMeasuredHeight();

                int translateY = mChildH - mScrollY;

                //向上滑動時,如果translateY等於屏幕高度時,即表明滑動到底部,可又頂層View控制滑動
                if (deltaY < 0 && translateY == mScreenHeight) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            default:
                break;


        }

        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        mScrollY = t;
    }
}

這裡在ACTION_MOVE裡做了減法,其實道理是一樣的。

onScrollChanged 是在View類中實現,查看其API可以看到其第二個參數t解釋

@param t Current vertical scroll origin.

即為當前View此次滑動的距離

SDown實現

public class MyWebView extends WebView {
    public float oldY;
    private int t;

    public MyWebView(Context context) {
        super(context);
        init();
    }

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

    public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        WebSettings settings = getSettings();
        settings.setJavaScriptEnabled(true);
        setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        getParent().getParent().requestDisallowInterceptTouchEvent(true);
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                oldY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:

                float Y = ev.getY();
                float Ys = Y - oldY;
                if (Ys > 0 && t == 0) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;

        }

        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        this.t = t;
        super.onScrollChanged(l, t, oldl, oldt);
    }


}

看以看到,這裡底部的View並沒有繼承ScrollView,而是選擇繼承了WebView,這裡只是為了方便,當然繼承ScrollView也是沒有問題。這裡只是需要按實際情況考慮,因為底部圖文詳情的內容就是一個WebView加載數據。

這個類的實現,按照之前說的原理應該很好理解。

外部ScrollView

public class CustomerScrollViews extends ScrollView {
    /**
     * 屏幕高度
     */
    private int mScreenHeight;

    private UpScrollView upScrollView;
    private MyWebView myWebView;
    private boolean init = false;

    private float fator = 0.2f;
    private int factorHeight;

    private boolean upShow = true;


    public CustomerScrollViews(Context context) {
        super(context);
        init(context);
    }

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

    public CustomerScrollViews(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);

    }

    private void init(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        mScreenHeight = dm.heightPixels;
        factorHeight = (int) (mScreenHeight * fator);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!init) {

            LinearLayout parentView = (LinearLayout) getChildAt(0);
            //獲得內部的兩個子view
            upScrollView = (UpScrollView) parentView.getChildAt(0);
            myWebView = (MyWebView) parentView.getChildAt(2);
//            //並設定其高度為屏幕高度
            upScrollView.getLayoutParams().height = mScreenHeight;
            myWebView.getLayoutParams().height = mScreenHeight;
            init = true;
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            scrollTo(0, 0);
        }
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_UP:
                int scrollY = getScrollY();
                if (upShow) {
                    if (scrollY <= factorHeight) {
                        smoothScrollTo(0, 0);
                    } else {
                        smoothScrollTo(0, mScreenHeight);
                        upShow = false;

                    }
                } else {
                    int scrollpadding = mScreenHeight - scrollY;
                    if (scrollpadding >= factorHeight) {
                        this.smoothScrollTo(0, 0);
                        upShow = true;

                    } else {
                        this.smoothScrollTo(0, mScreenHeight);
                    }

                }


                return true;

        }
        return super.onTouchEvent(ev);
    }


}

這個類的實現,就很靈活了,在onMeasure方法中初始化完內部的View之後,在OnTouch方法中就可以根據實際需求完成不同的邏輯實現,這裡只是為了仿照查看圖文詳情的效果,對整個視圖通過ScrollView的smoothScrollTo方法進行位移變化,這個邏輯很簡單。

這裡重點說一下一個地方:

upScrollView = (UpScrollView) parentView.getChildAt(0);
myWebView = (MyWebView) parentView.getChildAt(2);

你可能會奇怪中間的child(1)去了哪裡?這裡還要從MainActivity的布局文件說起

dual_scrollview_activity_layout1.xml




    

        


            

                


                    
                    

                    

                
            


            




            

        
    


整個布局文件可以看出,我們在CustomerScrollViews這個最外層的自定義ScrollView內部又放置了兩個自定義的ScrollView(就如我們看到的原理圖那樣),只不過在這兩個ScrollView類控件的中間通過layout又放置一個LinearLayout,裡面的內容就是在動圖中看到的那個中間的寫著qq,baidu字樣的用於切換WebView內容的一個View。這裡就不貼代碼了。

這樣,你就可以理解之前的child(1)為什麼被跳過了吧。

使用

public class DualScrollViewActivity1 extends Activity implements View.OnClickListener {

    private MyWebView webView;

    private TextView sinaTv, qqTv, baiduTv;
    private View line1, line2, line3;


    private final String BAIDU = "http://www.baidu.com";
    private final String QQ = "http://www.qq.com";
    private final String SINA = "http://sina.cn";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        InitView();
        sinaTv.performClick();
    }

    private void InitView() {
        setContentView(R.layout.dual_scrollview_activity_layout1);
        webView = V.f(this, R.id.web);

        sinaTv = V.f(this, R.id.one);
        sinaTv.setOnClickListener(this);
        qqTv = V.f(this, R.id.two);
        qqTv.setOnClickListener(this);
        baiduTv = V.f(this, R.id.three);
        baiduTv.setOnClickListener(this);


        line1 = V.f(this, R.id.line1);
        line2 = V.f(this, R.id.line2);
        line3 = V.f(this, R.id.line3);
    }


    @Override
    public void onClick(View v) {
        reset();
        String url = "";
        switch (v.getId()) {
            case R.id.one:
                line1.setVisibility(View.VISIBLE);
                url = SINA;
                break;
            case R.id.two:
                line2.setVisibility(View.VISIBLE);
                url = QQ;
                break;
            case R.id.three:
                line3.setVisibility(View.VISIBLE);
                url = BAIDU;
                break;
            default:
                break;
        }


        webView.loadUrl(url);
    }

    private void reset(){
        line1.setVisibility(View.GONE);
        line2.setVisibility(View.GONE);
        line3.setVisibility(View.GONE);
    }
}

這裡關於底部View內容更新,WebView 通過加載不同URL實現不同視圖效果,只是作為Demo測試。

這裡對滑動沖突的解決方法,是由內而外的展開,默認使頂層View失去攔截能力,在由底部View的滑動距離,做出不同邏輯判斷控制了頂層的攔截與否;這也是比較容易理解和實現的思路。當然,對於此類滑動沖突,有很多不同思路,這裡只是列舉其一。

實際開發開發中,這種帶有同方向滑動特性的控件嵌套時,產生的問題不只是滑動沖突,有時候會有內容顯示不全或不顯示的情況。

最常見如ScrollView嵌套ListView,這種情況只需自定義ListView使其高度計算為一個很大的值,某種意義上讓其失去了滑動的特性,但是這樣也讓ListView貌似失去了視圖回收機制,這種時候如果加載很多很多很多圖片,效果就會很不理想。對於這種情況,通過對ListView添加headView及footView也是一種解決的辦法,但也得實際UI情況允許。

ScrollView嵌套RecyclerView時稍微麻煩一點,需要自定義ScrollView,還需要自定義實現LinearLayoutManager。

由此可見,在Android中這種帶有類似特性的控件嵌套後,問題還是很多的。對此,Google出了一個叫做NestedScrolling的東西,接下來准備研究一下,看看其在解決滑動沖突方面的優勢。



  • 這裡說類ScrollView控件,就是說類似於ScrollView特性的控件,即自帶滾動特性的控件。 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved