阅读完本文约需 10 分钟。

废不说,有图

逼真模拟便利贴撕页效果,在一些需要分步操作的场景非常适合。

用户的每一步操作都非常清晰,撕页的效果可以提醒用户上一步操作已经圆满完成了。

比如,现在用户在填写人口普查的信息,必填项繁多复杂,如果全部塞在一个页面就会显得臃肿,用户很容易漏填错填。

总不能一直说,用户是 撅着屁股看天----有眼无珠 吧?

在职场,甩锅给用户可能是个好策略,如同iphone12没有充电器一样环保。

但人总不能骗自己,甩完锅之后的还是得反省思考。

撕页效果就是值得参考的交互优化方案。

最后的效果也非常nice,真是牛逼他妈说牛逼----牛逼plus。

一、设计思路

怎样让用户的注意力集中在当前的操作步骤上?怎样让用户操作完当前步骤后获得一种干净利落的感觉,从而持续集中精力操作下一步骤?

现实生活中的打工人如何处理自己的待办事项?有很多打工人就习惯于使用便利贴,小小的便利贴上写满各种任务,完成后就撕页进行下一项。

将这一设计用于app中,可以给用户带来同样的体验。

二、实现方案

1、UI拆解

1.1 形状分析

初看可能会觉得无从下手,首先按照纸张状态进行分析,分为“正面内容全部显示”、“正面内容部分显示”、“反面内容全部显示”三种状态。

用辣眼睛的色块进行区分,红色代表正面,蓝色代表反面,如下图:

1.2 模型设计

按照以上三种状态,延伸定义卷角、内容显示区域以及它们的合集区域,对应蓝色区域、红色区域以及它们的合集

/**
* 内容路径
*/
var contentPath: Path = Path()
/**
* 纸张卷角
*/
var dogEaredPath: Path = Path()
/**
* 组合路径 = 内容路径+纸张卷角
*/
var unionPath: Path = Path()
//卷角大小
var crimpSize: Float = 0F

在翻页时,本质上就是各个区域路径的变化。在绘制时,只有两种情况,要么需要绘制正面,要么不绘制正面,临界点为下图

在调用者看来,控件状态分为三种情况:正面正常显示状态、正在撕的状态、已撕完状态

1.3 方案参考

模型设计完毕,组合各个区域路径,定义关键点,采用贝塞尔曲线进行弯曲部分的绘制,以内容显示区域为例

2、UI绘制

2.1 状态定义

按之前定义,为方便区分和控制,绘制状态有如下两种,代码如下

/**
* 卷角在内状态
*/
val DRAW_STATE_INNER = 0x01
/**
* 卷角在外状态
*/
val DRAW_STATE_OUTER = 0x02
var drawState = DRAW_STATE_INNER

2.2 形状绘制

2.2.1 内容区域形状绘制

定义A~G各个点。需要注意的是,在DRAW_STATE_OUTER 的绘制状态时,D点为右上角顶点。如此多的点不可能一一进行控制,不是时间管理大师就不要撩拨这么多个点了,否则非常复杂。

以D点为关键点,用D点的坐标推算出其它各点的坐标,从而控制好D点即可控制所有点位。

/**
* 坐标点
*/
var pointA: Coordinate = Coordinate()
var pointB: Coordinate = Coordinate()
var pointC: Coordinate = Coordinate()
var pointD: Coordinate = Coordinate()
var pointE: Coordinate = Coordinate()
var pointF: Coordinate = Coordinate()
var pointG: Coordinate = Coordinate()
//outer状态下的顶点坐标
var pointH: Coordinate = Coordinate()
var pointI: Coordinate = Coordinate()

有了各个坐标点,即可组合成各个区域路径,以内容区域路径为例

contentPath.reset()
contentPath.moveTo(0F, 0F)
contentPath.lineTo(pointA.x, pointA.y)
contentPath.quadTo(pointB.x, pointB.y, pointC.x, pointC.y)
contentPath.lineTo(pointD.x, pointD.y)
contentPath.lineTo(pointE.x, pointE.y)
contentPath.quadTo(pointF.x, pointF.y, pointG.x, pointG.y)
contentPath.lineTo(paperWidth, paperHeight)
contentPath.lineTo(paperWidth, 0F)
contentPath.close()

2.2.2 卷角区域绘制

卷角区域采用与内容区域的差集获得。这里涉及到某一段贝塞尔曲线中的某一点的计算,计算出卷角贝塞尔曲线中中线的坐标,相连闭合后与内容区域作差集操作。

val pointb = BazierUtils.getBezierPoint(pointA, pointB, pointC, 0.5F)
val pointf = BazierUtils.getBezierPoint(pointE, pointF, pointG, 0.5F)
dogEaredPath.reset()
dogEaredPath.moveTo(pointb.x, pointb.y)
dogEaredPath.lineTo(pointD.x, pointD.y)
dogEaredPath.lineTo(pointf.x, pointf.y)
dogEaredPath.lineTo(pointb.x, pointb.y)
dogEaredPath.close()
dogEaredPath.op(contentPath, Path.Op.DIFFERENCE)

