你见过微信侧滑返回的联动效果,但开门效果、百叶窗效果见过吗?

SmartSwipe是一个Android侧滑处理框架,它封装了对控件侧滑事件(上/下/左/右4个方向滑动的手势事件)的捕获、分发及多点交替滑动的处理,基于SmartSwipe我们可以为控件添加各种你想要的侧滑效果。

先来看看它能做些什么吧!

如果已经了解SmartSwipe的功能,只是想了解他的实现原理
可跳过第一节,直接看第二节的原理介绍

一、 用法及演示

1.1 一行代码实现全局侧滑返回

//仿手机QQ的手势滑动返回
SmartSwipeBack.activityStayBack(application, null);     
//仿微信带联动效果的透明侧滑返回
SmartSwipeBack.activitySlidingBack(application, null);  
//侧滑开门样式关闭activity
SmartSwipeBack.activityDoorBack(application, null);     
//侧滑百叶窗样式关闭activity
SmartSwipeBack.activityShuttersBack(application, null); 
//仿小米MIUI系统的贝塞尔曲线返回效果
SmartSwipeBack.activityBezierBack(application, null);

侧滑返回的更多用法请戳 这里

效果图:

侧滑返回效果

1.2 一行代码让页面动起来

//为控件添加仿iOS的弹性留白效果:
//当纵向不能滚动(或滚动到顶/底)时,若继续拖动,则UI呈现弹性留白效果,释放后平滑恢复
SmartSwipe.wrap(view)
    .addConsumer(new SpaceConsumer())
    .enableVertical();

效果图:

弹性留白效果

1.3 一行代码让页面具有弹性

//为控件添加仿MIUI的弹性拉伸效果:
//当纵向不能滚动(或滚动到顶/底)时,若继续拖动,则UI呈现弹性拉伸效果,释放后平滑恢复
SmartSwipe.wrap(view)
    .addConsumer(new StretchConsumer())
    .enableVertical();

效果图:

弹性拉伸效果

1.4 一行代添加下拉刷新

//xxxMode第二个参数为false,表示工作方向为纵向:下拉刷新&上拉加载更多
//如果第二个参数设置为true,则表示工作方向为横向:右拉刷新&左拉加载更多
SmartSwipeRefresh.drawerMode(view, false).setDataLoader(loader);
SmartSwipeRefresh.behindMode(view, false).setDataLoader(loader);
SmartSwipeRefresh.scaleMode(view, false).setDataLoader(loader);
SmartSwipeRefresh.translateMode(view, false).setDataLoader(loader);

下拉刷新的更多用法请戳 这里

样式 效果图
drawerMode
drawerMode
behindMode
behindMode
scaleMode
scaleMode
translateMode
translateMode

1.5 一行代码添加滑动菜单

SmartSwipe.wrap(view)
    //添加抽屉效果,其效果与DrawerLayout相似
    //  DrawerLayout只支持左右2个方向,而DrawerConsumer支持上下左右4个方向
    .addConsumer(new DrawerConsumer())  
    //设置横向(左右两侧)的抽屉为同一个view(常见的侧滑显示删除按钮的功能)
    .setHorizontalDrawerView(buttonsViewGroup) 
    .setScrimColor(0x2F000000) //设置遮罩的颜色
    .setShadowColor(0x80000000) //设置边缘的阴影颜色
    ;

效果图:

滑动菜单

1.6 一行代码添加具有联动效果的滑动菜单

SmartSwipe.wrap(view)
    .addConsumer(new SlidingConsumer())
    .setRelativeMoveFactor(0.3F) //联动系数
    .setHorizontalDrawerView(buttonsView)
    .setScrimColor(0x2F000000)
    ;

效果图:

可联动滑动菜单

1.7 炫酷的封面

SmartSwipe.wrap(coverView)
    .addConsumer(new ShuttersConsumer()) //百叶窗效果
    .setScrimColor(0xAF000000)
    .enableAllDirections()
    .addListener(new SimpleSwipeListener() {
        @Override
        public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            //封面打开后自动隐藏或移除
            wrapper.setVisibility(View.GONE);
        }
    });

