高德效果
高德效果
- 搜到的一個效果,附上鏈接,用的behavior,我沒下源碼看,因為我只是想嘗試另外一種方式。具體效果暫不知
Android 仿高德地圖可拉伸的BottomSheet
以下是我用motionlayout實現的效果,沒有達到絲滑流暢,優化就看小伙伴你了
image
demo.apk下載體驗
緣由
- 使用高德地圖的時候看著這種體驗很好,隨后就想試試怎么達到類似效果
- 最近正在看MotionLayout的東西,正好就嘗試嘗試
MotionLayout
-
「譯」 MotionLayout 介紹 (Part I - IV)
系列教會你如何使用MotionLayout - 這里不做過多描述,總結一下在xml文件夾下創建
xxscene.xml
主要用于描述場景動畫的關鍵幀和view狀態變化等 -
xxscene.xml
內容包括 主要為3個關鍵內容:
-
Transition
過渡
constraintSetStart
:啟動約束場景
constraintSetEnd
:結束約束場景
app:dragDirection="dragUp"
拽動(拖拉)
-
KeyFrameSet
關鍵幀集合
KeyAttribute
關鍵幀
app:framePosition
位置,進度
app:target="@id/xxx
被描述的view id
-
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范圍內
拆解完畢
實現過程
- 設置閥值
/**
* 初始位置
*/
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;
- 重寫
MotionLayout
的onTouchEvent
事件 ,使用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
- 上拉超過頂部閥值
PROGRESS_TOP
之后標題出現在屏幕內,其余時候出現在屏幕外即可; - 初始狀態這里把
scaleX
和scaleY
設為0.9結束設為了1,僅僅是為了過渡好看,你可以不用設置隨意修改即可 - 背景色過渡,最開始透明,結束為白色背景。中間過渡關鍵幀95變純白背景
結果和改進
- 設置允許中間狀態后,之后進入下一步的過程,如圖,過于生硬
image
- 改進方向
- 動畫應該是勻速的,然而
setProgress(pro);
卻是一步直達; - 設置時間間隔勻速達到最后的進度即可,源碼已詳細注釋。改進之后見最上面效果圖;
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是我會長期維護的一個庫??????
?? Sugar 簡單便捷 快速開發Android項目,集合流行框架封裝
About me
- Email: a420245103@gmail.com
- 掘金: https://juejin.im/user/568be89760b24d71fed19d2b
- 簡書: http://www.lxweimin.com/u/114bbbfb977f
- apkbus: http://www.apkbus.com/?496060
- github: https://github.com/wobiancao
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.