使用MotionLayout實現高德地圖bottomSheets效果

[復制鏈接]
來自: a12a15a05 分類: Android精品源碼 上傳時間: 2019-6-5 14:18:58
Tag:

項目介紹:

高德效果
高德效果

以下是我用motionlayout實現的效果,沒有達到絲滑流暢,優化就看小伙伴你了


demo.apk下載體驗

緣由

  • 使用高德地圖的時候看著這種體驗很好,隨后就想試試怎么達到類似效果

  • 最近正在看MotionLayout的東西,正好就嘗試嘗試

MotionLayout

  • 「譯」 MotionLayout 介紹 (Part I - IV) 系列教會你如何使用MotionLayout

  • 這里不做過多描述,總結一下在xml文件夾下創建xxscene.xml 主要用于描述場景動畫的關鍵幀和view狀態變化等

  • xxscene.xml內容包括 主要為3個關鍵內容:

  1. Transition 過渡

constraintSetStart:啟動約束場景

constraintSetEnd:結束約束場景

app:dragDirection="dragUp" 拽動(拖拉)

  1. KeyFrameSet關鍵幀集合

KeyAttribute關鍵幀

app:framePosition 位置,進度

app:target="@id/xxx 被描述的view id

  1. ConstraintSet 約束集合

 <Transition
        app:constraintSetEnd="@id/slideup_end"
        app:constraintSetStart="@id/slideup_start"
        app:duration="600"
        app:interpolator="easeIn">
        <OnSwipe
            app:dragDirection="dragUp"
            app:maxAcceleration="600"
            app:touchAnchorSide="top"
            app:touchAnchorId="@id/content"
           />
        <KeyFrameSet>
            <KeyAttribute
                android:alpha="0"
                app:framePosition="45"
                app:target="@id/sugar_title" />

            <KeyAttribute
                android:alpha="1"
                app:framePosition="90"
                app:target="@id/sugar_title" />
        ...
        </KeyFrameSet>
 </Transition>   
 
 <ConstraintSet android:id="@+id/slideup_start">

        <Constraint
        ···
        />
    ...
  </ConstraintSet>  

拆解過程

  • 高德地圖是上拉之后是三段式的,如圖所示

123

  • MotionLayout就只有一個初始約束和結束約束,沒有中間約束,如何實現這種三段式效果?

  • 答: 使用progress, MotionLayout自帶進度

  • 有進度,什么時候執行下一步操作,什么時候又執行上一步操作?

  • 答: 根據手勢,我們可以判斷用戶下一步是往上拉還是下拉,設定階段閥值,超過進入下一步,未超過回到之前一步

  • 高德地圖中,只有手觸碰到bottomview的時候手勢才有效果,所以還需要判斷touch事件是否在view范圍內

拆解完畢

實現過程

  • 設置閥值

    /**
     * 初始位置
     */
    public final static float PROGRESS_START = 0f;
    /**
     * 頂部閥值 
     */
    public final static float PROGRESS_TOP = 0.9f;
    /**
     * 低部閥值 
     */
    public final static float PROGRESS_BOTTOM = 0.1f;
    /**
     * 中間位置 
     */
    public final static float PROGRESS_MIDDLE = 0.6f;
    /**
     * 結束位置 
     */
    public final static float PROGRESS_END = 1.0f;
  • 重寫MotionLayoutonTouchEvent事件 ,使用hasMiddle布爾值判斷是否有中間狀態

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        float progress = getProgress();
        View viewGroup = findViewById(R.id.content);
        Rect mRect = new Rect();
        if (!mTouchStared) {
            viewGroup.getHitRect(mRect);
            mTouchStared = mRect.contains((int) event.getX(), (int) event.getY());
        }
        float endY;
        if (hasMiddle) {
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_CANCEL:
                    mTouchStared = false;
                    break;
                case MotionEvent.ACTION_DOWN:
                    startY = event.getY();
                    break;
                case MotionEvent.ACTION_UP:
                    endY = event.getY();
                    //手勢向下
                    if ((endY - startY) > 0) {
                        if (progress >= PROGRESS_TOP) {
                            mTouchStared = false;
                            handleProgress(PROGRESS_END);

                        }
                        if (progress < PROGRESS_TOP && progress >= PROGRESS_MIDDLE) {
                            handleProgress(PROGRESS_MIDDLE);
                        }
                        if (progress < PROGRESS_MIDDLE) {
                            handleProgress(PROGRESS_START);
                        }
                        //手勢向上
                    } else {
                        if (progress <= PROGRESS_BOTTOM) {
                            handleProgress(PROGRESS_START);
                        }
                        if (progress > PROGRESS_BOTTOM && progress <= PROGRESS_MIDDLE) {
                            handleProgress(PROGRESS_MIDDLE);
                        }
                        if (progress > PROGRESS_MIDDLE) {
                            mTouchStared = false;
                            handleProgress(PROGRESS_END);
                        }
                    }
                    return mTouchStared;
            }
        } else {
            if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || event.getActionMasked() == MotionEvent.ACTION_UP) {
                mTouchStared = false;
                return super.onTouchEvent(event);
            }
        }
        return mTouchStared && super.onTouchEvent(event);

    }
  • bottom_scene.xml

    1. 上拉超過頂部閥值PROGRESS_TOP之后標題出現在屏幕內,其余時候出現在屏幕外即可;

    2. 初始狀態這里把scaleXscaleY設為0.9結束設為了1,僅僅是為了過渡好看,你可以不用設置隨意修改即可

    3. 背景色過渡,最開始透明,結束為白色背景。中間過渡關鍵幀95變純白背景

