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

高德效果 android

高德效果

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

demo.apk下載體驗

原因

  • 使用高德地圖的時候看着這種體驗很好,隨後就想試試怎麼達到相似效果
  • 最近正在看MotionLayout的東西,正好就嘗試嘗試

MotionLayout

  • 「譯」 MotionLayout 介紹 (Part I - IV)系列教會你如何使用MotionLayout
  • 這裏不作過多描述,總結一下在xml文件夾下建立xxscene.xml 主要用於描述場景動畫的關鍵幀和view狀態變化等
  • xxscene.xml內容包括 主要爲3個關鍵內容:
  1. Transition 過渡

constraintSetStart:啓動約束場景github

constraintSetEnd:結束約束場景express

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

  1. KeyFrameSet關鍵幀集合

KeyAttribute關鍵幀bash

app:framePosition 位置,進度app

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>  
複製代碼

拆解過程

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

1
2
3

  • MotionLayout就只有一個初始約束和結束約束,沒有中間約束,如何實現這種三段式效果?
  • 答: 使用progress, MotionLayout自帶進度
  • 有進度,何時執行下一步操做,何時又執行上一步操做?
  • 答: 根據手勢,咱們能夠判斷用戶下一步是往上拉仍是下拉,設定階段閥值,超過進入下一步,未超過回到以前一步
  • 高德地圖中,只有手觸碰到bottomview的時候手勢纔有效果,因此還須要判斷touch事件是否在view範圍內

拆解完畢less

實現過程

  • 設置閥值
/**
     * 初始位置
     */
    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是我會長期維護的一個庫⬇️⬇️⬇️ide

🍯 Sugar 簡單便捷 快速開發Android項目,集合流行框架封裝

About me

License

Copyright 2019, wobiancao       
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at 
 
       http://www.apache.org/licenses/LICENSE-2.0 

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
   
複製代碼
相關文章
相關標籤/搜索