Android滑動(dòng)沖突解決方法(二)

之前的一遍學(xué)習(xí)筆記主要就Android滑動(dòng)沖突中,在不同方向的滑動(dòng)所造成沖突進(jìn)行了了解误续,這種沖突很容易理解,當(dāng)然也很容易解決扫茅。今天女嘲,就同方向的滑動(dòng)所造成的沖突進(jìn)行一下了解,這里就先以垂直方向的滑動(dòng)沖突為背景诞帐,這也是日常開(kāi)發(fā)中最常見(jiàn)的一種情況欣尼。

這里先看一張效果圖

查看圖文詳情

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

上圖是在購(gòu)物軟件上常見(jiàn)的上拉查看圖文詳情停蕉,關(guān)于這中動(dòng)畫(huà)效果的實(shí)現(xiàn)愕鼓,其實(shí)實(shí)現(xiàn)整體的效果,辦法是有很多的慧起,網(wǎng)上有很多相關(guān)的例子菇晃,但是對(duì)某些細(xì)節(jié)的處理不是很清晰,比如蚓挤,下拉之后顯示的部分(例如底部的圖文詳情)又是一個(gè)類(lèi)ScrollView的控件(比如WebView)的話磺送,又會(huì)產(chǎn)生新的問(wèn)題驻子。這里就以下拉查看圖文詳情為背景做一下同方向滑動(dòng)沖突的分析。

整體思路

這里看下圖

多個(gè)ScrollView嵌套示意圖

首先估灿,關(guān)于這張圖做一些設(shè)定:

  • 黑色的框代表手機(jī)屏幕
  • 綠色的框代表一個(gè)外層的ScrollView崇呵,這個(gè)應(yīng)該是實(shí)際應(yīng)用中可見(jiàn)view的總長(zhǎng)度
  • 兩個(gè)紅色的框代表嵌套在里面的兩個(gè)類(lèi)ScrollView控件,這里我們就暫時(shí)簡(jiǎn)稱為 SUp,SDown

好了馅袁,接下來(lái)就分析一下實(shí)現(xiàn)整個(gè)流程的過(guò)程域慷。

這里必須明確的一點(diǎn),無(wú)論何時(shí)汗销,SUp和SDown可見(jiàn)的部分始終是手機(jī)屏幕的高度犹褒。知道了這一點(diǎn),我們就可以按以下步驟展開(kāi)

首先弛针,我們確保外部的ScrollView不攔截滑動(dòng)事件叠骑,這樣SUp必然獲得此次事件,然后根據(jù)其Action_Move事件削茁,當(dāng)其為向上滑動(dòng)自身滑動(dòng)距離+屏幕高度=其自身高度 時(shí)座云,即可認(rèn)為SUp滑動(dòng)到了底部,此時(shí)外部ScrollView可攔截滑動(dòng)事件付材,從而保證整個(gè)視圖能夠繼續(xù)向上滑動(dòng)朦拖,這個(gè)時(shí)候底部SDown就顯示出來(lái)了。

同理厌衔,這時(shí)候不允許外部ScrollView攔截滑動(dòng)事件璧帝,由SDown處理,根據(jù)其Action_move事件富寿,當(dāng)其為向下滑動(dòng)睬隶,且自身可滑動(dòng)距離為0時(shí),就說(shuō)明SDown已經(jīng)滑動(dòng)到了頂部页徐,這時(shí)外部ScrollView又可以獲得攔截滑動(dòng)事件的權(quán)利苏潜,從而保證整個(gè)視圖能夠向上繼續(xù)滑動(dòng),此時(shí)SUp再次顯示变勇,又開(kāi)始新一輪循環(huán)攔截恤左。

這樣整體的一個(gè)流程就可以實(shí)現(xiàn)動(dòng)圖中的效果。好了搀绣,說(shuō)完原理飞袋,還是看代碼。

代碼實(shí)現(xiàn)

SUp實(shí)現(xiàn)