效果图:

百叶窗封面
SmartSwipe.wrap(coverView)
    .addConsumer(new DoorConsumer()) //开门效果
    .setScrimColor(0xAF000000)
    .enableAllDirections()
    .addListener(new SimpleSwipeListener() {
        @Override
        public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            //封面打开后自动隐藏或移除
            wrapper.setVisibility(View.GONE);
        }
    });

效果图:

开门封面

关于封面的更多设置请参考: Demo

二、实现原理

2.1 先介绍一下ViewDragHelper

ViewDragHelper是Android官方支持库中有一个工具类。它可以帮助我们处理控件的拖拽:先创建一个自定义ViewGroup,将被拖动的控件添加到这个自定义ViewGroup中,并用ViewDragHelper来处理控件的拖拽。

ViewDragHelper的主要作用是:拦截父容器的touch事件,捕获一个子控件来进行拖拽,通过改变这个子控件的left和top来将其在父容器中重新定位,从而达到拖拽的效果。

在官方支持库中,滑动抽屉相关的SlidingPaneLayout和DrawerLayout,以及CoordinatorLayout布局相关的BottomSheetBehavior和SwipeDismissBehavior,都能看到ViewDragHelper的身影。

但是,ViewDragHelper的名称也表明它就是用来处理拖拽的,拖拽的对象必须是一个子View,在拖拽的过程中需要改变子控件的left和top,对于一些没有子View被拖拽的侧滑效果(例如:MIUI系统的贝塞尔曲线侧滑返回效果、手机QQ的侧滑返回效果及MIUI官方app中的普遍使用了的弹性拉伸效果等等),却有点力有不逮。

2.2 借鉴ViewDragHelper实现侧滑处理

针对侧滑这个手势,我们能不能将它的概念抽象一下,到底侧滑指的是什么呢?

  • 狭义侧滑:从屏幕的某个边缘开始向着远离该边缘的方向滑动
  • 广义侧滑:手指在屏幕上按下之后向着某个方向滑动

我的理解是,广义侧滑包含狭义侧滑,只不过是触发区域是否在屏幕边缘的区别罢了。

既然侧滑手势能被明确地抽象出来,那么我们是否可以借鉴ViewDragHelper的事件拦截思路将它做这样的封装?

对被侧滑控件的touch事件进行拦截分析,确认是否将其捕获作为侧滑手势
然后计算好侧滑的实时位移(手指滑动的位移,而不是不依赖于View的left与top)
再通过策略模式(Strategy Pattern)使用不同的策略不断消费侧滑的位移来进行侧滑效果的UI呈现。

答案是肯定的!

2.3 SmartSwipe的实现原理

SmartSwipe在ViewDragHelper的基础上,将它对子View的捕获及移动处理改造成对父View自身触摸事件的定性(能否及是否捕获)、定向(捕获的事件所触发的侧滑方向)及定位(事件捕获之后在侧滑方向上移动的距离),并将侧滑距离交由SwipeConsumer来消费,SwipeConsumer根据侧滑距离的变化对控件布局进行相应的改变。

