高德效果
- 搜到的一個效果驮吱,附上鏈接,用的behavior茧妒,我沒下源碼看,因?yàn)槲抑皇窍雵L試另外一種方式左冬。具體效果暫不知
Android 仿高德地圖可拉伸的BottomSheet
以下是我用motionlayout實(shí)現(xiàn)的效果桐筏,沒有達(dá)到絲滑流暢,優(yōu)化就看小伙伴你了
demo.apk下載體驗(yàn)
緣由
- 使用高德地圖的時候看著這種體驗(yàn)很好拇砰,隨后就想試試怎么達(dá)到類似效果
- 最近正在看MotionLayout的東西梅忌,正好就嘗試嘗試
MotionLayout
-
「譯」 MotionLayout 介紹 (Part I - IV)
系列教會你如何使用MotionLayout - 這里不做過多描述,總結(jié)一下在xml文件夾下創(chuàng)建
xxscene.xml
主要用于描述場景動畫的關(guān)鍵幀和view狀態(tài)變化等 -
xxscene.xml
內(nèi)容包括 主要為3個關(guān)鍵內(nèi)容:
-
Transition
過渡
constraintSetStart
:啟動約束場景
constraintSetEnd
:結(jié)束約束場景
app:dragDirection="dragUp"
拽動(拖拉)
-
KeyFrameSet
關(guān)鍵幀集合
KeyAttribute
關(guān)鍵幀
app:framePosition
位置,進(jìn)度
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>
拆解過程
- 高德地圖是上拉之后是三段式的,如圖所示
-
MotionLayout
就只有一個初始約束和結(jié)束約束涮总,沒有中間約束沟娱,如何實(shí)現(xiàn)這種三段式效果? - 答: 使用progress,
MotionLayout
自帶進(jìn)度 - 有進(jìn)度捌锭,什么時候執(zhí)行下一步操作,什么時候又執(zhí)行上一步操作?
- 答: 根據(jù)手勢尸诽,我們可以判斷用戶下一步是往上拉還是下拉,設(shè)定階段閥值盯另,超過進(jìn)入下一步性含,未超過回到之前一步
- 高德地圖中,只有手觸碰到bottomview的時候手勢才有效果鸳惯,所以還需要判斷touch事件是否在view范圍內(nèi)
拆解完畢
實(shí)現(xiàn)過程
- 設(shè)置閥值
/**
* 初始位置
*/
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;
/**
* 結(jié)束位置
*/
public final static float PROGRESS_END = 1.0f;
- 重寫
MotionLayout
的onTouchEvent
事件 ,使用hasMiddle
布爾值判斷是否有中間狀態(tài)
@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
之后標(biāo)題出現(xiàn)在屏幕內(nèi)商蕴,其余時候出現(xiàn)在屏幕外即可; - 初始狀態(tài)這里把
scaleX
和scaleY
設(shè)為0.9結(jié)束設(shè)為了1悲敷,僅僅是為了過渡好看究恤,你可以不用設(shè)置隨意修改即可 - 背景色過渡,最開始透明后德,結(jié)束為白色背景部宿。中間過渡關(guān)鍵幀95變純白背景
結(jié)果和改進(jìn)
- 設(shè)置允許中間狀態(tài)后,之后進(jìn)入下一步的過程瓢湃,如圖理张,過于生硬
- 改進(jìn)方向
- 動畫應(yīng)該是勻速的,然而
setProgress(pro);
卻是一步直達(dá)绵患; - 設(shè)置時間間隔勻速達(dá)到最后的進(jìn)度即可雾叭,源碼已詳細(xì)注釋。改進(jìn)之后見最上面效果圖落蝙;
private void handleProgress(float progress) {
//如果需要設(shè)置的進(jìn)度和當(dāng)前進(jìn)度相同不做處理
if (progress == getProgress()){
return;
}
//動畫播放時間底值
long time = 200;
//進(jìn)度間隔 >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為次數(shù) 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是我會長期維護(hù)的一個庫??????
?? Sugar 簡單便捷 快速開發(fā)Android項(xiàng)目暂幼,集合流行框架封裝
About me
- Email: a420245103@gmail.com
- 掘金: https://juejin.im/user/568be89760b24d71fed19d2b
- 簡書: http://www.reibang.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.