private data class ViewLayoutInfo(
var originalLeft: Int = 0,// original开头的为动画开始前的起始值
var originalTop: Int = 0,
var originalRight: Int = 0,
var originalBottom: Int = 0,
var left: Float = 0.0f,// 无前缀的为动画过程中的临时值
var top: Float = 0.0f,
var right: Float = 0.0f,
var bottom: Float = 0.0f,
var toLeft: Int = 0,// to开头的为动画目标值
var toTop: Int = 0,
var toRight: Int = 0,
var toBottom: Int = 0,
var progress: Float = 0.0f,// 进度 0.0f ~ 1.0f,用于控制 Alpha 动画
var isAlpha: Boolean = false,// 透明动画,新添加的执行此动画
var isConverted: Boolean = false,// 控制 progress 反转的标记
var waitingDestroy: Boolean = false,// 结束后销毁 View 的标记
var pos: Int = 0// 记录自己索引,以便销毁
) {
init {
left = originalLeft.toFloat()
top = originalTop.toFloat()
right = originalRight.toFloat()
bottom = originalBottom.toFloat()
}
}
以上,记录了执行动画和销毁View所需的数据。(于源码中第352行)
2.计算不同展示模式下View坐标的函数
if (layoutTopicMode) {
var index = 0
for (i in 1 until childCount) if (i != position) (getChildAt(i).tag as ViewLayoutInfo).run {
toLeft = measuredWidth - maxWidgetPadding - smallViewWidth
toTop = defMultipleVideosTopPadding + index * smallViewHeight + index * maxWidgetPadding
toRight = measuredWidth - maxWidgetPadding
toBottom = toTop + smallViewHeight
index++
}
} else {
var posOffset = 0
var pos = 0
if (childCount == 4) {
posOffset = 2
pos++
(getChildAt(0).tag as ViewLayoutInfo).run {
toLeft = measuredWidth.shr(1) - multiViewWidth.shr(1)
toTop = defMultipleVideosTopPadding
toRight = measuredWidth.shr(1) + multiViewWidth.shr(1)
toBottom = defMultipleVideosTopPadding + multiViewHeight
}
}
for (i in pos until childCount) if (i != position) {
val topFloor = posOffset / 2
val leftFloor = posOffset % 2
(getChildAt(i).tag as ViewLayoutInfo).run {
toLeft = leftFloor * measuredWidth.shr(1) + leftFloor * multipleWidgetPadding
toTop = topFloor * multiViewHeight + topFloor * multipleWidgetPadding + defMultipleVideosTopPadding
toRight = toLeft + multiViewWidth
toBottom = toTop + multiViewHeight
}
posOffset++
}
}
post(AnimThread(
(0 until childCount).map { getChildAt(it).tag as ViewLayoutInfo }.toTypedArray()
))
Demo源码中的add、remove、toggle方法重复代码过多,未来得及优化。这里只附上 addVideoView 中的计算部分(于源代码中第141行),只需稍微修改即可适用add、remove和toggle。(也可参考 CDNLiveVM 中的 calcPosition 方法,为经过优化的版本)layoutTopicMode = true 时,为大小屏模式。
由于是定制算法,只能适用这一种布局,故不写注释。只需明确一点,此方法最终目的是为了计算出每个View当前应该出现的位置,保存到上面定义的数据模型中并开启动画(最后一行 post AnimThread 为开启动画的代码,我这里是通过 post 一个线程来更新每一帧)。
可根据不同的需求写不同的实现,最终符合定义的数据模型即可。
3.逐渐减速的算法,使动画效果看起来更自然。
private inner class AnimThread(
private val viewInfoList: Array<ViewLayoutInfo>,
private var duration: Float = 180.0f,
private var processing: Float = 0.0f
) : Runnable {
private val waitingTime = 9L
override fun run() {
var progress = processing / duration
if (progress > 1.0f) {
progress = 1.0f
}
for (viewInfo in viewInfoList) {
if (viewInfo.isAlpha) {
viewInfo.progress = progress
} else viewInfo.run {
val diffLeft = (toLeft - originalLeft) * progress
val diffTop = (toTop - originalTop) * progress
val diffRight = (toRight - originalRight) * progress
val diffBottom = (toBottom - originalBottom) * progress
left = originalLeft + diffLeft
top = originalTop + diffTop
right = originalRight + diffRight
bottom = originalBottom + diffBottom
}
}
requestLayout()
if (progress < 1.0f) {
if (progress > 0.8f) {
var offset = ((progress - 0.7f) / 0.25f)
if (offset > 1.0f)
offset = 1.0f
processing += waitingTime - waitingTime * progress * 0.95f * offset
} else {
processing += waitingTime
}
postDelayed(this@AnimThread, waitingTime)
} else {
for (viewInfo in viewInfoList) {
if (viewInfo.waitingDestroy) {
removeViewAt(viewInfo.pos)
} else viewInfo.run {
processing = 0.0f
duration = 0.0f
originalLeft = left.toInt()
originalTop = top.toInt()
originalRight = right.toInt()
originalBottom = bottom.toInt()
isAlpha = false
isConverted = false
}
}
animRunning = false
processing = duration
if (!taskLink.isEmpty()) {
invokeLinkedTask()// 此方法执行正在等待中的任务,从源码中能看到,remove、add等函数需要依次执行,前一个动画未执行完毕就进行下一个动画可能会导致不可预知的错误。
}
}
}
}
上述代码除了提供减速算法,还一并更新了对应View数据模型的中间值,也就是模型定义种的 left, top, right, bottom 。
通过减速算法提供的进度值,乘以目标坐标与起始坐标的间距,得出中间值。
逐渐减速的算法关键代码为:
if (progress > 0.8f) {
var offset = ((progress - 0.7f) / 0.25f)
if (offset > 1.0f)
offset = 1.0f
processing += waitingTime - waitingTime * progress * 0.95f * offset
} else {
processing += waitingTime
}
这个算法实现的有缺陷,因为它直接修改了进度时间,大概率会导致执行完毕的时间与设置的预期时间(如设置200ms执行完毕,实际可能超过200ms)不符。文末我会提供一个优化的减速算法。
变量 waitingTime 表示等待多久执行下一帧动画。用每秒1000ms计算即可,如果目标为60刷新率的动画,设置为1000 / 60 = 16.66667即可(近似值)。
计算并存储每个 View 的中间值后,调用 requestLayout() 通知系统的 onMeasure 和 onLayout 方法,重新摆放 View 。
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
if (childCount == 0)
return
for (i in 0 until childCount) {
val child = getChildAt(i)
val layoutInfo = child.tag as ViewLayoutInfo
child.layout(
layoutInfo.left.toInt(),
layoutInfo.top.toInt(),
layoutInfo.right.toInt(),
layoutInfo.bottom.toInt()
)
if (layoutInfo.isAlpha) {
val progress = if (layoutInfo.isConverted)
1.0f - layoutInfo.progress
else
layoutInfo.progress
child.alpha = progress
}
}
}
4.定义边距相关的变量,供简单的定制修改
/**
* @param multipleWidgetPadding : 等分模式读取
* @param maxWidgetPadding : 大小屏布局读取
* @param defMultipleVideosTopPadding : 距离顶部变距
*/
private var multipleWidgetPadding = 0
private var maxWidgetPadding = 0
private var defMultipleVideosTopPadding = 0
init {
viewTreeObserver.addOnGlobalLayoutListener(this)
attrs?.let {
val typedArray = resources.obtainAttributes(it, R.styleable.AnyVideoGroup)
multipleWidgetPadding = typedArray.getDimensionPixelOffset(
R.styleable.AnyVideoGroup_between23viewsPadding, 0
)
maxWidgetPadding = typedArray.getDimensionPixelOffset(
R.styleable.AnyVideoGroup_at4smallViewsPadding, 0
)
defMultipleVideosTopPadding = typedArray.getDimensionPixelOffset(
R.styleable.AnyVideoGroup_defMultipleVideosTopPadding, 0
)
layoutTopicMode = typedArray.getBoolean(
R.styleable.AnyVideoGroup_initTopicMode, layoutTopicMode
)
typedArray.recycle()
}
}
取名时对这三个变量的职责定义,与编写逻辑时的定义有出入,所以有点词不达意,需参考注释。
由于这只是定制化的变量,并不重要,可根据业务逻辑自行随意修改。
5.复写 onMeasure 方法,这里主要是通知 TextureView 更新大小。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
multiViewWidth = widthSize.shr(1)
multiViewHeight = (multiViewWidth.toFloat() * 1.33334f).toInt()
smallViewWidth = (widthSize * 0.3125f).toInt()
smallViewHeight = (smallViewWidth.toFloat() * 1.33334f).toInt()
for (i in 0 until childCount) {
val child = getChildAt(i)
val info = child.tag as ViewLayoutInfo
child.measure(
MeasureSpec.makeMeasureSpec((info.right - info.left).toInt(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec((info.bottom - info.top).toInt(), MeasureSpec.EXACTLY)
)
}
setMeasuredDimension(
MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY)
)
}
factor = 1.0
if (factor == 1.0)
(1.0 - (1.0 - x) * (1.0 - x))
else
(1.0 - pow((1.0 - x), 2 * factor))
// x = time.
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun
我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin
我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano
我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是
我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些