Android 仿網(wǎng)易新聞上拉關閉Webview

效果如下:


device-2017-12-04-170020.gif

分為兩部分:1. View的創(chuàng)建哀卫。 2. 滑動事件處理

1. View的創(chuàng)建

從頁面上看澜沟,主要分為上下兩部分,上部為滾動的Webview晋控,底部為拉出來的CloseView
我這里自定義了ViewGrup汞窗。初始狀態(tài)Webview撐滿整個屏幕,CloseView不可見赡译,位于Webview底部仲吏。代碼如下

public class PullupCloseLayout extends ViewGroup {
    public final static int SIZE_DEFAULT_HEIGHT = 100;

    // 手勢滑動view
    private View mTarget;
    //底部上拉關閉view
    private ViewGroup mPullUpView;
    //滑動關閉頁面的最大高度
    private int mPullUpViewMaxHeight;
    public PullupCloseLayout(Context context) {
        this(context, null);
    }

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

    private void init(Context context) {
        //為底部CloseView
        mPullUpView = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_up_close, this);
        final DisplayMetrics metrics = getResources().getDisplayMetrics();
        mPullUpViewMaxHeight = (int) (SIZE_DEFAULT_HEIGHT * metrics.density);
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        if (mTarget == null) {
            ensureView();
        }
        if (mTarget == null) {
            return;
        }
        //WebView撐滿屏幕
        mTarget.layout(getPaddingLeft(), getPaddingTop(), width - getPaddingRight(), height - getPaddingBottom());
        //CloseView在 Webview底部
        mPullUpView.layout(0, height - getPaddingBottom(), width, height - getPaddingBottom() + mPullUpView.getMeasuredHeight());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mTarget == null) {
            ensureView();
        }
        if (mTarget == null) {
            return;
        }
        //設置Webview的高度撐滿全屏
        mTarget.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
        //設置CloseView 為固定高度
        mPullUpView.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(mPullUpViewMaxHeight, MeasureSpec.EXACTLY));
    }

    //初始化內部滾動view, 參考v4 SwipRefreshLayout
    private void ensureView() {
        if (mTarget == null) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!child.equals(mPullUpView)) {
                    mTarget = child;
                    break;
                }
            }
        }
    }
}

此時頁面布局完成蝌焚。接下來第二部處理滑動事件

2. 滑動事件處理

滑動事件主要處理兩個狀態(tài)裹唆, 1. 滑動到底部,可以隨手勢上滑只洒,松手可回彈许帐。 2. 可以隨慣性滑動并回彈

  1. 手勢上滑及回彈。
    判斷當頁面滑到底部不能繼續(xù)滑動的時候由本布局攔截手勢红碑, 并消費掉舞吭。 否則不了攔截。
    如何判斷滑動到底部析珊?
private boolean canChildScrollUp() {
      // 參數(shù)為正則代表向上是否可滑動羡鸥,負數(shù)則為向下, 一般用1和-1代表
        return ViewCompat.canScrollVertically(mTarget, 1);
 }

攔截滾動事件的代碼如下

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if (canChildScrollUp() || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            return false;
        }
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                final float initialDownY = getMotionEventY(ev, mActivePointerId);
                if (initialDownY == -1) {
                    return false;
                }
                //記錄按下的位置
                mInitialDownY = initialDownY;
                break;
            case MotionEvent.ACTION_MOVE:
                final float y = getMotionEventY(ev, mActivePointerId);
                if (y == -1) {
                    return false;
                }
                //判斷滾動的距離
                final float yDiff = mInitialDownY - y;
                //如果滾動距離>自定義的閾值忠寻,則認為需要跟隨手勢滾動了惧浴,此時開始攔截。
                if (yDiff > mTouchSlop && !mIsBeingDragged) {
                    mInitialMotionY = mInitialDownY + mTouchSlop;
                    mIsBeingDragged = true;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                mIsBeingDragged = false;
                mActivePointerId = -1;
                break;
        }

        return mIsBeingDragged;
    }

消費手勢 如下

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (canChildScrollUp()) {
            return false;
        }
        final int action = MotionEventCompat.getActionMasked(ev);
        int pointerIndex;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                mIsBeingDragged = false;
                break;
            case MotionEvent.ACTION_MOVE: {
                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (pointerIndex < 0) {
                    return false;
                }

                final float y = MotionEventCompat.getY(ev, pointerIndex);
                //設置滾動的阻力 0.5倍系數(shù)
                final int overscrollTop = (int) ((mInitialMotionY - y) * 0.5);
                if (mIsBeingDragged) {//消費滑動事件
                    if (overscrollTop > 0) {
                        moveSpinner(overscrollTop);
                    } else {
                        return false;
                    }
                }
                break;
            }

            case MotionEventCompat.ACTION_POINTER_DOWN: {
                pointerIndex = MotionEventCompat.getActionIndex(ev);
                if (pointerIndex < 0) {
                    Log.e(TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
                    return false;
                }
                mActivePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
                break;
            }

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                Log.i(TAG, "ACTION_UP");
                break;
            case MotionEvent.ACTION_UP:
                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (pointerIndex < 0) {
                    return false;
                }
                mIsBeingDragged = false;
                mActivePointerId = -1;
                finishSpinner();
                Log.i(TAG, "ACTION_UP");
                break;
        }
        return true;
    }

  // 手勢移動奕剃,滾動當前view衷旅,并切換底部關閉按鈕的狀態(tài)
    private void moveSpinner(int overscrollTop) {
        scrollBy(0, overscrollTop - getScrollY());
        updatePullUpViewState();
    }

    //手勢抬起,開始回彈動畫并回調是否關閉頁面
    private void finishSpinner() {
        if (getScrollY() > 0) {
            scrollBackAnimator(getScrollY());
        }
        //上拉回調纵朋。
        if (mPullUpListener != null) {
            mPullUpListener.pullUp(mCanClose);
        }
    }

