Android面试自定义View
Posted Rose J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android面试自定义View相关的知识,希望对你有一定的参考价值。
目录
自定义View和自定义ViewGroup的区别在于
自定义View主要是实现onMeasure + onDraw(注重绘制)
自定义ViewGroup主要是实现onMeasure + onLayout(注重布局)
1.继承ViewGourp派生特殊的Layout
(手写流式布局(仿淘宝))
这种方法主要用于实现自定义的布局,即除了LinearLayout,RelativeLayout,FrameLayout这几种系统的布局以外,重新定义一种新布局。注重测量和布局
样例
demo运行
构造函数
//在new一个的时候,只需要传一个参数public FlowLayout(Context context) {
super(context);
}
//反射
//xml解析的时候需要通过反射,反射的时候会应用这个构造函数,来构建对应的java类型的flowLayoutpublic FlowLayout(Context context,AttributeSet attrs) {
super(context, attrs);
}
//主题style
//xml解析时如果要用到自定义主题
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//第四个参数是一个style的资源引用(为view设置style属性),支持view的默认值,只有第三个参数为0或者未在theme中设置时才会生效,这个构造函数几乎不会用到
public FlowLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
重写方法
onMeasure
问题:如下,父布局宽度为wrap_content,子布局为match_parent,子布局TextView的宽度为多宽?
答案是:不知道,但是我们可以通过onMeasure的实现逻辑去知道。
onMeasure
是用来确定子View大小
确定子View坐标
最后就可以确定自己的大小(按照子View的坐标,计算并且相加,最终得到的就是view的大小
ps:ViewGroup 和view 大小相当于,买房子的合同面积(建筑面积)和实际使用面积)、
//度量:具体规划
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
在onMeasure传进来的是父布局给的大小宽高,实际的使用大小由子布局来确定。
所以Measure的作用是 通过测量孩子布局的大小,来计算出自己的大小
View的层级结构
DecorView会调用ViewGroup的onMeasure函数,然后在onMeasure函数中调用子元素的mesure函数,然后子元素又会调用自己的onMeasure函数,又会在自己onMeasure函数中调用子元素的mesure函数,层层遍历,保证所有的子元素都被测量,
1.测量子元素宽高
在onMeasure方法中,先对所有行和每一行的行高初始化
initMeasureParams();//初始化
在onMeasure方法中,我们首先要拿到子元素的数量,再通过getLayoutParams去获取子元素的宽高,我们不会获取到具体的数值,这里引进了一个MeasureSpec
MeasureSpec是什么
MeasureSpec是View中的内部类,基本都是二进制运算,由于int是32位的,用高两位表示mode,低30位表示size,MODE_SHIFT=30的作用是移位
UNSPECIFIED:不对View大小做限制,系统使用
EXACTLY:确切的大小:如100dp
AT_MOST:大小不可超过某数值,如:match_Parent,最大不能超过父布局
MeasureSpec通过父布局提供的大小,减去padding的约束,然后再在所剩区域内获取子元素的大小 最终计算得出的MeasureSpec,然后再通过measure方法测量校准后,获取子view的宽高(子View可能是match_Parent或者wrap_content属性)
然后通过子view的宽度和来和父布局给的宽度做判断是否需要换行
然后每次记录下自己的宽高
因为最后一个元素会跳出判断,所以要特别处理一下
2.确定自己大小
通过子view的测量结果,来重新测量自己的ViewGroup
onLayout
根据子元素的坐标位置,一行一行的确定子元素
onDraw
流式布局没有特殊的绘制,主要注重测量和布局
全部代码
public class FlowLayout extends ViewGroup {
private static final String TAG ="FlowLayout";
private int mHorizontalSpacing =dp2px(16);
private int mVerticalSpacing =dp2px(8);//可以加入空格变量
private static int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp, Resources.getSystem().getDisplayMetrics());
}
private List<List<View>> allLines;//记录所有的行,一行一行的存储,用于layout
List<Integer> lineHeights = new ArrayList<>();// 记录每一行的行高,用于layout
//在new一个的时候,只需要传一个参数
public FlowLayout(Context context)
{
super(context);
}
//反射
//xml解析的时候需要通过反射,反射的时候会应用这个构造函数,来构建对应的java类型的flowLayout
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
//主题style
//xml解析时如果要用到自定义主题
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void initMeasureParams(){
allLines =new ArrayList<>();
lineHeights =new ArrayList<>();
}
//度量:具体规划
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//度量孩子
initMeasureParams();//初始化
int childCount =getChildCount();//首先拿到子元素的数量
int paddingLeft =getPaddingLeft();
int paddingRight=getPaddingRight();
int paddingTop=getPaddingTop();
int paddingBottom=getPaddingBottom();//拿到内边距
List<View> lineView =new ArrayList<>();//保存一行中的所有的View
int lineWidthUsed =0;//记录这行已经使用了多宽的size
int lineHeightUsed =0;//一行的行高
//基于父布局给的宽高
int selfWidth =MeasureSpec.getSize(widthMeasureSpec);//ViewGroup解析的宽度
int selfHigjt=MeasureSpec.getSize(heightMeasureSpec);//ViewGroup解析的高度
int parentNeededWidth=0;// measure 过程中,子View要求的父ViewGroup的宽
int parentNeededHeight=0;// measure 过程中,子View要求的父ViewGroup的高
for (int i=0;i<childCount;i++){
View childView=getChildAt(i);//拿到子元素引用
LayoutParams childLP=childView.getLayoutParams();//拿到 子元素设置的宽高值 MATCH_PARENT = -1 WRAP_CONTENT = -2 -->转为size(不会有明确的size 手机大小不同) -->MeasureSpec
int childWidthMeasureSpec=getChildMeasureSpec(widthMeasureSpec,paddingLeft+paddingRight,childLP.width);
int childHightMeasureSpec=getChildMeasureSpec(heightMeasureSpec,paddingTop+paddingBottom,childLP.height);
childView.measure(childWidthMeasureSpec,childHightMeasureSpec);
//获取子View的宽高
int childMeasuredWidth=childView.getMeasuredWidth();
int childMeasuredHight=childView.getMeasuredHeight();
//如果需要换行
if (childMeasuredWidth +lineWidthUsed+mHorizontalSpacing>selfWidth){
allLines.add(lineView);//记录行
lineHeights.add(lineHeightUsed);//记录行高
//一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来
parentNeededHeight=parentNeededHeight + lineHeightUsed;
parentNeededWidth = Math.max(parentNeededWidth,lineWidthUsed);
lineView =new ArrayList<>();
lineWidthUsed =0;
lineHeightUsed =0;//重置
}
lineView.add(childView);
//每行都有自己的宽和高
lineWidthUsed=lineWidthUsed + childMeasuredWidth ;
lineHeightUsed=lineHeightUsed + childMeasuredHight ;
//处理最后一行数据
if (i == childCount-1){
allLines.add(lineView);//记录行
lineHeights.add(lineHeightUsed);//记录行高
parentNeededHeight=parentNeededHeight + lineHeightUsed;
parentNeededWidth = Math.max(parentNeededWidth,lineWidthUsed);
}
}
//根据子View的测量结果,来重新测量自己的ViewGroup
//作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来测量
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
int realWidth=(widthMode ==MeasureSpec.EXACTLY)?selfWidth:parentNeededWidth;
int realHeight=(heightMode ==MeasureSpec.EXACTLY)?selfHigjt:parentNeededHeight;
//确定自己的大小
setMeasuredDimension(realWidth,realHeight);
}
//布局:所有的子View进行布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int curL =getPaddingLeft();//左边距
int curT =getPaddingTop();//上边距
int lineCount =allLines.size();//获取layout中的行数
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.getWidth();
// int bottom=top+view.getHeight();
int right=left+view.getMeasuredWidth();
int bottom=top+view.getMeasuredHeight();
view.layout(left,top,right,bottom);
curL=right+mHorizontalSpacing;
}
curL =getPaddingLeft();
curT =curT +lineHeight;
}
}
//绘制:将View绘制到屏幕上
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
2.继承View重写onDraw方法
这种方法主要用于实现一些不规则的效果,即这种效果不方便通过布局的组合方式来达到,往往需要静态或者动态地显示一些不规则的图形。注重绘制。
这种方式需要自己支持wrap_content,并且padding也需要自己处理
Paint画笔标志位Flag含义
Paint.ANTI_ALIAS_FLAG :抗锯齿标志
Paint.FILTER_BITMAP_FLAG : 使位图过滤的位掩码标志
Paint.DITHER_FLAG : 使位图进行有利的抖动的位掩码标志
Paint.UNDERLINE_TEXT_FLAG : 下划线
Paint.STRIKE_THRU_TEXT_FLAG : 中划线
Paint.FAKE_BOLD_TEXT_FLAG : 加粗
Paint.LINEAR_TEXT_FLAG : 使文本平滑线性扩展的油漆标志
Paint.SUBPIXEL_TEXT_FLAG : 使文本的亚像素定位的绘图标志
Paint.EMBEDDED_BITMAP_TEXT_FLAG : 绘制文本时允许使用位图字体的绘图标志
绘制
运行
margin属性有效
padding和wrap_content
padding属性和wrap_content属性无效
在绘制的时候考虑padding,padding属性和wrap_content属性生效
上述代码中心思想就是在绘制的时候考虑到View四周的空白即可,其中圆心和半径都会考虑到View四周的padding,从而做相应的调整
以上是关于Android面试自定义View的主要内容,如果未能解决你的问题,请参考以下文章