@cxm-2016
2017-03-31T23:08:22.000000Z
字数 2918
阅读 2759
0
Android
版本:0
作者:陈小默
说明:Kotlin + Anko
在某些场景下,我们需要将一个特别长的ScrollView中的特定部分占满屏幕,但是由于不同手机的屏幕尺寸不一样,我们就不能使用静态的方式去指定相应View的尺寸。就像下图这样:
这个界面的上面使用了一个地图,为了解决地图和ScrollView的滑动冲突,我们需要定义一个布局,用来进行事件分发:
@SuppressLint("ViewConstructor")
class InterceptLayout(context: Context, private val scrollView: ScrollView) : RelativeLayout(context) {
override fun onTouchEvent(event: MotionEvent) = true
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
scrollView.requestDisallowInterceptTouchEvent(ev.action != MotionEvent.ACTION_UP)
return false
}
}
这个View会拦截并处理本应该被传递给ScrollView的触摸事件,这样我们就可以对地图进行正常的拖动以及缩放操作。
以下代码使用Anko库实现DSL风格
scrollView {
backgroundColor = Color.BLACK
linearLayout {
orientation = LinearLayout.VERTICAL
linearLayout {
orientation = LinearLayout.VERTICAL
mRelativeLayout = this
addView(with(InterceptLayout(context, this@scrollView)) {
addView(with(MapView(context)) {
//地图
mMapView = this
this
}.lparams(matchParent, matchParent))
this
}.lparams(matchParent, 0) { weight = 1f })
linearLayout {
orientation = LinearLayout.VERTICAL
backgroundResource = R.drawable.shape_gradient_blue_to_black
textView("显示在屏幕上的文字") {
//显示的文字
gravity = Gravity.CENTER
textColor = Color.WHITE
textSize = px2sp(32)
backgroundResource = R.drawable.shape_radius_black_gray
}.lparams(matchParent, matchParent) {
margin = dip(8)
}
}.lparams(matchParent, dip(200))
}.lparams(matchParent, wrapContent)
textView("被隐藏在下方的文字") {
//隐藏的文字
gravity = Gravity.CENTER
textColor = Color.WHITE
textSize = px2sp(32)
backgroundResource = R.drawable.shape_radius_black_gray
}.lparams(matchParent, dip(400)) {
margin = dip(8)
}
}.lparams(matchParent, wrapContent)
我们为了实现图上的效果,我们需要测量View的位置以及屏幕的高度。需要注意的是,在视图没有显示在屏幕上的时候,我们是无法对View的属性进行准确的测量的。那么我们如何知道View在什么时候进行的绘制呢?
ViewTreeObserver是一个注册监听视图树的观察者,在视图树全局事件改变时接收通知。这个全局事件不仅还包括整个树的布局,从绘画过程开始,触摸模式的改变等。
该类可以使用的接口如下:
interface ViewTreeObserver.OnGlobalFocusChangeListener
//View树中任意视图焦点发生改变时回调
interface ViewTreeObserver.OnGlobalLayoutListener
//View树中任意视图的可见状态发生改变时回调
interface ViewTreeObserver.OnPreDrawListener
//当前View被绘制时回调
interface ViewTreeObserver.OnTouchModeChangeListener
//当前View的触摸模式发生改变时回调
有了OnPreDrawListener接口,我们可以知道View被绘制的时间,在这时候我们就能拿到View相关的全部信息了。
mFragment.mRelativeLayout.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
val screenHeight = displayMetrics.heightPixels
val locations = IntArray(2)
mFragment.mRelativeLayout.getLocationOnScreen(locations)
val height = screenHeight - locations[1]
val params = LinearLayout.LayoutParams(matchParent, height)
mFragment.mRelativeLayout.layoutParams = params
mFragment.mRelativeLayout.viewTreeObserver.removeOnPreDrawListener(this) //如果不移除此监听,在滑动过程中将会持续回调
return true
}
})
这里需要注意这段代码
val locations = IntArray(2)
mFragment.mRelativeLayout.getLocationOnScreen(locations)
getLocationOnScreen方法会将当前View的位置信息写进一个数组中,所以需要将一个接受数据的容器当作参数进行传递。位置信息会以[x,y]
的顺序写进数组。
最后需要注意的一点是,我们在得到数据后需要移除当前监听,否则滑动时该View仍然会重绘,那么视图将会被重新调整到占满屏幕的位置,于是就会造成滑动无效的错觉。