登录 立即注册
安币:

安卓巴士 - 安卓开发 - Android开发 - 安卓 - 移动互联网门户

使用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
很给力,安卓巴士有你更精彩!
回复
上传代码
查看数:320 收藏数:2 下载数: 点赞数:1
代码贡献英雄榜
用户名 下载数
联系我们
首页/微信公众账号投稿

帖子代码编辑/版权问题

QQ:435399051,769657487

如何获得代码达人称号

如何成为签约作者

联系我们
关闭
合作电话:
15618560077
Email:
805941275@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| 广东互联网违法和不良信息举报中心|中国互联网举报中心|下载客户端|申请友链|手机版|站点统计|安卓巴士 ( 粤ICP备15117877号 )

快速回复 返回顶部 返回列表