在Values下面创建一个attr.xml文件
"reference":某一资源id
"color":颜色值
"boolean":布尔值
"dimen":尺寸值
"float":浮点值
"integer":整型值
"string":字符串
"fraction":百分数
"enum":枚举值
"flag":位或运算
ViewGroup绘制流程
注意:View 及ViewGroup基本相同,只是在ViewGroup中不仅要绘制自己,还要绘制其中的子控件,而View 只要绘制自己就可以了
绘制流程分为三步:测量,布局,绘制,分别对应onMeasure(),onLayout(),onDraw()
onMeasure():测量当前控件的大小,为正式布局提供建议(只是建议,用不用还要看onLayout()函数)
主要是setMeasuredDimension(宽,高)设置
三个mode
Unspecified(未指定):父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小
Exactly (完全):父元素 决定子元素的确切大小,子元素将被限定在给定的边界里而忽略他本身的大小
At_most(至多):子元素至多达到指定大小的值
模式提取:
int mode = MeasureSpec.getMode(widthMeasureSpec);
if(mode == MeasureSpec.UNSPECIFIED){ }
if(mode == MeasureSpec.EXACTLY){ }
if(mode == MeasureSpec.AT_MOST){ }
获取 宽高
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
onLayout(): 使用layout()函数对所有子控件进行布局
onDraw():根据布局的位置绘图
public class MyLinearView extends ViewGroup {
public MyLinearView(Context context) {
this(context, null);
}
public MyLinearView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyLinearView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int mearsureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int mearsureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int mearsureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childAt = getChildAt(i);
measureChild(childAt, widthMeasureSpec, heightMeasureSpec);
//获得子控件的高度和宽度
int childWidth = childAt.getMeasuredWidth();
int childHeight = childAt.getMeasuredHeight();
//得到最大宽度 , 并且累加高度
height += childHeight;
width = Math.max(childWidth, width);
}
int finalwidth = 0;
int finalHeight = 0;
if (mearsureWidthMode == MeasureSpec.EXACTLY) {
//使用测量宽度
finalwidth = mearsureWidth;
} else {
//使用计算的宽度
finalwidth = width;
}
if (mearsureHeightMode == MeasureSpec.EXACTLY) {
finalHeight = measureHeight;
} else {
finalHeight = height;
}
setMeasuredDimension(finalwidth, finalHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int top = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childAt = getChildAt(i);
int childWidth = childAt.getMeasuredWidth();
int childHeight = childAt.getMeasuredHeight();
childAt.layout(0, top, childWidth, top + childHeight);
top += childHeight;
}
}
}
获取子控件Margin
如果要自定义ViewGroup支持子控件的layout_margin值,则自定义的ViewGroup类必须重写generateLayoutParams()函数,并且在该函数中返回一个ViewGroup.MarginLayoutParams派生类对象.
需要在修改一点
public class MyLinearView extends ViewGroup {
public MyLinearView(Context context) {
this(context, null);
}
public MyLinearView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyLinearView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int mearsureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int mearsureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int mearsureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childAt = getChildAt(i);
MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();
measureChild(childAt, widthMeasureSpec, heightMeasureSpec);
//获得子控件的高度和宽度
int childWidth = childAt.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
int childHeight = childAt.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
//得到最大宽度 , 并且累加高度
height += childHeight;
width = Math.max(childWidth, width);
}
int finalwidth = 0;
int finalHeight = 0;
if (mearsureWidthMode == MeasureSpec.EXACTLY) {
//使用测量宽度
finalwidth = mearsureWidth;
} else {
//使用计算的宽度
finalwidth = width;
}
if (mearsureHeightMode == MeasureSpec.EXACTLY) {
finalHeight = measureHeight;
} else {
finalHeight = height;
}
setMeasuredDimension(finalwidth, finalHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int top = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childAt = getChildAt(i);
MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();
int childWidth = childAt.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
int childHeight = childAt.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
childAt.layout(0, top+layoutParams.topMargin, childWidth, top + childHeight);
top += childHeight;
}
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
MarginLayoutParams marginLayoutParams = new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
return marginLayoutParams;
}
}
实现FlowLayout 布局
public class FlowLayout extends ViewGroup {
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量模式
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
//测量宽高
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int lineWidth = 0;
int lineHeight = 0;
int height = 0;
int width = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childAt = getChildAt(i);
measureChild(childAt, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) childAt.getLayoutParams();
int childWidth = childAt.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = childAt.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + childWidth > measureWidth) {
height += lineHeight;
//因为当前行放不下当前控件,而将此控件调到下一行,所以将此控件的高度和宽度初始化给lineHeight,lineWidth
lineHeight = childHeight;
lineWidth = childWidth;
} else {
//否则累加值lineWidth , lineHeight 并取最大高度
lineHeight = Math.max(lineHeight, childHeight);
lineWidth += childWidth;
}
//因为最后一行是不会超出width范围的,所以需要单独处理
if (i == childCount - 1) {
height += lineHeight;
width += Math.max(width, lineWidth);
}
//当属性是MeasureSpec.EXACTLY时,那么它的高度是确定的
//当属性是wrap_content时,由于是通过内部控价的大小来最终确定他的大小的,所以它的大小是不确定的
//此时对应的属性是MeasureSpec.At_most,这就需要我们自己计算他应当的大小,并设置进去
int finalWidth = 0;
if (measureWidthMode == MeasureSpec.EXACTLY) {
finalWidth = measureWidth;
} else {
finalWidth = width;
}
int finalHeight = 0;
if (measureHeightMode == MeasureSpec.EXACTLY) {
finalHeight = measureHeight;
} else {
finalHeight = height;
}
setMeasuredDimension(finalWidth, finalHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
int lineWidth = 0;
int lineHeight = 0;
int top = 0;
int left = 0;
for (int i = 0; i < count; i++) {
View childAt = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) childAt.getLayoutParams();
int childWidth = childAt.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = childAt.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (childWidth + lineWidth > getMeasuredWidth()) {
//如果换行,则当前控件将放到下一行,从最左边开始,所以left就是0;
//而top则需要加上 上一行的行高,才是这个控件的top坐标
top += lineHeight;
left = 0;
//同样,重新初始化lineHeight 和 lineWidth
lineHeight = childHeight;
lineWidth = childWidth;
} else {
lineHeight = Math.max(lineHeight, childHeight);
lineWidth += childWidth;
}
//计算childView 的 left top right bottom
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + childAt.getMeasuredWidth();
int bc = tc + childAt.getMeasuredHeight();
childAt.layout(lc, tc, rc,bc);
//将left 作为下一个子控件的起始点
left += childWidth;
}
}
/******************* 设置Margin必须重写底下三个方法 ********************/
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
}
int childCount = mViewBinding.flowlayout.getChildCount();
for (int i = 0; i < childCount; i++) {
View childAt = mViewBinding.flowlayout.getChildAt(i);
DevShapeUtils.shape(DevShape.RECTANGLE)
.radius(6)
.solid(R.color.black)
.into(childAt);
}
13.控件高级属性
gestureDetector手势检测
概述:当用户触摸屏幕的时候,会产生许多手势,如down,up,scroll,fling等
一般用onTouch(MotionEvent)
如果是复杂的手势就用GestureDetector
分为俩个接口
onGestureListener onDoubleTapListener
一个外部类
SimpleOnGestureListener
public class GestureVew implements GestureDetector.OnGestureListener {
//用户按下屏幕就会触发
@Override
public boolean onDown(MotionEvent e) {
return false;
}
//如果按下的时间超过瞬间,而且在按下的时候没有松开或者拖动的,触发
@Override
public void onShowPress(MotionEvent e) {
}
//一次单独的轻击抬起操作,如果在按下去还有其他操作就不行
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
//在屏幕上拖动事件,无论是用手拖动View,还是以抛的动作滚动,都会多次触发这个函数,在ACTION_MOVE动作发生时就会触发该函数
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
//长按触摸屏,超过一定时长,就会触发这个函数
@Override
public void onLongPress(MotionEvent e) {
}
//滑屏,用户按下触摸屏,快速移动后松开,由一个MotionEvent ACTION_DOWN ,多个ACTION_MOVE 一个ACTION_UP触发
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
}
<1>.创建onGestureListener()监听函数
GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
};
或者直接类 implements
GestureDetector.OnGestureListener
<2>.创建GestureDetector
GestureDetector gestureDetector = new GestureDetector(this, onGestureListener);
<3>.拦截onTouch
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
<4>.绑定控件
mViewBinding.tv.setOnTouchListener(this);
https://zhangqifan.blog.csdn.net/article/details/87855781
13.2Window 与 WindowManager
补充几个知识点:
构建其LayoutParams时,Flag和Type 比较重要
Flag:用来控制Window 的显示特性
表示此window不需要获取焦点,不接收各种输入事件,此标记会同时启用FLAG_BOT_TOUCH_MODAL,最终事件会直接传递给下层具有焦点的Window
FLAG_NOT_FOCUSABLE = 0x00000008
自己Window区域内的事件自己处理;自己Window区域外的事件传递给底层Window处理,一般这个选项回默认开启,否则其他Window无法收到事件
FLAG_NOT_TOUCH_MODAL = 0x00000020
可以让Window显示在锁屏上
FLAG_SHOW_WHEN_LOCKED = 0X00080000
Type参数是int类型的,表示Window的类型,Window有是那种类型
应用Window 层级范围 1 - 99
子Window的层级范围 1000-1999
系统Window的层级范围 2000-2999
最好加个权限,适用于系统类型的Window
WindowManager 常用的三个方法
addView updateView removeView
腾讯电脑管家的小火箭