[关闭]
@cxm-2016 2017-03-31T23:08:22.000000Z 字数 2918 阅读 2792

Android:ViewTreeObserver实现指定View占满屏幕

0 Android

版本:0
作者:陈小默
说明:Kotlin + Anko


Demo完整示例-ViewPartFullScreenActivity

在某些场景下,我们需要将一个特别长的ScrollView中的特定部分占满屏幕,但是由于不同手机的屏幕尺寸不一样,我们就不能使用静态的方式去指定相应View的尺寸。就像下图这样:


解决滑动冲突

这个界面的上面使用了一个地图,为了解决地图和ScrollView的滑动冲突,我们需要定义一个布局,用来进行事件分发:

  1. @SuppressLint("ViewConstructor")
  2. class InterceptLayout(context: Context, private val scrollView: ScrollView) : RelativeLayout(context) {
  3. override fun onTouchEvent(event: MotionEvent) = true
  4. override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
  5. scrollView.requestDisallowInterceptTouchEvent(ev.action != MotionEvent.ACTION_UP)
  6. return false
  7. }
  8. }

这个View会拦截并处理本应该被传递给ScrollView的触摸事件,这样我们就可以对地图进行正常的拖动以及缩放操作。

UI代码

以下代码使用Anko库实现DSL风格

  1. scrollView {
  2. backgroundColor = Color.BLACK
  3. linearLayout {
  4. orientation = LinearLayout.VERTICAL
  5. linearLayout {
  6. orientation = LinearLayout.VERTICAL
  7. mRelativeLayout = this
  8. addView(with(InterceptLayout(context, this@scrollView)) {
  9. addView(with(MapView(context)) {
  10. //地图
  11. mMapView = this
  12. this
  13. }.lparams(matchParent, matchParent))
  14. this
  15. }.lparams(matchParent, 0) { weight = 1f })
  16. linearLayout {
  17. orientation = LinearLayout.VERTICAL
  18. backgroundResource = R.drawable.shape_gradient_blue_to_black
  19. textView("显示在屏幕上的文字") {
  20. //显示的文字
  21. gravity = Gravity.CENTER
  22. textColor = Color.WHITE
  23. textSize = px2sp(32)
  24. backgroundResource = R.drawable.shape_radius_black_gray
  25. }.lparams(matchParent, matchParent) {
  26. margin = dip(8)
  27. }
  28. }.lparams(matchParent, dip(200))
  29. }.lparams(matchParent, wrapContent)
  30. textView("被隐藏在下方的文字") {
  31. //隐藏的文字
  32. gravity = Gravity.CENTER
  33. textColor = Color.WHITE
  34. textSize = px2sp(32)
  35. backgroundResource = R.drawable.shape_radius_black_gray
  36. }.lparams(matchParent, dip(400)) {
  37. margin = dip(8)
  38. }
  39. }.lparams(matchParent, wrapContent)

监听视图变化

我们为了实现图上的效果,我们需要测量View的位置以及屏幕的高度。需要注意的是,在视图没有显示在屏幕上的时候,我们是无法对View的属性进行准确的测量的。那么我们如何知道View在什么时候进行的绘制呢?

ViewTreeObserver是一个注册监听视图树的观察者,在视图树全局事件改变时接收通知。这个全局事件不仅还包括整个树的布局,从绘画过程开始,触摸模式的改变等。

该类可以使用的接口如下:

  1. interface ViewTreeObserver.OnGlobalFocusChangeListener
  2. //View树中任意视图焦点发生改变时回调
  3. interface ViewTreeObserver.OnGlobalLayoutListener
  4. //View树中任意视图的可见状态发生改变时回调
  5. interface ViewTreeObserver.OnPreDrawListener
  6. //当前View被绘制时回调
  7. interface ViewTreeObserver.OnTouchModeChangeListener
  8. //当前View的触摸模式发生改变时回调

有了OnPreDrawListener接口,我们可以知道View被绘制的时间,在这时候我们就能拿到View相关的全部信息了。

  1. mFragment.mRelativeLayout.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
  2. override fun onPreDraw(): Boolean {
  3. val screenHeight = displayMetrics.heightPixels
  4. val locations = IntArray(2)
  5. mFragment.mRelativeLayout.getLocationOnScreen(locations)
  6. val height = screenHeight - locations[1]
  7. val params = LinearLayout.LayoutParams(matchParent, height)
  8. mFragment.mRelativeLayout.layoutParams = params
  9. mFragment.mRelativeLayout.viewTreeObserver.removeOnPreDrawListener(this) //如果不移除此监听,在滑动过程中将会持续回调
  10. return true
  11. }
  12. })

这里需要注意这段代码

  1. val locations = IntArray(2)
  2. mFragment.mRelativeLayout.getLocationOnScreen(locations)

getLocationOnScreen方法会将当前View的位置信息写进一个数组中,所以需要将一个接受数据的容器当作参数进行传递。位置信息会以[x,y]的顺序写进数组。

最后需要注意的一点是,我们在得到数据后需要移除当前监听,否则滑动时该View仍然会重绘,那么视图将会被重新调整到占满屏幕的位置,于是就会造成滑动无效的错觉。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注