前言
我先承认我吹牛了,标题是近千Star(虽然只有700),且登顶Trending榜(当时排Kotlin分类第三名)。但是我仍然有一颗上进的心,吹牛是无罪的!
大家且先放下手中40m的大刀,让我先跑39米,看看我这次能给大家整个什么花活再决定是否砍我。
还是同样的道理,我知道没图是骗不了人的,先放图,看看最终实现的效果是否真的那么炫酷。
怎么样是不是还算有那么一丢丢炫酷的感觉??
这个效果是一个自定义ViewGroup,相信有很多小伙伴平常写自定义View还是较多的,但是自定义ViewGroup就比较少了。主要啊,自定义ViewGroup考虑的东西太多了,又是什么测量,又是什么布局,有的时候还得考虑滑动冲突,真的是要多麻烦有多麻烦。
不过,本篇就带你一点点分析效果入手,到实现效果,再到处理这些麻烦事,由浅入深的让你也能轻松学会自定义ViewGroup。妈妈再也不用担心我不会自定义ViewGroup了。
作者:大神Mlx
链接:https://juejin.im/post/6873653741627637774
高级UI进阶B站视频讲解:https://www.bilibili.com/video/BV1fE411P7PA
话不多说,放码过来。
这是本篇特效的Github地址:github.com/MlxChange/W…
特效分析
由于是自定义ViewGroup,效果还是蛮复杂的。我刚开始看到这个原效果图的时候,我内心是拒绝的,不能说你UI设计那么炫酷,根本不考虑我们程序员的辛苦把,坚决不做这种东西!
于是我和UI打了一架,谁赢了听谁的。现在看到实现的效果,我只想说 真香。
好了,我不皮了。我们认真来看一下效果把。
首先,这个效果可以看到有很多的页面,就像列表一样,记得之前看过一个效果就是探探那种效果,左滑喜欢,右滑不喜欢和这个类似。所以我起初想的是看能不能从自定义LayoutManager
或者自定义一个View继承于RecyclerView
入手,后来我还是放弃了,因为它们不太容易实现预览到下一个界面这种效果,最终决定了自定义一个ViewGroup去实现。
这个效果我觉得最特殊的在于能够通过拖拽看到下一个界面的内容,就像拉窗帘一样,并且划过中心之后,能够自动的滑动到另一侧,并且有反弹效果,然后显示下一个页面。当下一个页面完全显示的时候,此时拖拽按钮自动生成,再次预览下下个页面。而且按钮也能按回去,按回去以后反方向就会生成一个新的按钮,显示上一页的内容。
综上所述,我总结了几点:
- 是一个自定义
ViewGroup
- 子View堆叠显示,就像列表一样可以自定义子View的内容,并且能预览到下一页或者上一页的内容。
- 有一个拖拽的按钮,在往外拉的时候按钮也在不断的变大,缩回去的时候按钮在不断变小,并且是一个很圆滑的效果
- 在完全滑动到另一侧的时候,还会反弹几下
其实我觉得其他的都比较好实现,比较麻烦的就是这个拖拽按钮,我决定从拖拽按钮入手。
拖拽按钮
虽然讲的是自定义ViewGroup,但是做自定义View不能一上来就开干,我先写个自定义View,先看看这个拖拽按钮能不能实现,如果这个都不能实现的话,那后面的就找个借口甩锅给UI不是就不需要写了吗。
我先看看这个拖拽按钮是怎么实现的。
先从左边的按钮开始看,毕竟屏幕的原点在左上角嘛。
首先它距离左边有一小点距离,从上到下就是一条线,只是中间有个圆不圆,尖不尖的突起。这个突起我先不管它,我先画出这个线。因为是测试,我创建一个TestView
class TestView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {}
然后是画线。我们不妨先思考一下,如何画这个线。在效果图中虽然确实是一条线,但是它左边也有内容显示,所以我觉得更像是一个细长的矩形,只不过这个细长的矩形中出了一个叛徒(突起)。所以画线变成了画矩形,而且由于这个叛徒的存在,简单的canvas画矩形显然是无法在后续的矩形上添加这个叛徒的,所以得用那个另外一个方式画矩形,那么是什么呢?
没错,就是Path
,Path
一样可以达到同样的效果。所以开干。
画矩形
定义一个画笔和Path
var path= Path()
var paint =Paint()
init {
paint.color=Color.RED //为了方便辨识,我们定义红色
paint.style=Paint.Style.STROKE
paint.isAntiAlias=true
}
矩形怎么画呢?其实和我们在纸上画一模一样,从原点画个线到右边,然后在画直线到下面,再往左画一个直线,最后和原点闭合。如此一个矩形就出来了。灵魂画师就是我本人了。
这个画法其实Path很好的帮我们实现,并且我们为了方便,还是定义一个中心点centerX和centerY
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
path.lineTo(100f,0f)//100只是个测试距离,画上图步骤1
path.lineTo(100f,centerY*2)//画步骤2
path.lineTo(0f,centerY*2)//画步骤3
path.close()//闭合,也就是画步骤4
canvas.drawPath(path,paint)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
centerX= (w/2).toFloat()//定义屏幕中心X值
centerY= (h/2).toFloat()//定义屏幕中心Y值
}
那我们看一下效果吧
emmm看起来有点意思了,不过状态栏是什么鬼?啊 这个不要管他啦,就是透明状态栏而已啦。
画突起
那我们现在考虑一下如何画这个突起呢?
其实突起嘛,在纸上画应该是这样的
你看如果我们在纸上画还是很简单的,右边的线那里只需要多个突起即可。多了三步,看看代码怎么实现吧
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
path.lineTo(100f,0f)//上图第一步
path.lineTo(100f,centerY-200)//上图第二步
path.lineTo(180f,centerY)//第三步
path.lineTo(100f,centerY+200)//第四步
path.lineTo(100f,centerY*2)//第五步
path.lineTo(0f,centerY*2)//第六步
path.close()//第七步
canvas.drawPath(path,paint)
}
效果如何呢?
看起来有点意思了哦~不过人家那个效果看起来很圆滑,你这个尖尖的,有点不大一样啊。
这位兄台,你说的很有道理,不过圆的我不会画啊。咋办呢?就只有尖尖才能维持得了生活这样子,里面的老哥个个都是人才说话又好听,啊等等不对,走错片场了。
圆形是什么呢?是曲线,没错吧?在计算机中我们该如何画曲线呢?我们看看有没有小伙伴知道啊,我等你十分钟
啊,十分钟过去了,没人知道我就说答案了哦
没错,就是贝塞尔曲线!
什么,你没听过贝塞尔曲线?老哥,你out了。
贝塞尔曲线是计算机中模拟曲线的一种算法,通过方程balabalabla,省略一堆定义…
简而言之,贝塞尔曲线就是帮我们画曲线的,通过位置点和控制点来确定曲线。如果不会的小伙伴,我建议先学习一下关于贝塞尔曲线的基础用法。
我们看一下二阶贝塞尔曲线的方法把
/**
* 从上一个点开始,绘制二阶Bezier曲线
* (x1,y1)为控制点, (x2,y2)为终点
*/
public void quadTo(float x1, float y1, float x2, float y2) ;
那么话说回来,用贝塞尔曲线画,应该怎么画呢?我们继续尝试在纸上画一下试试
贝塞尔曲线就对应着步骤三了.起始点就是A点了,控制点就是B点了,终点那应该就是C点了。
这个点的坐标呢?我们假设步骤一的长度为100,a点到B点在X方向上的偏移是100,B点的Y坐标就是中心坐标的话,B点的坐标就是(100+100,centerY)。然后A到C的距离我们设定为200,这些应该不难理解
我们代码上实践一下
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
path.lineTo(100f,0f)//步骤1
path.lineTo(100f,centerY-100)步骤2
path.quadTo(200f,centerY,100f,centerY+100)//步骤3
path.lineTo(100f,centerY*2)//步骤4
path.lineTo(0f,centerY*2)//步骤5
path.close()//步骤6
canvas.drawPath(path,paint)
}
效果是如何的呢?我们看看
emmm好像不是那么辣眼睛了,不过这个突起着实有点突兀。怎么看怎么觉得像是塞了钱进剧组的。
你这么一说,我和效果图一比对,我感觉我做成这样,老板怕不是要立马开除我了。可是效果图那么圆滑的特效是如何做出来的呢?
其实也很简单,二阶贝塞尔曲线不行,那就让它兄弟三阶贝塞尔曲线出马,三阶贝塞尔曲线还不行,就四阶五阶。。。
其实最终效果是个六阶贝塞尔曲线,如下图
P1点和P8点是位置点,其余全是控制点。红色的线就是生成的曲线,是不是看得出来很圆滑了呢?方案是不是很简单呢?
啊,大刀不要落下来!我认错了!其实一点不简单。我当初思考,也是思考了半天才想出来。
六阶虽然难弄,但是我们可以分解啊。分解成两个三阶的贝塞尔曲线不就OK了?
我们以P1为起始点,P4为终点,P2,P3就是位置点,我们半个半个画不就很简单了嘛
上半部分就变成了这样:
那我们来看一下三阶贝塞尔曲线的方法把
/*
* x1,y1是第一个控制点的位置,对应P2
* x2,y2是第二个控制点的位置, 对应P3
* x3,y3是最后一个位置点的位置,对应p4
*/
public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
那么他们的坐标是怎么确定的呢?
我们在纸上继续画画试试
P1,P4就是位置点,P2,P3就是控制点。那么P2,P3,P4的距离如何去确定呢?
有很多在线生成贝塞尔曲线的网站,你生成四个点,摆的差不多了,看看他们的坐标就能大概知道是多少了。
我之前呢已经去试过了,以P1为(0,0)的话呢,几个点的坐标如下:
p2(0,100),p3(90,75),p4(100,150)。由于P4的Y坐标我们已经确定为centerY了,所以P1只能是(100,centerY-150).代码如下:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
path.lineTo(100f,0f)//第一步
path.lineTo(100f,centerY-150)//第二步
path.cubicTo(100f,centerY-150f+100,100f+90f,centerY-150+75f,100f+100f,centerY)//第三步
path.lineTo(100f,centerY*2)//第五步
path.lineTo(0f,centerY*2)//第六步
path.close()//第七步
canvas.drawPath(path,paint)
}
由于啊,我们只是花了上半部分,所以上图中的第四步没有画,我们主要是想先看看上半部分是不是我们想要的效果,运行一下,效果来咯
看起来上半部分效果确实圆滑了许多,那我依葫芦画瓢,把下半部分也完成。那下半部分的话,就是以P4为起点了
p1就是上半部分的p4位置,对照上半部分的相对位置,那么此时p2,p3,p4的坐标就很容易确定了
p1(100f+100f,centerY),p2(100f+90f,centerY+90f),p3(100f,centerY+75)
这是代码:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
path.lineTo(100f,0f)//第一步
path.lineTo(100f,centerY-150)//第二步
path.cubicTo(100f,centerY-150f+100,100f+90f,centerY-150+75f,100f+100f,centerY)//第三步
path.cubicTo(100f+90f,centerY+90f,100f,centerY+75f,100f,centerY+150f)//第四步
path.lineTo(100f,centerY*2)//第五步
path.lineTo(0f,centerY*2)//第六步
path.close()//第七步
canvas.drawPath(path,paint)
}
空口无凭,来看看效果才是硬道理
怎么样,效果是不是很好很圆滑了,再也不是尖尖角了。Mlx,YES!
既然这个效果已经实现了,我们开始下一步,动画!
拖拽动画
首先,分析上面的动画,我们可以发现,这个小突起会跟随手指上下移动,并且当手指向右边滑动的时候,突起会变大,那我们先不管左右的情况,先考虑上下滑动的情况。
上下动画
在上面画的这个突起中,突起的最高点我们设定的Y值是centerY。突起如果移动,这几个点的相对位置应该是不会变的。唯一改变的就是突起的最高点才对。
那么问题来了,突起的最高点它应该怎么变化?
没错,就是跟随手指上下移动。现在情况就是这样了,突起的最高点也就是上半部分和下半部分的位置点,Y值是centerY,现在应该Y值跟随手指,而其他的相对距离保持不变。
还是以这个图为例,就是P4点的Y值改变,P1 P2 P3的X坐标都不会发生改变,Y坐标以P4的坐标为原点。这样的话就能让这个突起只是上下改变了。
也就是说,
P1(100,P4.y−150),P2(100,P4.y−50),P3(190,P4.y−75)P4(200,Y)
这里唯一的变量就是Y了,Y既然跟随手指那就是手指按下的地方的Y值咯。怎么记录呢?
嘿嘿嘿,就是onTouchEvent
方法啦
我们先定义一个变量currentY来记录当前手指的位置,如果没有触摸默认就是屏幕中心
var currentY=0f//记录当前手指触摸位置
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
centerX= (w/2).toFloat()
centerY= (h/2).toFloat()
currentY=centerY //默认为屏幕中心
}
现在我们需要记录触摸的位置
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action){
MotionEvent.ACTION_MOVE-> {
currentY=event.y
invalidate()//重新绘制界面
}
}
return true
}
只做了这些还不够哦,我们画画的地方还没有更改呢,还没有以P4为标准呢。我们现在去改一下。当时是centerY,现在新人换旧人了,应该是currentY
了
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
path.lineTo(100f,0f)
path.lineTo(100f,currentY-150)
//画上半部分
path.cubicTo(100f,currentY-150f+100,100f+90f,currentY-150+75f,100f+100f,currentY)
//画下半部分
path.cubicTo(100f+90f,currentY+90f,100f,currentY+75f,100f,currentY+150f)
path.lineTo(100f,centerY*2)
path.lineTo(0f,centerY*2)
path.close()
canvas.drawPath(path,paint)
}
啊哈,让我们高兴得看看效果把~
???
兄弟我真不是在逗你玩,咱们出现了一点小的失误。你想啊,path是路径对吧,我们一直在添加路径,可是之前的路径还在啊,也就是说这些路径是叠加的。所以我们需要每次绘制路径之后,删除原来的路径。
就像这样:
override fun onDraw(canvas: Canvas) {
...
canvas.drawPath(path,paint)//画路径
path.reset()//画完之后删除之前的路径
}
如此一来,我们再看一下?
这样的话是不是可以了?简直完美!我没骗你把~
左右动画
上下的事情处理完了,是不是轮到左右的滑动事件了?
我们再次分析一下效果哈~
我们再次观察效果图,会发现
在往外拖拽的过程中,这个突起小啾啾会变大,但是当突起到达了屏幕中心的时候,是最大的,不会再进一步变大了。
你问我突起变大什么意思?我怀疑你在开车。。。
突起变大,用图的话就很好理解,我们继续在纸上作图
一开始突起是这样的,变大以后就是这样的
我们观察一下什么地方发生了变化呢?
可以很明显地观察到,P1到P4的距离发生了改变,我们暂且称P1到P4在Y方向的距离为这个突起的半径把。因为P1到P4的距离是整个突起在Y方向的一半嘛~
那么图中很明显就是这个半径变大了,而且P1到P4在X方向上也变大了,
再用一幅图来表示
可以看到有个蓝色的矩形,简单的来说, 突起变大,就是这个蓝色的矩形在不断的变大,这个矩形的长度就是整个突起的长度,这个矩形的宽度就是突起的宽度。当然了,我这里画的不太像,毕竟灵魂画手。
所以说P1,P2,P3,P4的坐标完全可以用这个矩形来表示。我们可以给这个矩形定义长度dragHeight
和宽度dragWidth
,长度对应Y方向,宽度对应X方向。
就像是这样:
上图矩形的宽度定义为dragWidth
,矩形的长度定义为dragHeight
。经过多次测试,得到最好的效果后,坐标关系对应如下。而且已经知道,P4的Y坐标就是手指触摸坐标currentY。那么最终坐标如下:
P1(x,currentY-dragHeight) , P2(x, (P1.Y+P4.Y)/2 + 12dp)
P3((x+dragWidth)∗0.94,(P1.Y+P4.Y)/2),P4(x+dragWidth,currentY)
而且,
dragHeight=dragWidth∗1.5
看到了吗,dragHeight
是由dragWidth
决定的。而四个点的坐标全部由dragHeight
来决定。
那么dragWidth
怎么确定呢?聪明的小伙伴应该已经发现了,没错,这个dragWidth就是由手指左右拖动的距离来确定的。可是为了良好的用户体验,不让手指挡住拖动按钮,所以突起的最高点应该在手指按下地方的往左一点点。定义currentX
为手指触摸点的X值,那么有下面公式:
dragWidth=currentX−12dp
可能有小伙伴问了,你这些都是啥啥啥,上来整一堆公式,还让不让人愉快的玩耍了。
其实我也么的办法,这些参数啊确实是我调教好的你也可以直接拿去用啦,如果你觉得不好,你也可以自己手动修改以下参数的啦
那么既然有了这些参数,我们修改一下之前的代码,首先定义这个矩形的长和宽
var dragWidth=0
var dragHeight=0
然后修改一下之前的控制点和数据点的坐标,不过这里直接填写数据是不是很麻烦,根本不知道哪个点是哪个点呢。所以根据我们的图,来定义七个点,分别对应下图中的点1到点8
照例我们不能再ondraw中定义哦~为了方便大家能够对照图来理解,我直接按图中点来命名了,大家看我红色标注的点,那个才是真正的点。而且大家就不要吐槽我的命名了哦。需要注意的是,我们的曲线是两个三阶贝塞尔曲线组成的,
//上半部分
private val point1=PointF(0f,0f)
private val point2=PointF(0f,0f)
private val point3=PointF(0f,0f)
//最右边
private val point4=PointF(0f,0f)
//下半部分
private val point5=PointF(0f,0f)
private val point6=PointF(0f,0f)
private val point7=PointF(0f,0f)
所以我们修改一下绘画的代码:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//下面分别是设置对应的七个点的坐标
point1.x=100f
point1.y=currentY-dragHeight
point2.x=100f
point2.y=(currentY+point1.y)/2 + 30
point3.x=(100f+dragWidth)*0.94f
point3.y=(point1.y+currentY)/2
point4.x=100f+dragWidth
point4.y=currentY
point7.x=100f
point7.y=currentY+dragHeight
point5.x=(100f+dragWidth)*0.94f
point5.y=(currentY+point7.y)/2
point6.x=100f
point6.y=currentY+dragHeight/2 - 30
//第一步
path.lineTo(100f,0f)
//第二步
path.lineTo(point1.x,point1.y)
//第三步,画上半部分
path.cubicTo(point2.x,point2.y,point3.x,point3.y,point4.x,point4.y)
//第四步,画下半部分
path.cubicTo(point5.x,point5.y,point6.x,point6.y,point7.x,point7.y)
//第五步
path.lineTo(100f,centerY*2)
//第六步
path.lineTo(0f,centerY*2)
//第七步
path.close()
canvas.drawPath(path,paint)
path.reset()
}
好,完美,我们运行一下看看效果
你会发现,啥效果也没有。。哈哈哈,一切都是我蒙你的,傻了吧?
开个玩笑活跃一下氛围,毕竟大家都看了这么久了,可能都累了,不然我下面给你吃?
其实没有效果很简单,我们的所有的公式都是基于这个矩形的长和宽来说的,注意啊,矩形指的是半个突起那个矩形,也就是公式里的矩形。
而我们现在矩形的长和宽都是0,所以当然是没有效果啦。因此,我们需要给矩形的长和宽设定一个初始值。我们知道,矩形的长是宽度的1.5倍,所以只需要给宽设定一个初始值即可。这里初始值暂且设定为100,即
var dragWidth=100f
var dragHeight=0f
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
dragHeight = dragWidth*1.5f
...
}
说这么多,和刚才效果不是一样的?有啥用啊
这你就误会了兄dei,现在我们这个突起的大小完全是按照矩形的长和宽来决定的,而矩形的长也是由矩形的宽来决定的。也就是说,矩形的宽决定了这个突起的大小。那么我们回想一下,如果右滑突起变大,本质上其实就是矩形的宽变大。在上面我们也说到了这一点。
所以只需要在触摸事件那里改变矩形宽度,突起就会变化,不信我们来试试
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action){
MotionEvent.ACTION_MOVE-> {
currentY=event.y
//更新矩形宽度
dragWidth=event.x-30
invalidate()
}
}
return true
}
看看效果如何把:
咦,有点意思了。和效果基本一样了,不过还差两点。
- 最多只能滑倒屏幕中心,无法再滑动
- 如果没有滑到屏幕中心,自动回到初始位置。
我们一条一条来解决
限制滑动距离
其实这个很简单,如果当前的触摸事件的X值超过了centerX,就让它还是centerX
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action){
MotionEvent.ACTION_MOVE-> {
currentY=event.y
//判断当前触摸事件的X值是否超过了屏幕中心
dragWidth = if(event.x>centerX){
centerX-30
}else{
event.x-30
}
invalidate()
}
}
return true
}
改动很简单,这里就不放效果了。就是无法在滑过屏幕中心了。
放手回到初始位置
在没达到屏幕中心的时候,如果放手了,我们回到初始位置。这还不简单么,矩形宽度设置为初始值100不就好了么?
没错就是这样,我们先判断放手。放手在什么地方判断啊,肯定还是触摸事件的方法中嘛
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action){
...
//手指离开屏幕
MotionEvent.ACTION_UP->{
//如果矩形宽度小于屏幕中心
if(dragWidth < centerX){
dragWidth=100f //回到初始位置
invalidate() //更新界面
}
}
}
return true
}
这还不简单?手到擒来~
emmm,是回到初始位置了,可是人家那个是一点点回去的啊,你这突然回去简直是让我猝不及防。没有一点点防备~~~
一点点回去该如何实现呢?
啊,我们的老朋友属性动画又蹦出来了。没错,还是依靠我们的老朋友属性动画才能够实现。属性动画大家都比较熟悉了,我们先自定义一个属性动画,然后让它根据时间慢慢回到初始位置。而且这里,我们需要注意一点的是,原来的效果还有一个回过头的效果,其实就是一个插值器OvershootInterpolator
。自定义估值器和插值器由于篇幅以及效果,且这两个自定义非常简单,我就不做介绍了。
我们开始定义这么一个回弹动画
private val dragReboundAnimator = ValueAnimator.ofFloat(0f, 1f)
private var reboundLength = 0f //需要回弹的距离是多少
private var dragReboundX = 0f // 初始回弹地点的X值
init{
...
dragReboundAnimator.doOnStart {
reboundLength = dragWidth -100
dragReboundX = dragWidth
}
dragReboundAnimator.duration = 700
dragReboundAnimator.interpolator = OvershootInterpolator(3f)
dragReboundAnimator.addUpdateListener {
dragWidth = dragReboundX - it.animatedValue as Float * reboundLength
invalidate()
}
}
首先我来解释一下这几个变量的意思
dragReboundAnimator
是定义了一个属性动画,从0到1,比如0 0.1 0.2一直到1这样。reboundLength
呢,是需要回弹的距离,比如说我现在dragWidth也就是矩形宽度是400,而初始位置是100,所以需要回弹的距离是300,也就是这300的距离需要一点点的回弹回去而如果宽度是600,那么就是500的距离一点点回去。dragReboundX
是你刚开始松手的时候,我需要知道你是从哪里开始回弹的。
整个流程是这样的,当需要回弹的时候,我先记录下回弹初始的位置和需要回弹的距离,然后更新的时候就是回弹初始的位置 减去回弹的距离 * 动画值。什么意思呢?就是初始距离是600,需要回弹的距离是600-100=500,并且动画值初始值为0.也就是如下
600−500∗0=600
600- 5000.1 =550
600 -5001 =100.
这样就实现了一点点的回弹。并且我们应用了OvershootInterpolator(3f)
插值器,它允许你冲过头然后再回到正确位置。
那我们来看一下效果把
嗯!效果已经很好了!感觉和原图差不多了呢。在优化一些细节相信一定没问题的.本篇就暂时不优化了,放在下一篇讲。
小结
洋洋洒洒也写了7000多字了,可是自定义ViewGroup
实在是东西太多,需要讲的内容很多。所以我已经在尽量精简的情况下,一篇文章讲完着实不大现实。而且,相信小伙伴们看到这里估计也累了,需要休息休息。
所以我这里做个小结把
自定义View的话,确实需要一点点分析效果,首先先做一个差不多的,然后再慢慢修改,慢慢优化。这种事情尤其是对于初学者来说是非常重要的。所以,大家可以根据我的思路来慢慢的体会。
所以最终效果是ViewGroup,但是本篇讲的大多数是自定义View,这是因为自定义ViewGroup的最终目标还是能够有自定义View的效果,如果效果做不出来一切都是空谈~
下一节,我将会讲解如何弹到另外一边,以及如何自定义ViewGroup的一些知识啦~小伙伴如果喜欢的话可以给我点个赞,关注我,给我一些更下去的动力。
欢迎关注我,一个喜欢自定义View和NDK,研究源码的小喵喵~
更多Android系列教程,Android开发高级进阶学习笔记,金九银十面试专题系列资料上传在GitHub:https://github.com/Timdk857/Android-Architecture-knowledge-2-