发布于2021-06-07 22:09 阅读(1332) 评论(0) 点赞(19) 收藏(4)
(1)什么是自定义View?什么是高级UI?
(2)LayoutParams解析原理
(3)MeasureSpec原理解析
(4)自定义流式布局项目实战
(5)坐标系介绍
(6)XXLayout布局源码解析
(1)实现UI
(2)自定义View
(3)自定义View决定了做的APP是否漂亮,做的APP的效果怎么样。
(4)Java与Kotlin是语言基础
(5)自定义View就是Android基础。对于Android工程师来说,绘制自定义View只是入门功夫。
(1)一个效果只要它能够在手机上面实现你就应该具备实现它的能力。
(2)大量的实践、练习。
(1)onLayout onMeasure
(2)布局用的最多的是ViewGroup,在ViewGroup中用的最多的又是Layout,无论用的是什么Layout布局,最终都属于布局。Layout继承自ViewGroup。
(1)onDraw
(2)onDraw是绘制,里面包含了
(3)用的最多的是View里面
(1)onTouchEvent(事件分发)
(2)用在组合的ViewGroup中。
(1)只需要重写onMeasure()和onDraw()
(2)在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View.
(1)只需要重写onMeasure()和onLayout()
(2)自定义ViewGroup一般利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout.
(1)FlowLayout,流式布局,这个概念在移动端或者前端开发中很常见,特别是在多标签展示中,往往起到了关键的作用。然而Android官方,并没有为开发者提供这样的一个布局。
(1)首先要测量,有多大多高.要测量每一个小房间的房子,然后再得到大房子的面积。
(2)布局,如果连通,摆放
(3)onDraw是对每一个小房间的装饰
(4)自定义View主要是实现 onMeasure + onDraw(房间装修)
(5)自定义ViewGroup主要是实现onMeasure + onLayout(整套房子装修及布局)
/**
* 1.此构造函数在Java代码中去new的时候调用
* @param context
*/
public FlowLayout(Context context) {
super(context);
}
/**
* 1.在XML布局中使用的时候调用。
* (1)XML以序列化的方式去创建对象
* (2)解析的函数是在LayoutInflater中去解析XML的内容。
*
* 2.序列化
* (1)可以自定义序列化解析,在IOT中使用的最多.
* (2)IOT协议,序列化是一套数据交换的规则
* (3)物联网:蓝牙 传递的数据,串口
* (4)NFC:射频
*
* 3.通过反射去hook到函数去构造View
* @param context
* @param attrs
*/
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 1.自定义Style时调用
* (1)有黑白主题时就使用此构造创建对象
* @param context
* @param attrs
* @param defStyleAttr
*/
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 1.自定义属性
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
/*@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}*/
(1)测量的过程就是将xml中的宽高属性转变成具体dp或dip值的过程。
(1)LayoutParams是ViewGroup中的一个值。
(2)在自定义View的过程中有可能需要自己定义LayoutParams,比如ViewPager,可以根据自己的需求来定义。
(3)自定义LayoutParams,是为了增加自定义的相关属性,根据自己的需求定义属性。
(4)如何将LayoutParams转变成具体的值dp,dip.
android:layout_width="10dp"
android:layout_width="match_parent"(-1)
android:layout_width="wrap_content"(-2)
(1)View的结构是树形结构
(2)ViewGroup是父亲,它有孩子View.
(3)从继承关系上来说,View是父类,而ViewGroup是子类
(4)ViewGroup可以包含各种View
(5)每一个View中存在三种情况
(1)match_parent受到父亲的限制
(2)wrap_content:受到子View布局方式的限制。
(3)根据View的绘制流程与树形结构来看,在调用onMeasure()函数度量的时候,方法的参数就来自于当前View的父亲,它是一种递归测量。
(4)onMeasure()没有将测量到的值返回,测量的值是通过getMeasuredWidth()与getMeasuredHeight()去获取到的。
(5)setMeasuredDimension(width,height)保存测量值,就是为了能够获取。
(1)测量的过程中使用
(2)是View中的内部类,基本都是二进制运算.由于int是32位的,用高两位表示mode,低30位表示size,MODE_SHIFT=30的作用是移位。
(3)具体测量模式
(1)是getChildMeasureSpec()方法的具体算法
(2)作为子View要将控件大小转变为布局的大小
(3)getChildMeasureSpec(int spec, int padding, int childDimension)
/**
* 1.为什么要实现onMeasure?
* (1)测量
* (2)先测量子,再测量自己。(先测量小房间,再测量整套房子)多数情况是这样测量
* (3)ViewPager测量比较特殊,只需要测自己。
*
* 2.需要解决widthMeasureSpec,heightMeasureSpec从哪里来的问题?
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* 1.1先度量孩子
* (1)度量孩子主要是去解析layout_width与layout_height属性
* (2)要将layout_width与layout_height属性变成具体的大小,dp或dip值
*/
int childCount = getChildCount();
/**
* 1.2getChildMeasureSpec(int spec, int padding, int childDimension)
*
* - 第一个参数spec来自于父级布局指定的大小,父亲的大小是父亲给的。
* - 第二个参数padding.要在父亲的空间大小上分配一块区域作为孩子的大小空间,至少要与父亲之间有一定的间隔。所以父亲要减去这一个padding才是当前孩子的大小空间。
* - 第三个参数childDimension指的是孩子需要的大小空间。
*
* 1.3以下步骤拿到的是距离父亲的左右上下的padding
*
* 1.4childLP.width、childLP.height指的是xml的宽与高,也就是孩子需要的大小空间。
*
* 1.5通过getChildMeasureSpec的计算就得到了具体的值
*/
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
for(int i = 0 ; i < childCount;i++){
View childView = getChildAt(i);
//1.1.1将layoutParams转变为measureSpec
LayoutParams childLP = childView.getLayoutParams();
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
paddingLeft+paddingRight,childLP.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
paddingTop+paddingBottom,childLP.height);
childView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
(1)要解决的问题就是孩子节点大小分配的问题,父亲的大小是多少,能给多少的问题。
(2)这也就是决定孩子大小计算的算法。
(3)父亲的大小也有三种情况
(4)正因为如此,就会有9种算法获得孩子的大小。
(1)根据不同的测量模式,通过判断值,判断match_parent、wrap_parent的方式,计算出不同的孩子的大小。
(2)计算后得到的是一个理论的值。
(3)只有计算完所有孩子的大小之后,才能得到确切的值。
(1)宽度是每一行中最宽的一个组合项(取决于几个标签的组合后最宽的那一组的宽度)的宽度。
(2)流式布局的高度是所有行数的高度之和。
List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
int lineWidthUsed = 0; //记录这行已经使用了多宽的size
int lineHeight = 0; // 一行的行高
//1.1.2获取子View的度量宽高
int childMeasuredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();
//2.3.1view 是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
lineViews.add(childView);
//2.3.2每行都会有自己的宽和高
lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childMeasuredHeight);
(1)添加是否需要换行判断
if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
}
(2)解析父亲能够给我的参考大小
/**
* 1.6解析父亲能够给我的参考大小
* (1)这个大小是根据MeasureSpec去计算出来的
*/
int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的父亲给我的宽度
int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的父亲给我的高度
(3)换行先清空每一行记录的相关值
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
(4)子View要求分配的大小宽高
//(2)换行时子View需要的大小空间(宽高)
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
(1)父亲的宽高仍旧是一个ViewGroup,它的宽高值只是一个参考值,不是确切值。必须考虑父亲的测量模式Mode.
(2)根据子View的度量结果,来重新度量自己ViewGroup
/**
* 1.再度量自己,保存
* (1)为什么要保存?
* (2)测量完之后保存是为了通过getMeasuredWidth()与getMeasuredHeight()方法获取到值。
* (3)自己的宽高取决于子View的宽高情况
* (4)根据子View的度量结果,来重新度量自己ViewGroup
* (5)作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth: parentNeededWidth;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ?selfHeight: parentNeededHeight;
setMeasuredDimension(realWidth,realHeight);
(1)getLeft、getTop、getRight、getBottom针对的是视图坐标系。
(2)主要是因为其是针对父View做布局。
(3)布局第一个节点,当把第一个布局放在父View上面的时候,第一个节点的位置取决于与父View的左边与顶部距离。右边距离是子View宽度+子View与左边的距离。高度是子View高度+与父View顶部的距离。
(4)问题的关键是计算第一个节点的左边界与上边界。
int curL = getPaddingLeft();
int curT = getPaddingTop();
(5)第二行的View如何摆放?
allLines.add(lineViews);
lineHeights.add(lineHeight);
/**
* 3.处理最后一行数据
* (1)原因是最后一行不会走换行的逻辑,也就记录不了最后一行的子View与行数
* (2)所以单独做处理
*/
if (i == childCount - 1) {
allLines.add(lineViews);
lineHeights.add(lineHeight);
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
}
(6)一行一行摆放
/**
* 1.布局摆放
* (1)子View到底在父View的哪个位置
* (2)获取第一个子View摆放在父View中的左边距与顶边距
* (3)一行一行进行布局
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int lineCount = allLines.size();
int curL = getPaddingLeft();
int curT = getPaddingTop();
for(int i = 0 ; i < lineCount;i++){
List<View> lineViews = allLines.get(i);
int lineHeight = lineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++){
View view = lineViews.get(j);
int left = curL;
int top = curT;
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
view.layout(left,top,right,bottom);
curL = right + mHorizontalSpacing;
}
curT = curT + lineHeight + mVerticalSpacing;
curL = getPaddingLeft();
}
}
(1)在measure()过程结束后就可以获取到对应的值;
(2)通过setMeasuredDimension()方法来进行设置的.
(1)在layout()过程结束后才能获取到;
(2)通过视图右边的坐标减去左边的坐标计算出来的.
(1)在度量onMeasure之后,布局onLayout()之前,要获取到一个View的宽与高,都要使用度量之后的值,而不要使用度量之前的值。
(1)因为父View会度量子View,而子View又要度量子View.所以如果子View度量多次,就会导致onMeasure执行多次。这是由它的父View决定的。
(2)所以在onMeasure测量的时候,一些测量记录也需要清零
/**
* 清空测量参数,避免内存抖动
*/
private void clearMeasureParams() {
allLines.clear();
lineHeights.clear();
}
(1)如果在onMeasure中去初始化集合,在onMeasure多次调用时会出现内存抖动
private void initMeasureParams(){
allLines = new ArrayList<>();
lineHeights = new ArrayList<>();
}
(2)因为多次初始化集合,会在内存中分配空间,导致内存产生不连续存放空间,如果某一时刻需要申请大量的内存,需要连续内存空间时,就会由GC触发内存回收,从而引发内存抖动。即内存一直处于申请回收申请回收过程。
package com.gdc.knowledge.highui.flowlayout;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* @author XiongJie
* @version appVer
* @Package com.gdc.knowledge.highui.flowlayout
* @file
* @Description:流式布局
* @date 2021-6-7 14:26
* @since appVer
*/
public class FlowLayout extends ViewGroup {
private static final String TAG = "FlowLayout";
private int mHorizontalSpacing = dp2px(16); //每个item横向间距
private int mVerticalSpacing = dp2px(8); //每个item纵向间距
private List<List<View>> allLines = new ArrayList<>(); // 记录所有的行,一行一行的存储,用于layout
List<Integer> lineHeights = new ArrayList<>(); // 记录每一行的行高,用于layout
/**
* 1.此构造函数在Java代码中去new的时候调用
* @param context
*/
public FlowLayout(Context context) {
super(context);
}
/**
* 1.在XML布局中使用的时候调用。
* (1)XML以序列化的方式去创建对象
* (2)解析的函数是在LayoutInflater中去解析XML的内容。
*
* 2.序列化
* (1)可以自定义序列化解析,在IOT中使用的最多.
* (2)IOT协议,序列化是一套数据交换的规则
* (3)物联网:蓝牙 传递的数据,串口
* (4)NFC:射频
*
* 3.通过反射去hook到函数去构造View
* @param context
* @param attrs
*/
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 1.自定义Style时调用
* (1)有黑白主题时就使用此构造创建对象
* @param context
* @param attrs
* @param defStyleAttr
*/
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 清空测量参数,避免内存抖动
*/
private void clearMeasureParams() {
allLines.clear();
lineHeights.clear();
}
/**
* 1.自定义属性
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
/*@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}*/
/**
* 1.为什么要实现onMeasure?
* (1)测量
* (2)先测量子,再测量自己。(先测量小房间,再测量整套房子)多数情况是这样测量
* (3)ViewPager测量比较特殊,只需要测自己。
*
* 2.需要解决widthMeasureSpec,heightMeasureSpec从哪里来的问题?
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//解决内存抖动
clearMeasureParams();
/**
* 1.1先度量孩子
* (1)度量孩子主要是去解析layout_width与layout_height属性
* (2)要将layout_width与layout_height属性变成具体的大小,dp或dip值
*/
int childCount = getChildCount();
/**
* 1.2getChildMeasureSpec(int spec, int padding, int childDimension)
*
* - 第一个参数spec来自于父级布局指定的大小,父亲的大小是父亲给的。
* - 第二个参数padding.要在父亲的空间大小上分配一块区域作为孩子的大小空间,至少要与父亲之间有一定的间隔。所以父亲要减去这一个padding才是当前孩子的大小空间。
* - 第三个参数childDimension指的是孩子需要的大小空间。
*
* 1.3以下步骤拿到的是距离父亲的左右上下的padding
*
* 1.4childLP.width、childLP.height指的是xml的宽与高,也就是孩子需要的大小空间。
*
* 1.5通过getChildMeasureSpec的计算就得到了具体的值
*/
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
/**
* 1.6解析父亲能够给我的参考大小
* (1)这个大小是根据MeasureSpec去计算出来的
*/
int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的父亲给我的宽度
int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的父亲给我的高度
/**
* 2.计算流式布局的宽高
* 2.1宽度是每一行中最宽的一个组合项(取决于几个标签的组合后最宽的那一组的宽度)的宽度。
* 2.2流式布局的高度是所有行数的高度之和。
*/
List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
int lineWidthUsed = 0; //记录这行已经使用了多宽的size
int lineHeight = 0; // 一行的行高
/**
* 3.子View要求的父ViewGroup的宽高
*/
int parentNeededWidth = 0; // measure过程中,子View要求的父ViewGroup的宽
int parentNeededHeight = 0; // measure过程中,子View要求的父ViewGroup的高
for(int i = 0 ; i < childCount; i++){
View childView = getChildAt(i);
//1.1.1将layoutParams转变为measureSpec
LayoutParams childLP = childView.getLayoutParams();
if (childView.getVisibility() != View.GONE) {
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//1.1.2获取子View的度量宽高
int childMeasuredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();
//2.3.3判断是否需要换行
if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
//(1)一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来
allLines.add(lineViews);
lineHeights.add(lineHeight);
/**
* (2)换行时子View需要的大小空间(宽高)
* 高度=向父亲要求分配的高度+单个view行高+纵向间距
* 宽度=最大值(向父亲要求分配的宽度+单个view宽度+横向间距)
*/
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}
//2.3.1view 是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
lineViews.add(childView);
//2.3.2每行都会有自己的宽和高
lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childMeasuredHeight);
/**
* 3.处理最后一行数据
* (1)原因是最后一行不会走换行的逻辑,也就记录不了最后一行的子View与行数
* (2)所以单独做处理
*/
if (i == childCount - 1) {
allLines.add(lineViews);
lineHeights.add(lineHeight);
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
}
}
}
/**
* 1.再度量自己,保存
* (1)为什么要保存?
* (2)测量完之后保存是为了通过getMeasuredWidth()与getMeasuredHeight()方法获取到值。
* (3)自己的宽高取决于子View的宽高情况
* (4)根据子View的度量结果,来重新度量自己ViewGroup
* (5)作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth: parentNeededWidth;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ?selfHeight: parentNeededHeight;
/**
* 1.记录的是子View真正需要的宽和高
* (1)不能按如下方式保存是因为父级ViewGroup测量模式的大小值只是一个参考值,需要根据子View的度量
* 结果,来重新度量自己的大小。然后保存。
* setMeasuredDimension(parentNeededWidth,parentNeededHeight);
*/
setMeasuredDimension(realWidth,realHeight);
}
/**
* 1.布局摆放
* (1)子View到底在父View的哪个位置
* (2)获取第一个子View摆放在父View中的左边距与顶边距
* (3)一行一行进行布局
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int lineCount = allLines.size();
int curL = getPaddingLeft();
int curT = getPaddingTop();
for(int i = 0 ; i < lineCount;i++){
List<View> lineViews = allLines.get(i);
int lineHeight = lineHeights.get(i);
// @TODO 要考虑gravity属性的情况计算宽高
for (int j = 0; j < lineViews.size(); j++){
View view = lineViews.get(j);
int left = curL;
int top = curT;
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
view.layout(left,top,right,bottom);
curL = right + mHorizontalSpacing;
}
curT = curT + lineHeight + mVerticalSpacing;
curL = getPaddingLeft();
}
}
public static int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources
.getSystem().getDisplayMetrics());
}
}
(1)盒子套盒子
(2)如果有margin是相对于它的父view来说的
(1)需要判断子View是否可见的情况
if (childView.getVisibility() != View.GONE) {}
感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!
原文链接:https://blog.csdn.net/xiogjie_67/article/details/117675880
作者:狗蛋你别跑
链接:http://www.phpheidong.com/blog/article/90088/67b2735ea0f3d4ff76cf/
来源:php黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 php黑洞网 All Rights Reserved 版权所有,并保留所有权利。 京ICP备18063182号-4
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!