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

自定义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
        }
    }

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342