美文网首页Android自我学习路线
什么是自定义View,什么是高级UI

什么是自定义View,什么是高级UI

作者: Smile丶微笑 | 来源:发表于2020-07-28 00:45 被阅读0次

自定义View是Android基础

1、什么是自定义View?

一个效果只要它能够在手机上面实现,我们就应该具有实现它的能力。

学习方式?实践->理论

2、自定义View包含什么?

布局:onmeasure、onlayout

例如 Layout ViewGroup

显示:onDraw

例如:View: canvas、paint、matrix、clip、rect、animation、path(贝塞尔曲线)、

line

交互:onTouchEvent:组合的ViewGroup

3、自定义View如何分类?

(1)自定义View

在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他View

(2)自定义ViewGroup

一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout

4、自定义View的绘制流程

image

(1)测量(onMeasure())

先测量子View的大小,再测量自己的大小

(2)布局(onLayout())

(3)实际绘制内容(onDraw())

自定义View的关键是:

(1)自定义View主要实现 onMeasure() + onDraw()

(2)自定义ViewGroup主要实现 onMeasure() + onLayout()

阅读源码可以根据这两种分类,确定关键代码在哪里。

5、自定义流式布局(FlowLayout)源码
先测量onMeasure(),再布局onLayout()

package com.example.myany.widgets

import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi

class FlowLayout: ViewGroup {

    //new
    constructor(context: Context)
            : this(context, null) {
    }

    //反射
    constructor(context: Context, attrs: AttributeSet?)
            : this(context, attrs, 0) {

    }

    //主题style
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
            : super(context, attrs, defStyleAttr) {
    }

    //自定义属性
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int)
            : super(context, attrs, defStyleAttr, defStyleRes) {
    }

    private val mHorizontalSpacing = 30
    private val mVerticalSpacing = 30

    private val allLines = mutableListOf<List<View>>()//记录所有的行,一行一行的存储,用于layout
    private val lineHeights = mutableListOf<Int>()//记录每一行的高度,用于layout

    private fun clearMData(){
        allLines.clear()
        lineHeights.clear()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.e("FlowLayout", "onMeasure")
        clearMData()
        //先度量孩子
        val childCount = childCount
        val paddingLeft = paddingLeft
        val paddingRight  = paddingRight
        val paddingTop = paddingTop
        val paddingBottom = paddingBottom

        //解析父亲给我的宽高
        val selfWidth = MeasureSpec.getSize(widthMeasureSpec)
        val selfHeight = MeasureSpec.getSize(heightMeasureSpec)


        var lineViews = mutableListOf<View>()//保存一行中所有的view
        var lineUsedWidth: Int = 0//记录这行已经使用的size
        var lineHeight: Int = 0//一行的高度

        var parentNeededWidth = 0 //measure过程中,子view要求父ViewGroup的宽
        var parentNeededHeight = 0 //measure过程中,子view要求父ViewGroup的高

        for(i in 0 until childCount) {

            Log.e("FlowLayout", "measure child $i")

            val childView = getChildAt(i)
            val childLp = childView.layoutParams
            //将layoutParams转换成 measureSpec
            val childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft+paddingRight, childLp.width)
            val childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop+paddingBottom, childLp.height)
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)

            //获得子view的度量宽高
            val childMeasureWidth = childView.measuredWidth
            val childMeasureHeight = childView.measuredHeight

            //若需要换行
            if (childMeasureWidth + lineUsedWidth + mHorizontalSpacing >= selfWidth) {
                //一旦换行,我们就可以判断当前行需要的宽和高了,所以此时记录下来
                allLines.add(lineViews)
                lineHeights.add(lineHeight)
                parentNeededHeight += lineHeight + mVerticalSpacing
                parentNeededWidth = parentNeededWidth.coerceAtLeast(lineUsedWidth + mHorizontalSpacing)

                lineViews = mutableListOf()
                lineUsedWidth = 0
                lineHeight = 0

            }

            //view 是分行的layout的,所以要记录每一行有哪些view,这样可以方便layout布局
            lineViews.add(childView)
            //每行都会有自己的宽和高
            lineUsedWidth += childMeasureWidth + mHorizontalSpacing
            lineHeight = lineHeight.coerceAtLeast(childMeasureHeight)

            //处理最后一行
            if (i == childCount - 1) {
                allLines.add(lineViews)
                lineHeights.add(lineHeight)
                parentNeededHeight += lineHeight + mVerticalSpacing
                parentNeededWidth = parentNeededWidth.coerceAtLeast(lineUsedWidth + mHorizontalSpacing)
            }
        }

        parentNeededHeight += paddingTop + paddingBottom

        //再度量自己,保存
        //根据子view的度量结果,来重新度量自己ViewGroup
        //作为一个ViewGroup,它自己也是一个view,它的大小也需要根据它的父亲给它提供的宽高来度量
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val realWidth = if (widthMode == MeasureSpec.EXACTLY) selfWidth else parentNeededWidth
        val realHeight = if (heightMode == MeasureSpec.EXACTLY) selfHeight else parentNeededHeight

        setMeasuredDimension(realWidth, realHeight)
    }


    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        Log.e("FlowLayout", "onLayout")
        var curL = paddingLeft
        var curT = paddingTop

        val lineCount = allLines.size

        for (i in 0 until lineCount) {
            Log.e("FlowLayout", "layout line $i")
            val lineViews = allLines[i]
            val count = lineViews.size
            for (j in 0 until count) {
                Log.e("FlowLayout", "layout line view $j")
                val view = lineViews[j]
                val left = curL
                val top = curT
                val right = left + view.measuredWidth
                val bottom = top + view.measuredHeight
                view.layout(left, top, right, bottom)
                curL = right + mHorizontalSpacing
            }
            curL = paddingLeft
            curT += lineHeights[i] + mVerticalSpacing
        }
    }

}

相关文章

网友评论

    本文标题:什么是自定义View,什么是高级UI

    本文链接:https://www.haomeiwen.com/subject/sodxrktx.html