飞道的博客

Android——基于LinearLayout实现的可联动伸缩布局组件

749人阅读  评论(0)

 

首先先预览一下实现的效果:灰色区域是设置的分割线,可以支持设置分割线的粗度和颜色属性:

 


  
  1. <declare-styleable name="ZoomLayout">
  2. <attr name="IntervalLineWidth" format="reference" />
  3. <attr name="IntervalLineColor" format="reference" />
  4. </declare-styleable>

实现思路:

1.首先在线性布局里的child中插入分割线view,根据设置的分割线粗和颜色进行设置。

2.增加分割线view的点击事件setOnTouchListener,比如,当手指向左移动时,缩小当前分割线左边子view的宽度,增大当前分割线右边子view的宽度,以实现分割线的左右移动和左右子view的横轴改变。联动拖动:当左边拖动到最小值(比如左边的子view 设置的minWidth是10dp)时,会判断左边所有的子view宽度是否达到了最小值,如果没有,则同时减少宽度,实现联动拖动的效果。

全部代码如下:


  
  1. package com.ng.nguilib
  2. import android.content.Context
  3. import android.graphics.Color
  4. import android.util.AttributeSet
  5. import android.view.Gravity
  6. import android.view.MotionEvent
  7. import android.view.View
  8. import android.view.ViewGroup
  9. import android.widget.LinearLayout
  10. /**
  11. * 描述:可缩放的layout
  12. * @author Jzn
  13. * @date 2020/9/9
  14. */
  15. class ZoomLayout constructor(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
  16. //子layout列表
  17. private var mChildLayoutList: ArrayList<View> = arrayListOf()
  18. private var mIntervalList: ArrayList<View> = arrayListOf()
  19. //起始点位置
  20. private var mStartX = 0f
  21. private var mStartY = 0f
  22. //位移
  23. private var mIntervalX = 0f
  24. private var mIntervalY = 0f
  25. //分割线是否添加过
  26. private var hadAdd = false
  27. //保存每个子view的宽度
  28. private var mChildWidthList: ArrayList< Int> = arrayListOf()
  29. private var mChildHeightList: ArrayList< Int> = arrayListOf()
  30. //变化中的子view宽度
  31. private var mRunningXList: ArrayList< Int> = arrayListOf()
  32. private var mRunningYList: ArrayList< Int> = arrayListOf()
  33. //params
  34. private var mIntervalLineWidth = 1
  35. private var mIntervalLineColor = 1
  36. init {
  37. val ta = context.obtainStyledAttributes(attrs, R.styleable.ZoomLayout)
  38. mIntervalLineWidth = context.resources.getDimensionPixelOffset(ta.getResourceId(R.styleable.ZoomLayout_IntervalLineWidth, R.dimen.dd10))
  39. mIntervalLineColor = ta.getColor(R.styleable.ZoomLayout_IntervalLineColor, Color.BLACK)
  40. ta.recycle()
  41. }
  42. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  43. super.onMeasure(widthMeasureSpec, heightMeasureSpec)
  44. refreshChildList()
  45. addSplit()
  46. refreshChildSizeList()
  47. val maxSize = if (measuredHeight > measuredWidth) measuredHeight else measuredWidth
  48. //修正分割线宽度
  49. mIntervalList.forEachIndexed { _, child ->
  50. val lp: ViewGroup.LayoutParams = child.layoutParams
  51. if (orientation == HORIZONTAL) {
  52. lp.height = maxSize
  53. } else if (orientation == VERTICAL) {
  54. lp.width = maxSize
  55. }
  56. child.layoutParams = lp
  57. }
  58. }
  59. //刷新子view数组
  60. private fun refreshChildList() {
  61. if (mChildLayoutList.size != childCount) {
  62. mChildLayoutList.clear()
  63. for (i in 0 until childCount) {
  64. val childView: View = getChildAt(i)
  65. mChildLayoutList.add(childView)
  66. }
  67. }
  68. }
  69. //刷新子view size
  70. private fun refreshChildSizeList() {
  71. if (mChildWidthList.size != childCount) {
  72. mChildWidthList.clear()
  73. mChildLayoutList.forEachIndexed { _, child ->
  74. mChildWidthList.add(child.measuredWidth)
  75. }
  76. mRunningXList = mChildWidthList
  77. }
  78. if (mChildHeightList.size != childCount) {
  79. mChildHeightList.clear()
  80. mChildLayoutList.forEachIndexed { _, child ->
  81. mChildHeightList.add(child.measuredHeight)
  82. }
  83. mRunningYList = mChildHeightList
  84. }
  85. }
  86. //在子view中设置操作分割线
  87. private fun addSplit() {
  88. if (mChildLayoutList.size == childCount && !hadAdd) {
  89. //在子view的间距中添加操作view
  90. mChildLayoutList.forEachIndexed { index, child ->
  91. if (index < mChildLayoutList.size - 1) {
  92. addIntervalLine(index, child)
  93. }
  94. }
  95. hadAdd = true
  96. }
  97. }
  98. //增加垂直分割线
  99. private fun addIntervalLine(number: Int, child: View) {
  100. val interValView = View(context)
  101. interValView.setBackgroundColor(mIntervalLineColor)
  102. var lp: ViewGroup.LayoutParams = LayoutParams(measuredWidth, mIntervalLineWidth)
  103. if (orientation == HORIZONTAL) {
  104. lp = LayoutParams(mIntervalLineWidth, measuredHeight)
  105. } else if (orientation == VERTICAL) {
  106. lp = LayoutParams(measuredWidth, mIntervalLineWidth)
  107. }
  108. interValView.layoutParams = lp
  109. val tarGetLocation = IntArray( 2)
  110. child.getLocationOnScreen(tarGetLocation)
  111. if (orientation == HORIZONTAL) {
  112. interValView.x = tarGetLocation[ 0].toFloat()
  113. } else if (orientation == VERTICAL) {
  114. interValView.y = tarGetLocation[ 1].toFloat()
  115. }
  116. val realIndex = 1 + number * 2
  117. interValView.setOnTouchListener { view, motionEvent ->
  118. when (motionEvent.action) {
  119. MotionEvent.ACTION_DOWN -> {
  120. mStartX = motionEvent.x
  121. mStartY = motionEvent.y
  122. }
  123. MotionEvent.ACTION_UP -> {
  124. refreshChildSizeList()
  125. view.performClick()
  126. }
  127. }
  128. mIntervalX = mStartX - motionEvent.x
  129. mIntervalY = mStartY - motionEvent.y
  130. if (orientation == HORIZONTAL) {
  131. if (isChildValueLegal(mRunningXList[realIndex - 1] - mIntervalX.toInt(), realIndex - 1) &&
  132. isChildValueLegal(mRunningXList[realIndex + 1] + mIntervalX.toInt(), realIndex + 1)
  133. ) {
  134. mRunningXList[realIndex - 1] -= mIntervalX.toInt()
  135. mRunningXList[realIndex + 1] += mIntervalX.toInt()
  136. }
  137. // 联动调整左边
  138. if (!isChildValueLegal(mRunningXList[realIndex - 1] - mIntervalX.toInt(), realIndex - 1)) {
  139. gravity = Gravity.START
  140. var fixMulti = 0
  141. if (realIndex - 2 > 0) {
  142. for (index in 0..realIndex - 2) {
  143. //这里要判断是否是分割线
  144. if (index % 2 == 0 && isChildValueLegal(mRunningXList[index] - mIntervalX.toInt(), index)) {
  145. mRunningXList[index] -= mIntervalX.toInt()
  146. fixMulti++
  147. }
  148. }
  149. mRunningXList[realIndex + 1] += mIntervalX.toInt() * fixMulti
  150. }
  151. }
  152. //联动调整右边
  153. if (!isChildValueLegal(mRunningXList[realIndex + 1] + mIntervalX.toInt(), realIndex + 1)) {
  154. gravity = Gravity.END
  155. var fixMulti = 0
  156. for (index in (realIndex + 2) until childCount) {
  157. if (index % 2 == 0 && isChildValueLegal(mRunningXList[index] + mIntervalX.toInt(), index)) {
  158. mRunningXList[index] += mIntervalX.toInt()
  159. fixMulti++
  160. }
  161. }
  162. mRunningXList[realIndex - 1] -= mIntervalX.toInt() * fixMulti
  163. }
  164. } else if (orientation == VERTICAL) {
  165. if (isChildValueLegal(mRunningYList[realIndex - 1] - mIntervalY.toInt(), realIndex - 1) &&
  166. isChildValueLegal(mRunningYList[realIndex + 1] + mIntervalY.toInt(), realIndex + 1)) {
  167. mRunningYList[realIndex - 1] -= mIntervalY.toInt()
  168. mRunningYList[realIndex + 1] += mIntervalY.toInt()
  169. }
  170. // 联动调整上面
  171. if (!isChildValueLegal(mRunningYList[realIndex - 1] - mIntervalY.toInt(), realIndex + 1)) {
  172. gravity = Gravity.TOP
  173. var fixMulti = 0
  174. if (realIndex - 2 > 0) {
  175. for (index in 0..realIndex - 2) {
  176. if (index % 2 == 0 && isChildValueLegal(mRunningYList[index] - mIntervalY.toInt(), index)) {
  177. mRunningYList[index] -= mIntervalY.toInt()
  178. fixMulti++
  179. }
  180. }
  181. mRunningYList[realIndex + 1] += mIntervalY.toInt() * fixMulti
  182. }
  183. }
  184. // 联动调整下面
  185. if (!isChildValueLegal(mRunningYList[realIndex + 1] + mIntervalY.toInt(), realIndex + 1)) {
  186. gravity = Gravity.BOTTOM
  187. var fixMulti = 0
  188. for (index in (realIndex + 2) until childCount) {
  189. if (index % 2 == 0 && isChildValueLegal(mRunningYList[index] + mIntervalY.toInt(), index)) {
  190. mRunningYList[index] += mIntervalY.toInt()
  191. fixMulti++
  192. }
  193. }
  194. mRunningYList[realIndex - 1] -= mIntervalY.toInt() * fixMulti
  195. }
  196. }
  197. mChildLayoutList.forEachIndexed { index, child ->
  198. val childLp: LayoutParams = child.layoutParams as LayoutParams
  199. childLp.weight = 0f
  200. if (orientation == HORIZONTAL) {
  201. childLp.width = mRunningXList[index]
  202. } else if (orientation == VERTICAL) {
  203. childLp.height = mRunningYList[index]
  204. }
  205. child.layoutParams = childLp
  206. }
  207. //防止左越界
  208. if (mChildLayoutList.size != 0) {
  209. mChildLayoutList[ 0].x = 0f
  210. }
  211. true
  212. }
  213. mIntervalList.add(interValView)
  214. addView(interValView, realIndex, lp)
  215. }
  216. private fun isChildValueLegal(value: Int, index: Int): Boolean {
  217. val minZoom = if (orientation == HORIZONTAL) {
  218. mChildLayoutList[index].minimumWidth
  219. } else {
  220. mChildLayoutList[index].minimumHeight
  221. }
  222. return value > minZoom
  223. }
  224. }

代码下载和效果预览地址:

https://github.com/jiangzhengnan/NguiLib

求start~求fork~

也欢迎提交~


转载:https://blog.csdn.net/qq_22770457/article/details/108560992
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场