广告

Android自定义视图构造函数详解与实战技巧

Android自定义视图构造函数详解与实战技巧

构造函数的角色与调用顺序

Android自定义视图通常提供多种构造函数入口,以适应不同的创建场景。从 XML 布局解析到动态创建再到主题样式渲染,不同构造函数承担不同职责,确保视图在各种环境中都能正确初始化。

在 XML 布局中使用时,最常调用的是 Context + AttributeSet 的构造函数,它会把布局属性传递给自定义视图,方便后续解析自定义属性。而在代码中通过 new MyView(context) 直接创建时,通常会调用最简单的 Context 构造函数。

为确保兼容性和可扩展性,通常会在构造函数中进行统一初始化,通过 init() 等方法把属性解析和成员变量赋值集中处理,这样可以避免重复逻辑并降低出错概率。

Android自定义视图构造函数详解与实战技巧

public class MyCustomView extends View {public MyCustomView(Context context) {this(context, null);}public MyCustomView(Context context, AttributeSet attrs) {this(context, attrs, R.attr.myViewStyle);}public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(attrs);}@TargetApi(21)public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(attrs);}private void init(AttributeSet attrs) {// 解析自定义属性、初始化成员}
}

如何选择合适的构造函数版本

若仅从代码创建视图,优先使用 Context 构造函数,并在初始化阶段处理默认值。若要在 XML 中使用自定义属性,应实现 Context + AttributeSet 构造函数以及初始化流程。

从 API 21 起,4 参数构造函数(Context, AttributeSet, int, int)逐步变得可选,但在某些风格化场景下仍然有用,尤其是当需要显式指定 defStyleRes 时。

为了兼容性和清晰性,常见的做法是将 3 参数版本作为主入口,并在 4 参数版本上加以扩展;在 4 参数版本上使用注解 @RequiresApi 来标记仅在高版本设备可用。

public class MyCustomView extends View {public MyCustomView(Context context) {this(context, null);}public MyCustomView(Context context, AttributeSet attrs) {this(context, attrs, R.attr.myViewStyle);}public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(attrs);}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(attrs);}private void init(AttributeSet attrs) {// 属性解析与初始化}
}

Android自定义视图的测量与绘制核心要点

onMeasure 的正确实现要点

onMeasure 是决定视图大小的核心点,它会根据父布局的约束和自身的期望尺寸来计算最终宽高。理解 MeasureSpec 的模式(EXACTLY、AT_MOST、UNSPECIFIED)是关键。

在实现 onMeasure 时,通常需要先理解 期望尺寸、父布局约束及最小尺寸之间的关系,然后使用 resolveSize 或自定义测量逻辑得到最终尺寸并调用 setMeasuredDimension。

正确处理 wrap_content 与 match_parent 的组合关系,可以避免屏幕密度带来的不必要绘制开销,同时确保自定义视图在不同场景下表现一致。

private int mDesiredWidth = 200;
private int mDesiredHeight = 200;@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = measureDimension(mDesiredWidth, widthMeasureSpec);int height = measureDimension(mDesiredHeight, heightMeasureSpec);setMeasuredDimension(width, height);
}private int measureDimension(int desired, int measureSpec) {int result = desired;int mode = MeasureSpec.getMode(measureSpec);int size = MeasureSpec.getSize(measureSpec);if (mode == MeasureSpec.EXACTLY) {result = size;} else if (mode == MeasureSpec.AT_MOST) {result = Math.min(desired, size);}return result;
}

onDraw 的性能要点

onDraw 的性能直接影响滑动流畅度,应避免在绘制过程中创建对象、执行复杂计算或进行 I/O。应尽量将 Paint、Path 等对象预创建好,重复绘制时复用。

在绘制时应尽量减少区域面积,必要时进行裁剪(clipRect)以降低像素处理量,并在绘制完成后尽快释放临时资源,保持渲染管线的连贯性。

通过开启硬件加速、使用位图缓存和避免过度抗锯齿等手段,可以提升大面积绘制的性能表现。

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);// 仅绘制需要的部分,避免每帧创建对象canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);canvas.drawCircle(getWidth()/2f, getHeight()/2f, Math.min(getWidth(), getHeight())/2f, mPaint);
}

自定义属性注入与主题适配的实战

在 XML 中使用自定义属性

通过 attrs.xml 声明自定义属性集合,并在布局中通过 app 命名空间进行赋值,可以实现直观的 UI 定义与样式复用。

为实现主题切换的兼容性,建议把常用属性放在自定义 View 的属性集合中,并在样式中提供默认值或主题取值。






private void init(AttributeSet attrs) {if (attrs != null) {TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MyCustomView);int color = a.getColor(R.styleable.MyCustomView_viewColor, Color.BLACK);float radius = a.getDimension(R.styleable.MyCustomView_cornerRadius, 0);a.recycle();mPaint.setColor(color);mCornerRadius = radius;}
}

主题与样式的进一步适配

通过样式引用默认风格可以实现一致的外观,例如在布局或主题中绑定一个通用的 Widget 样式,确保不同页面的自定义视图风格统一。

在构造函数中传递 defStyleAttr,能够让系统优先解析布局属性、然后是默认样式、最后才是主题属性,从而实现更强的可定制性与向后兼容性。


自定义属性的进阶使用

结合动态更新与数据驱动的视图行为,可以在数据变化时更新自定义属性并调用 invalidate() 或 requestLayout(),从而达到响应式 UI 效果。

在高性能场景中,避免频繁的布局请求,优先通过 invalidate() 刷新绘制区域,同时通过缓存计算结果来降低重复工作。

public void setViewColor(int color) {if (mPaint.getColor() != color) {mPaint.setColor(color);invalidate(); // 仅重绘,不触发布局}
}

广告

后端开发标签