public class UpScrollView extends ScrollView {
    /**
     * 屏幕高度
     */
    private int mScreenHeight;
    /**
     * 上一次的坐標(biāo)
     */
    private float mLastY;
    /**
     * 當(dāng)前View滑動(dòng)距離
     */
    private int mScrollY;
    /**
     * 當(dāng)前View內(nèi)子控件高度
     */
    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) {
        //默認(rèn)設(shè)定頂層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;

                //向上滑動(dòng)時(shí)链患,如果translateY等于屏幕高度時(shí)巧鸭,即表明滑動(dòng)到底部,可由頂層View控制滑動(dòng)
                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里做了減法麻捻,其實(shí)道理是一樣的纲仍。

onScrollChanged 是在View類(lèi)中實(shí)現(xiàn)呀袱,查看其API可以看到其第二個(gè)參數(shù)t解釋

  • @param t Current vertical scroll origin.

即為當(dāng)前View此次滑動(dòng)的距離

SDown實(shí)現(xiàn)

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并沒(méi)有繼承ScrollView郑叠,而是選擇繼承了WebView夜赵,這里只是為了方便,當(dāng)然繼承ScrollView也是沒(méi)有問(wèn)題锻拘。這里只是需要按實(shí)際情況考慮,因?yàn)榈撞繄D文詳情的內(nèi)容就是一個(gè)WebView加載數(shù)據(jù)击蹲。

這個(gè)類(lèi)的實(shí)現(xiàn)署拟,按照之前說(shuō)的原理應(yīng)該很好理解。

外部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);
            //獲得內(nèi)部的兩個(gè)子view
            upScrollView = (UpScrollView) parentView.getChildAt(0);
            myWebView = (MyWebView) parentView.getChildAt(2);
//            //并設(shè)定其高度為屏幕高度
            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);
    }

}

這個(gè)類(lèi)的實(shí)現(xiàn)歌豺,就很靈活了推穷,在onMeasure方法中初始化完內(nèi)部的View之后,在OnTouch方法中就可以根據(jù)實(shí)際需求完成不同的邏輯實(shí)現(xiàn)类咧,這里只是為了仿照查看圖文詳情的效果馒铃,對(duì)整個(gè)視圖通過(guò)ScrollView的smoothScrollTo方法進(jìn)行位移變化,這個(gè)邏輯很簡(jiǎn)單痕惋。

這里重點(diǎn)說(shuō)一下一個(gè)地方:

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

你可能會(huì)奇怪中間的child(1)去了哪里区宇?這里還要從MainActivity的布局文件說(shuō)起

dual_scrollview_activity_layout1.xml

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

    <com.example.dreamwork.activity.superscrollview.CustomerScrollViews
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <com.example.dreamwork.activity.superscrollview.UpScrollView

                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:scrollbars="vertical">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    <ImageView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:scaleType="fitXY"
                        android:src="@drawable/taobao" />
                    <ImageView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:scaleType="fitXY"
                        android:src="@drawable/taobao" />

                    <TextView
                        android:textSize="20sp"
                        android:padding="10dp"
                        android:gravity="center"
                        android:layout_marginTop="20dp"
                        android:layout_marginBottom="60dp"
                        android:text="查看圖文詳情"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content" />

                </LinearLayout>
            </com.example.dreamwork.activity.superscrollview.UpScrollView>

            <include layout="@layout/selector_tab_items" />

            <com.example.dreamwork.activity.superscrollview.MyWebView
                android:id="@+id/web"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

        </LinearLayout>
    </com.example.dreamwork.activity.superscrollview.CustomerScrollViews>

</LinearLayout>

整個(gè)布局文件可以看出,我們?cè)贑ustomerScrollViews這個(gè)最外層的自定義ScrollView內(nèi)部又放置了兩個(gè)自定義的ScrollView(就如我們看到的原理圖那樣)值戳,只不過(guò)在這兩個(gè)ScrollView類(lèi)控件的中間通過(guò)layout又放置一個(gè)LinearLayout议谷,里面的內(nèi)容就是在動(dòng)圖中看到的那個(gè)中間的寫(xiě)著qq,baidu字樣的用于切換WebView內(nèi)容的一個(gè)View。這里就不貼代碼了堕虹。

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