SmartSwipe的封装思路如下:

  • 用一个ViewGroup将需要处理侧滑事件的控件View包裹起来(被包裹起来的控件作为它的contentView
  • 可以为这个ViewGroup添加一些附属控件(如:滑动抽屉
  • 拦截这个ViewGroup的touch事件,并将touch事件转换为侧滑距离交给SwipeConsumer进行消费
  • SwipeConsumer根据侧滑距离的变化对控件布局进行相应的改变
  • 通过继承SwipeConsumer,用不同的方式来改变控件布局(例如:对contentView及附属控件的位置、缩放、透明等进行改变),从而实现各种侧滑的效果。

于是,侧滑的手势事件识别及滑动距离计算的工作在框架内部就统一完成了,至于根据侧滑距离来实现各种不同的UI呈现效果,就可以很方便地通过继承SwipeConsumer来实现了。

2.4 如何创建自定义SwipeConsumer?

以框架内置的仿MIUI系统应用中弹性拉伸效果的实现为例

根据侧滑距离,对contentView进行缩放和平移,从而实现弹性拉伸效果

代码如下:

public class StretchConsumer extends SwipeConsumer {
    @Override
    public void onDetachFromWrapper() {
        super.onDetachFromWrapper();
        View contentView = mWrapper.getContentView();
        if (contentView != null) {
            contentView.setScaleX(1);
            contentView.setScaleY(1);
            contentView.setTranslationX(0);
            contentView.setTranslationY(0);
        }
    }

    @Override
    public void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy) {
        View contentView = mWrapper.getContentView();
        if (contentView != null) {
            if (distanceXToDisplay >= 0 && isLeftEnable() || distanceXToDisplay <= 0 && isRightEnable()) {
                contentView.setScaleX(1 + Math.abs((float) distanceXToDisplay) / mWidth);
                contentView.setTranslationX(distanceXToDisplay / 2F);
            }
            if (distanceYToDisplay >= 0 && isTopEnable() || distanceYToDisplay <= 0 && isBottomEnable()) {
                contentView.setScaleY(1 + Math.abs((float) distanceYToDisplay) / mHeight);
                contentView.setTranslationY(distanceYToDisplay / 2F);
            }
        }
    }
}

以上就是实现弹性拉伸效果的全部代码,很简单,不是吗?

它的使用方式同样简单:

SmartSwipe.wrap(view) //指定目标控件
    .addConsumer(new StretchConsumer()) //添加弹性拉伸效果
    .enableVertical(); //指定工作方向为:上、下2个方向

再来看看仿手机QQ侧滑返回的效果如何实现

手机QQ侧滑时UI没有任何变化
在手指释放时,根据滑动的方向和速率来决定是否finish当前Activity

代码如下:

public class StayConsumer extends SwipeConsumer {
    private int mMinVelocity = 1000;

    public StayConsumer() {
        //不能通过滑动距离判断是否需要打开
        setOpenDistance(Integer.MAX_VALUE)
                .setMaxSettleDuration(0); //打开时无需动画,时间置为0
    }

    @Override
    protected void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy) {
        //滑动时不需要对contentView做任何改变
    }

    @Override
    public void onSwipeReleased(float xVelocity, float yVelocity) {
        //在释放时,根据速率和方向来决定是否打开
        if (Math.abs(xVelocity) > Math.abs(yVelocity)) {
            if (mDirection == DIRECTION_LEFT && xVelocity >= mMinVelocity || (mDirection == DIRECTION_RIGHT && xVelocity <= -mMinVelocity)) {
                //置为打开状态
                mCurSwipeDistanceX = getSwipeOpenDistance();
                mProgress = 1;
            }
        } else {
            if (mDirection == DIRECTION_TOP && yVelocity >= mMinVelocity || (mDirection == DIRECTION_BOTTOM && yVelocity <= -mMinVelocity)) {
                //置为打开状态
                mCurSwipeDistanceY = getSwipeOpenDistance();
                mProgress = 1;
            }
        }
        super.onSwipeReleased(xVelocity, yVelocity);
    }

    public int getMinVelocity() {
        return mMinVelocity;
    }

    //支持使用者设置最低速率的阈值
    public StayConsumer setMinVelocity(int minVelocity) {
        if (minVelocity > 0) {
            this.mMinVelocity = minVelocity;
        }
        return this;
    }
}

是不是也很简单!

点击这里了解创建自定义SwipeConsumer的详细步骤

小结

本文介绍了SmartSwipe侧滑处理框架的使用方式及实现原理,并通过2个示例介绍了自定义侧滑效果的方法。

只是文中的示例是较为简单的侧滑效果,至于复杂的侧滑效果实现介绍,如果读者们需要的话,我接下来另外写一篇文章来单独介绍,如有需要,请给我留言!

另外,Star一个开源项目是对它最好的鼓励和支持!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容