至此已經(jīng)實現(xiàn)隨手勢上滑并回彈柿顶,效果如下


device-2017-12-04-164045.gif
  1. 慣性和回彈效果。
    搜索了很多資料都沒有特別好用的回彈效果操软。 這里通過WebView 的 overScrollBy 的回調方法拿到Webview滾動到底部時候可繼續(xù)滾動的距離嘁锯,并在此時增加一個繼續(xù)滑動的動效,模擬慣性
    首先要在PullupCloseLayout中拿到 該回調聂薪。 這里定義了一個監(jiān)聽器
public class MyWebview extends WebView {
    public MyWebview(Context context) {
        super(context);
    }

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

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        if (mOverscrollListener != null) {
            mOverscrollListener.overScroll(deltaX, deltaY,isTouchEvent);
        }
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }

    private PullUpOverScrollListerer mOverscrollListener;

    public void registerOverscrollListener (PullUpOverScrollListerer listener) {
        if (listener != null) {
            mOverscrollListener = listener;
        }
    }
    public void unRegisterOverscrollListener () {
        mOverscrollListener = null;
    }

}

這樣在PullupcloseLayout中

private void ensureView() {
        if (mTarget == null) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!child.equals(mPullUpView)) {
                    mTarget = child;
                    //判斷滾動的view為自己的實現(xiàn)了onScrollBy方法的 webview則注冊該監(jiān)聽
                    if (mTarget instanceof MyWebview) {
                        MyWebview webView = (MyWebview) mTarget;
                        webView.setOverScrollMode(View.OVER_SCROLL_NEVER);//去掉滑到底部的反饋水紋
                        webView.registerOverscrollListener(this);
                    }
                    break;
                }
            }
        }
    }

     ......

    @Override
    public void overScroll(int deltaX, int deltaY, boolean isTouchEvent) {
        if (!mIsBeingDragged && !canChildScrollUp() && deltaY > mTouchSlop && mCurrentMotionEvent != MotionEvent.ACTION_MOVE) {
            //1.5 倍慣性距離, 且最大滾動距離為滑動關閉的閾值
            deltaY = Math.min((int)(deltaY * 1.5), mPullUpCloseHeight);
            scrollBackAnimator((int) (deltaY * 1.5));
        }
    }

     .....

    //回彈動畫
    private void scrollBackAnimator(final int y) {
        Log.i(TAG, "scrollBackAnimator y =" + y);
        if (y == 0) {
            return;
        }
        if (mAnimator != null) {
            mAnimator.cancel();
            mAnimator = null;
        }
        mAnimator = ValueAnimator.ofFloat(0, 1);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float f = (float) animation.getAnimatedValue();
                scrollTo(0, (int) (y * (1 - f)));
            }

        });

        //long duration = SCROLL_MAX_DURATION_MS * y / mPullUpViewMaxHeight;
        mAnimator.setDuration(SCROLL_MAX_DURATION_MS);
        mAnimator.start();
    }

至此實現(xiàn)了慣性回彈家乘。 效果如下
device-2017-12-04-165546.gif

附github地址:
PullupCloseLayout

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市藏澳,隨后出現(xiàn)的幾起案子仁锯,更是在濱河造成了極大的恐慌,老刑警劉巖翔悠,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件业崖,死亡現(xiàn)場離奇詭異野芒,居然都是意外死亡,警方通過查閱死者的電腦和手機腻要,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門复罐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人雄家,你說我怎么就攤上這事≌凸觯” “怎么了趟济?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咽笼。 經(jīng)常有香客問我顷编,道長,這世上最難降的妖魔是什么剑刑? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任媳纬,我火速辦了婚禮,結果婚禮上施掏,老公的妹妹穿的比我還像新娘钮惠。我一直安慰自己,他們只是感情好七芭,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布素挽。 她就那樣靜靜地躺著,像睡著了一般狸驳。 火紅的嫁衣襯著肌膚如雪预明。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天耙箍,我揣著相機與錄音撰糠,去河邊找鬼。 笑死辩昆,一個胖子當著我的面吹牛阅酪,可吹牛的內容都是我干的。 我是一名探鬼主播卤材,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼遮斥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扇丛?” 一聲冷哼從身側響起术吗,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帆精,沒想到半個月后较屿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隧魄,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年隘蝎,在試婚紗的時候發(fā)現(xiàn)自己被綠了购啄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡嘱么,死狀恐怖狮含,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情曼振,我是刑警寧澤几迄,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站冰评,受9級特大地震影響映胁,放射性物質發(fā)生泄漏。R本人自食惡果不足惜甲雅,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一解孙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抛人,春花似錦弛姜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盅惜,卻和暖如春中剩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抒寂。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工结啼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屈芜。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓郊愧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親井佑。 傳聞我的和親對象是個殘疾皇子属铁,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內容