Android自定义ViewAndroid_花式组件,自定义组件Android _ 资源、知识总结链接目录 + 框架

自定义 view 练手 - 自定义 flexbox-layout

2018-10-18  本文已影响6人  前行的乌龟

项目初衷


上一个例子,我们自己实现了一个简单的 textview ,我想认真去做的同学应该对绘制文字,自定义 view 测量,计算宽高有一些心得了,这很重要,计算宽高才是自定义 view 开始的核心,很多时候绘制没有我们想象的难,卡住我们的往往是计算位置的逻辑,这块只有多写,积累静养心得,之后自定义 view 才不会让我们素手无策

练过自定义 view 之后,我们再来练习一下 自定义 viewgroup ,很多页面动画,联动效果都是通过 自定义 viewgroup 或是相关 API 实现的

这里选择一个简单的 自定义 viewgroup 作为开始,我们来简单模仿一下 flexbox-layout:


ezgif.com-video-to-gif.gif

项目地址:BW_Libs

我写文章也不容易,希望大家点个赞,点个喜欢,关注,github 给个 start 啥的,多谢大家啦,么么哒 ~~

CustomeFlexLayout 思路


FlexLayout 的思路很简单的,横向一个线性布局,没位置能放 view 之后换行

我这里没有和别人一样去计算有多少行 view ,我考虑item view 的 height 可能不统一,所以是在计算总高度的时候,顺带把 每一个 item view 的布局坐标也记录下来了

直接看代码:


    // 声明一个 map 集合,记录所有子 view 及其布局坐标
    var ChildViewList = mutableMapOf<View, Rect>()

    // 宽和高分别维护一个分隔值,在构造函数种初始化,10dp
    var widthoffset: Int = 0
    var heightoffset: Int = 0
    // 在布局方法中,直接根据上面计算出的 view 及其布局坐标进行布局,
    // 计算布局坐标和计算 viewgroup 总高度逻辑其实是在一起的,就没必要分2部写了
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        for ((view, rect) in ChildViewList) {
            view.layout(rect.left, rect.top, rect.right, rect.bottom)
        }
    }
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        var widthSize = MeasureSpec.getSize(widthMeasureSpec)

        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        var heightSize = MeasureSpec.getSize(heightMeasureSpec)

        // viewgroup 提供了遍历计算所有子 view 宽高的方法了,我们没必要自己去 for 循环遍历计算了
        measureChildren(widthMeasureSpec, heightMeasureSpec)
      
      // calculateAllChildViewLayoutRect 方法是计算总高度和子 view 布局坐标的方法
        setMeasuredDimension(widthSize, calculateAllChildViewLayoutRect(widthSize))
    }
    fun calculateAllChildViewLayoutRect(maxWidth: Int): Int {

        // 起始坐标设为上下分割值的负数,是为了后面好计算
        var lineWidth: Int = -widthoffset
        var lineHeight: Int = -heightoffset
        var totalHeight: Int = 0

        for (index in 0..childCount - 1) {
            var rect = Rect()
            val childView = getChildAt(index)
            val width = childView.measuredWidth
            val height = childView.measuredHeight
  
            // 累积每行的宽度,宽度超过最大值说明一行放不下了,另起一行
            lineWidth += widthoffset + width
            if (height > lineHeight) lineHeight = height

            // 一行放不下了,需要新启动一行
            if (lineWidth > maxWidth) {
                // 行高加入view 总高度
                totalHeight += lineHeight + heightoffset

                // 计算本 view 的布局坐标
                rect.left = 0
                rect.right = width
                rect.top = totalHeight
                rect.bottom = totalHeight + height
                ChildViewList.put(childView, rect)
                
                // 最后一个 view 就不要重置数据了,添加总行高时还需要 view 高度呢
                if( index < childCount - 1 ){
                    // 行宽行高重置
                    lineWidth = width
                    lineHeight = 0
                }
            } else {
                // 计算本 view 的布局坐标
                rect.left = lineWidth - width
                rect.right = lineWidth
                rect.top = totalHeight
                rect.bottom = totalHeight + height
                ChildViewList.put(childView, rect)
            }

            // 最后一个view 把最后一行高度加入总高度
            if (index == childCount - 1) totalHeight += lineHeight + heightoffset
        }
        return totalHeight
    }
布局和在页面添加数据
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent" android:layout_height="match_parent"
    tools:context="com.bloodcrown.bw.customeview.FlexLayoutActivity">

    <com.bloodcrown.bw.customeview.CustomeFlexLayout
        android:id="@+id/view_flex"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>


    <Button
        android:id="@+id/btn_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="添加项目"
        android:textSize="22sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</android.support.constraint.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/colorAccent"
    android:padding="5dp"
    android:textSize="22sp"
    tools:text="AAAAAAAA"/>
class FlexLayoutActivity : AppCompatActivity() {

    private var random = Random()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_flex_layout)

        btn_add.setOnClickListener({
            view_flex.addView(createView())
        })
    }

    fun createView(): View {
        val textview: TextView = layoutInflater.inflate(R.layout.item_flex, null) as TextView
        textview.text = random.nextInt(10000).toString()

        val animatorSet = AnimatorSet()
        val animator1 = ObjectAnimator.ofFloat(textview, "translationY", -50f, 0f)
        val animator2 = ObjectAnimator.ofFloat(textview, "alpha", 0.3f, 1f)
        animatorSet.setDuration(500)
        animatorSet.playTogether(animator1, animator2)
        animatorSet.start()
        return textview
    }

}

关于动画启动的时机,我也不清楚为啥要写在这里的,按说我们给 viewgroup add 一个 view 后会重新走一遍 viewgroup 测量,布局 ,绘制的,但是我们在 item view 添加到 viewgroup 之前就启动了一个动画,为啥还能正常执行,我也是百思百思不得其解啊,只能认为属性动画会判断绑定 view 所在状态,要是没有计算完毕就先不会执行动画

逻辑不是很复杂,这里大家要是更简单的每行高度一样,算行数的话逻辑更简单,这个例子是为了带大家找找 自定义 viewgroup 的感觉,这个着呢很重要,先会先 ok,很多时候会省我们很多事 ~

上一篇 下一篇

猜你喜欢

热点阅读