結果和改進

  • 設置允許中間狀態后,之后進入下一步的過程,如圖,過于生硬

  • 改進方向

  1. 動畫應該是勻速的,然而setProgress(pro);卻是一步直達;

  2. 設置時間間隔勻速達到最后的進度即可,源碼已詳細注釋。改進之后見最上面效果圖;

 private void handleProgress(float progress) {
        //如果需要設置的進度和當前進度相同不做處理
        if (progress == getProgress()){
            return;
        }
        //動畫播放時間底值
        long time = 200;
        //進度間隔 >0 說明上拉 < 0說明下滑
        float interval = progress - getProgress();
        long startTime, endTime;
        if (interval > 0) {
            startTime = (long) (getProgress() * time);
            endTime = (long) (progress * time);
        } else {
            endTime = (long) (getProgress() * time);
            startTime = (long) (progress * time);
        }
        if (timeDisposable != null){
            timeDisposable.dispose();
        }
        //startTime 初始時間 endTime - startTime為次數 0為延遲時間 3為間隔 單位TimeUnit.MILLISECONDS 毫秒
        timeDisposable = Observable.intervalRange(startTime, endTime - startTime, 0, 3, TimeUnit.MILLISECONDS)
                .observeOn(Schedulers.io())
                .compose(((BaseActivity) getContext()).getProvider().bindToLifecycle())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new Function<Long, Long>() {
                    @Override
                    public Long apply(Long aLong) throws Exception {
                        //下滑需要反向
                        if (interval < 0) {
                            long interStart = aLong - startTime;
                            return endTime - interStart;
                        }
                        return aLong;
                    }
                })
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) throws Exception {
                        float pro = (Float.valueOf(aLong) / time);
                        setProgress(pro);
                    }
                });
    }

源碼已放入sugar demo中,sugar是我會長期維護的一個庫??????

[`

相關源碼推薦:

我來說兩句
所有評論(7)
天天bug 2019-6-5 18:55:32
不錯不錯,樓主辛苦了。。。
回復
應用安卓 2019-6-5 19:19:28
感謝分享,安卓巴士有你更精彩:lol
回復
Wsdtg 2019-6-5 19:20:28
感覺樓主很用心,辛苦啦~
回復
碼農創新者 2019-6-5 19:21:50
支持,感謝,祝巴士越來越好~
回復
Frank_z 2019-6-5 19:26:17
每次我都積極回帖的,想要安幣~
回復
gongags 2019-6-5 19:26:57
感覺樓主很用心,辛苦啦~
回復
wkdemo 2019-6-6 11:10:24
很給力,安卓巴士有你更精彩!
回復
421 1 0
代碼貢獻英雄榜
用戶名 下載數
聯系我們
首頁/微信公眾賬號投稿

帖子代碼編輯/版權問題

QQ:435399051,769657487

如何獲得代碼達人稱號

如何成為簽約作者

領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

两码中特期期