自定义View
2019-01-24 本文已影响6人
jie啊
继承已有的View,简单改写它们的尺寸:重写onMeasure()
- 重写onMeasure()
- 用getMeasuredWidth()和getMeasuredHeight()获取到测量出的尺寸
- 计算出最终尺寸
- 用setMeasuredDimension(w,h)把结果保存
* 继承已有控件,重写onMeasure
*/
public class SquareImageView extends android.support.v7.widget.AppCompatImageView {
public SquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
int size = Math.max(measuredWidth, measuredHeight);
setMeasuredDimension(size, size); // 保存测得的尺寸
}
//
// @Override
// public void layout(int l, int t, int r, int b) {//直接在此指定宽高,样式虽说改变,但是没有经过测量,父view并不知晓控件宽高已经改写,
//会导致父控件中的子view重叠
// int w = r - l;
// int h = b - t;
// int size = Math.min(w, h);
// super.layout(l, t, r + size, b + size);
//
// }
}
对自定义View完全进行自定义尺寸计算:重写onMeasure()
- 重写onMeasure()
- 计算出自己的尺寸
- 用resolveSize()或者resolveSizeAndState()修正结果
- resolveSize()/resolveSizeAndState()代码原理
- ⾸先⽤ MeasureSpec.getMode(measureSpec) 和 MeasureSpec.getSize(measureSpec) 取出⽗ 对⾃⼰的尺⼨限制类型和具体限制
尺⼨; - 如果 measure spec 的 mode 是 EXACTLY,表示⽗ View 对⼦ View 的尺⼨做出
了精确限制,所以就放弃计算出的 size,直接选⽤ measure spec 的 size; - 如果 measure spec 的 mode 是 AT_MOST,表示⽗ View 对⼦ View 的尺⼨只限
制了上限,需要看情况:- 如果计算出的 size 不⼤于 spec 中限制的 size,表示尺⼨没有超出限制,所
以选⽤计算出的 size; - ⽽如果计算出的 size ⼤于 spec 中限制的 size,表示尺⼨超限了,所以选⽤
spec 的 size,并且在 resolveSizeAndState() 中会添加标志
MEASURED_STATE_TOO_SMALL(这个标志可以辅助⽗ View 做测量和布
局的计算;
- 如果计算出的 size 不⼤于 spec 中限制的 size,表示尺⼨没有超出限制,所
- ⾸先⽤ MeasureSpec.getMode(measureSpec) 和 MeasureSpec.getSize(measureSpec) 取出⽗ 对⾃⼰的尺⼨限制类型和具体限制
- 如果 measure spec 的 mode 是 UNSPECIFIED,表示⽗ View 对⼦ View 没有任
何尺⼨限制,所以直接选⽤计算出的 size,忽略 spec 中的 size。
public class CircleView extends View {
private static final int RADIUS = (int) Utils.dpToPixel(80);//应该在自定义属性值可动态设置
private static final int PADDING = (int) Utils.dpToPixel(30);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.RED);//画布
canvas.drawCircle(RADIUS+PADDING, RADIUS+PADDING, RADIUS, p);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = (PADDING + RADIUS) * 2; //圆的半径加padding
int height = (PADDING + RADIUS) * 2;
width = resolveSizeAndState(width, widthMeasureSpec, 0);////resolveSize()
height = resolveSizeAndState(height, widthMeasureSpec, 0);//resolveSizeAndState是否压缩状态
setMeasuredDimension(width, height);
}
}
⾃定义 Layout:重写 onMeasure() 和 onLayout():TagLayout
- 重写 onMeasure()
- 遍历每个⼦ View,⽤ measureChildWidthMargins() 测量⼦ View
- 需要重写 generateLayoutParams() 并返回 MarginLayoutParams 才能使⽤
measureChildWithMargins() ⽅法
- 需要重写 generateLayoutParams() 并返回 MarginLayoutParams 才能使⽤
- 有些⼦ View 可能需要重新测量(⽐如换⾏处)
- 测量完成后,得出⼦ View 的实际位置和尺⼨,并暂时保存
- measureChildWidthMargins() 的内部实现(代码必读):
通过 getChildMeasureSpec(int spec, int padding, int childDimension) ⽅
法计算出⼦ View 的 widthMeasureSpec 和 heightMeasureSpec,然后调
⽤ child.measure() ⽅法来让⼦ View ⾃我测量;
- 遍历每个⼦ View,⽤ measureChildWidthMargins() 测量⼦ View
- 重写 onLayout()
public class TagLayout extends ViewGroup {
private static final String TAG = "TagLayout";
List<Rect> childrenBounds = new ArrayList<>();//便于存放子view尺寸,容器以情况而定
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
Rect childBounds = childrenBounds.get(i);
child.layout(childBounds.left, childBounds.top, childBounds.right, childBounds.bottom);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//widthMeasureSpec,heightMeasureSpec是TagLayout的测量模式
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthUsed = 0;
int heightUsed = 0;
int lineMaxHeight = 0;
int lineWidthUsed = 0;
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
int specMode = MeasureSpec.getMode(widthMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed);//测量子view,0表示,最右边的view可以放下,避免被压缩
if (specMode != MeasureSpec.UNSPECIFIED && lineWidthUsed + child.getMeasuredWidth() > specWidth) {
lineWidthUsed = 0;
heightUsed += lineMaxHeight;
lineMaxHeight = 0;
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed);//
}
Rect rect;
if (childrenBounds.size() <= i) {
rect = new Rect();
childrenBounds.add(rect);
} else {
rect = childrenBounds.get(i);
}
rect.set(lineWidthUsed, heightUsed,
lineWidthUsed + child.getMeasuredWidth(), heightUsed + child.getMeasuredHeight());
lineWidthUsed += child.getMeasuredWidth();
widthUsed = Math.max(widthUsed, lineWidthUsed);
lineMaxHeight = Math.max(lineMaxHeight, child.getMeasuredHeight());
}
int w = widthUsed;
int h = heightUsed + lineMaxHeight;
setMeasuredDimension(w, h);//保存尺寸
}
//重写
@Override
public LayoutParams generateLayoutParams(AttributeSet p) {
return new MarginLayoutParams(getContext(), p);
}
}