使用###

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);
    }
}

關(guān)于底部View內(nèi)容更新赴捞,WebView 通過(guò)加載不同URL實(shí)現(xiàn)不同視圖效果逼裆,只是作為Demo測(cè)試昼汗,實(shí)際中應(yīng)考慮通過(guò)fragment切換實(shí)現(xiàn)涨颜。

這里對(duì)滑動(dòng)沖突的解決方法署咽,是由內(nèi)而外的展開(kāi)麻裁,默認(rèn)使頂層View失去攔截能力泼诱,在由底部View的滑動(dòng)距離屿良,做出不同邏輯判斷控制了頂層的攔截與否擎勘;這也是比較容易理解和實(shí)現(xiàn)的思路粉洼。當(dāng)然然评,對(duì)于此類(lèi)滑動(dòng)沖突仅财,有很多不同思路,這里只是列舉其一碗淌。

實(shí)際開(kāi)發(fā)開(kāi)發(fā)中盏求,這種帶有同方向滑動(dòng)特性的控件嵌套時(shí)抖锥,產(chǎn)生的問(wèn)題不只是滑動(dòng)沖突,有時(shí)候會(huì)有內(nèi)容顯示不全或不顯示的情況碎罚。

最常見(jiàn)如ScrollView嵌套ListView磅废,這種情況只需自定義ListView使其高度計(jì)算為一個(gè)很大的值,某種意義上讓其失去了滑動(dòng)的特性荆烈,但是這樣也讓ListView貌似失去了視圖回收機(jī)制拯勉,這種時(shí)候如果加載很多很多很多圖片,效果就會(huì)很不理想憔购。對(duì)于這種情況宫峦,通過(guò)對(duì)ListView添加headView及footView也是一種解決的辦法,但也得實(shí)際UI情況允許玫鸟。

ScrollView嵌套R(shí)ecyclerView時(shí)稍微麻煩一點(diǎn)导绷,需要自定義ScrollView,還需要自定義實(shí)現(xiàn)LinearLayoutManager屎飘。

由此可見(jiàn)妥曲,在Android中這種帶有類(lèi)似特性的控件嵌套后,問(wèn)題還是很多的钦购。對(duì)此檐盟,Google出了一個(gè)叫做NestedScrolling的東西,接下來(lái)準(zhǔn)備研究一下押桃,看看其在解決滑動(dòng)沖突方面的優(yōu)勢(shì)遵堵。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市怨规,隨后出現(xiàn)的幾起案子陌宿,更是在濱河造成了極大的恐慌,老刑警劉巖波丰,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壳坪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掰烟,警方通過(guò)查閱死者的電腦和手機(jī)爽蝴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纫骑,“玉大人蝎亚,你說(shuō)我怎么就攤上這事∠裙荩” “怎么了发框?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)煤墙。 經(jīng)常有香客問(wèn)我梅惯,道長(zhǎng)宪拥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任铣减,我火速辦了婚禮她君,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘葫哗。我一直安慰自己缔刹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布劣针。 她就那樣靜靜地躺著校镐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酿秸。 梳的紋絲不亂的頭發(fā)上灭翔,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天魏烫,我揣著相機(jī)與錄音辣苏,去河邊找鬼。 笑死哄褒,一個(gè)胖子當(dāng)著我的面吹牛稀蟋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呐赡,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼退客,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了链嘀?” 一聲冷哼從身側(cè)響起萌狂,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怀泊,沒(méi)想到半個(gè)月后茫藏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霹琼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年务傲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枣申。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡售葡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忠藤,到底是詐尸還是另有隱情挟伙,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布模孩,位于F島的核電站像寒,受9級(jí)特大地震影響烘豹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诺祸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一携悯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筷笨,春花似錦憔鬼、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仰禀,卻和暖如春照雁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背答恶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工饺蚊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悬嗓。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓污呼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親包竹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子燕酷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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