2.3 渐变&阴影绘制

为突出立体效果,需要绘制卷角区域往内容区域上的投影和弯曲处的渐变

代码如下:

mPaint.setShadowLayer(20F, 10F, -10F, shadowColor)
drawPath(dogEaredPath, mPaint)
mPaint.shader = LinearGradient(pointD.x / 2F, pointD.y + pointD.x / 2F, pointD.x * 3 / 4F, pointD.y + pointD.x / 4F, shadowColor, Color.WHITE, Shader.TileMode.CLAMP)
drawPath(dogEaredPath, mPaint)

2.4 ViewGroup的onDraw方法

在使用ViewGroup时,onDraw方法的调用受限于mViewFlags中的WILL_NOT_DRAW标识,当ViewGroup中无子View时,不会执行onDraw方法中的逻辑,初始化时将其配置

setWillNotDraw(false)

2.5 裁切子View

ViewGroup中通过drawChild方法判断是否绘制子view,此方法可以讲处理过的Canvas传递给子View。在DRAW_STATE_OUTER 的绘制状态时,无需再绘制子View

override fun drawChild(canvas: Canvas?, child: View?, drawingTime: Long): Boolean {if (drawState == DRAW_STATE_INNER) {canvas?.save()canvas?.clipPath(childContentPath)val flag = super.drawChild(canvas, child, drawingTime)canvas?.restore()return flag} else {return true}
}

3、交互

3.1 手动控制

用户可以手动撕页

3.2 动画

控制D点坐标即可控制其它所有的点的坐标,动画时长设置成888毫秒,不求别的,就图个吉利,代码如下

/**
* 撕页动画
*/
var tearAnim: ValueAnimator? = null
/**
* 开始便利贴撕页动画
*/
fun startTearAnim() {tearAnim?.cancel()tearAnim = ValueAnimator.ofFloat(0F, paperWidth * 2.5F)with(tearAnim!!) {// 图个吉利duration = 888Linterpolator = AccelerateInterpolator()dStartX = pointD.xdStartY = pointD.yaddUpdateListener {offset = it.animatedValue as FloatpointD.x = dStartX + offsetpointD.y = dStartY - offset//边界控制if (pointD.x <= 0F) {pointD.x = 0F}if (pointD.y >= paperHeight) {pointD.y = paperHeight}executeCrimpSizeFunc(dStartX + offset)configPoint(pointD.x, pointD.y)combinePath()postInvalidate()}addListener(object : AnimatorListenerAdapter() {override fun onAnimationEnd(animation: Animator?) {super.onAnimationEnd(animation)state = STATE_TEAREDonTearStateChangeListener?.onTearStateChanged(state)}})start()}
}

3.4 边界控制

当用户手指移动超出边界时,必须强制赋值,否则效果拉胯

//边界控制
if (pointD.x <= 0F) {pointD.x = 0F
}
if (pointD.y >= paperHeight) {pointD.y = paperHeight
}

3.5 细节设计

注意,在撕页过程中,卷角的弯曲大小是有变化的,因此在配置各个坐标点的时候,将卷角大小考虑进去

4、扩展

通用方案参考

在这个控件中,便利贴是正方形的,如果想要方案更加通用,适用于所有翻页效果,则可以按照下图的思路进行设计,原则也是同样的,控制其中的一个点从而控制所有的点。

模型设计是这样的,任选abcd矩形区域内的一点D,那么点D对应的纸张内容显示的绘制区域为蓝色部分,线段BC为点D、点d的中垂线,△BCd与△DBC相同,点A为线段Dd的中点,点J、K、L、M为线段DA的中垂线与各个线段的交点。

已知点D、点d的坐标即可推算出其它所有的点的坐标,由于d点为顶点,无需变动,因此控制D点即可控制所有的点。

5、优化

优化思路有很多,针对低端机器的形状绘制,可采用SurfaceView,对一些线段进行抽点绘制,避免路径的相交、合并操作。

当然,这里仅作抛砖引玉。

三、后记

此篇为《隔壁产品馋哭》系列文章的完结篇,一路坚持下来,很不容易。

但我就算写再多的怎样艰难奋进的话语,如:

苦苦构思各个交互设计;

深夜还在想控件的实现方案;

文章推广困难重重;

写文章被同事嘲讽…...

可是这些和读者又有什么关系呢?只能让读者发挥同理心,感受到系列文章创作的不易,从而点赞转发评论……甚至打赏!

你,感受到了么?

后会有期。

控件放在了gitee上,点击“阅读原文”获取

技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

推荐阅读:

音视频面试基础题

OpenGL ES 学习资源分享

开通专辑 | 细数那些年写过的技术文章专辑

NDK 学习进阶免费视频来了

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

便利贴撕页效果,隔壁产品都馋哭了相关推荐

  1. 花里胡哨的3D翻页卡片,隔壁产品都馋哭了

    阅读完本文约需7分钟. 废不说,看图,有图有** 带有立体纵深的卡片翻页效果,稍加组合和颜色变化就可以搭配出多种不同的风格,如: 比赛比分牌 卡片翻页时钟 一.设计思路 如何使得数字的变化更为灵动?3 ...

  2. 做个锤子的开关,隔壁产品都馋哭了

    码个蛋(codeegg) 第1049 次推文 作者:彭也 链接:https://zhuanlan.zhihu.com/p/193117308 前言 废话不多,有图有** 虽然老罗的锤子处于倒闭与即将倒 ...

  3. android 控件随手指移动_液体流动控件,隔壁产品都馋哭了

    作者:彭也 链接: https://www.jianshu.com/p/4f0844c72e8a 模拟液体流动的展开特效,适合一些需要侧边展开进行辅助说明的页面,如用户在填写某个表单,需要操作很多步骤 ...

  4. android 根据bounds坐标进行点击操作_炫酷的Android时钟UI控件,隔壁产品都馋哭了...

    废话不多说,先上效果效果酷炫,动画丰富,效果爆炸boom-设计思路看腻了市面上各种丑陋难看的时钟控件,是时候整点新活!将现实生活中的摆钟圆形表盘设计.电子手表的数显表盘设计抽象出来,提取出" ...

  5. 程序员为这支笔掰头10个月,隔壁小学生都馋哭了

    鱼羊 萧箫 发自 凹非寺 量子位 报道 | 公众号 QbitAI 见证一款互联网风格的新智能硬件诞生,是一种怎样的体验? 产品经理和软硬件工程师们的面对面battle,总是其中见(xi)怪(wen)不 ...

  6. 用了5年的旧笔记本不要丢,1/4新机价格升级机器学习战斗本,隔壁研究员都馋哭了...

    大数据文摘出品 来源:medium 编译:zeroinfinity.Andy 高性能的硬件配置在机器学习研究中是枪炮一般的存在.面对大规模数据和多层结构算法,普通的计算机根本无法胜任. 但是,高配置的 ...

  7. 全国最好吃的大学食堂来啦!隔壁小孩都馋哭了!

    考研择校千千万 有人看学术实力.有人看院校排名 有人看导师水准.有人看未来发展 但俗话说的好 民以食为天 不吃饱怎么搞学习? 全国最好吃的大学食堂 今天给大家盘一下 凭食堂打下优秀口碑 干饭人看完别流 ...

  8. 擎创动态 | 1024 这么过,隔壁公司都馋哭了

    左手咖啡杯,右手耍魔方 身穿潮流衣,眼戴金丝镜 发型酷帅炫,多才又多艺 百变外观,精致容颜 可盐可甜,又御又萌 技术高超,能力卓越 没错,描述的这种靓仔靓女 就是擎创的中流砥柱--程序员 1024,是 ...

  9. 电脑主机,晚上就煎肉,把隔壁宿舍都馋哭了!

    点击上方"大鱼机器人",选择"置顶/星标公众号" 福利干货,第一时间送达! 本文转自网络,如有侵权请联系我们删除 ------------------分割线-- ...

最新文章

  1. 分析师洞察:边缘数据中心的UPS系统
  2. vue/cli 3.0 与 2.0脚手架怎样mock数据
  3. python 如何查看列表(List)的维度? (需要将List转换成numpy数组)
  4. 联系 Contact
  5. 华为云网络覆盖全球2500+站点,打造高品质、低成本接入体验
  6. html网页访问计数器,HTML添加网站计数器(Cookie)
  7. Exchange Server 2003邮件服务器系统的基本部署思路
  8. 鸟哥的linux私房菜简答题答案,《鸟哥的Linux私房菜》7章 Linux文件与目录管理 习题答案...
  9. HTTP Status 500 - The absolute uri: http://java.sun.com/jsp/jstl/core cannot-报错解决方法
  10. 波士顿动力再发逆天机器人视频:倒立、360°旋转、空中劈叉,真是秀儿
  11. PyQt5的信号和槽
  12. c语言ascii码表_零基础学C语言——变量、常量与数据类型
  13. SharePoint 通过控制上传下载对文件进行加密解密(二)
  14. w ndows 10关机快捷键,win10系统
  15. git中的origin
  16. 安全管理实务之一:补丁管理(转)
  17. 解决:Unknown column ‘字段名‘ in ‘field list‘报错
  18. php使用addons,think addons教程
  19. 上财计算机专业全国排名,2021软科财经类大学排名,上海财经遥遥领先,东财仅排第六...
  20. 投资日记2015.6

热门文章

  1. 60行代码爬取知乎神回复
  2. Linux/Centos: 服务器TIME_WAIT和CLOSE_WAIT区别及解决方案
  3. Sequelize 数据迁移
  4. 国内首个写作机器人上岗,1秒完成文章写作,就问你怕不怕!
  5. MediaPlayer提示“无法播放视频”
  6. 简单的c#winform画图工具
  7. C语言一战式学习网址链接
  8. 放纵,正在毁掉这一代年轻人
  9. 奇安信技术支持实习生面试
  10. 小明的调查作业java_小明的调查作业