面试题 1:请举例说明自定义 View 中模板方法模式的应用
考点分析
此问题主要考查对模板方法模式的理解,以及该模式在 Android 自定义 View 生命周期方法里的实际运用。
回答内容
模板方法模式定义了一个操作的算法骨架,把一些步骤的实现延迟到子类。在 Android 自定义 View 中,View
类提供了一系列生命周期方法,像 onMeasure()
、onLayout()
、onDraw()
等,这些构成了绘制 View 的算法骨架,开发者可重写这些方法实现特定逻辑。
java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
// 自定义圆形 View 类,继承自 View
public class CustomCircleView extends View {
// 用于绘制的画笔对象
private Paint paint;
// 构造函数,接收上下文参数
public CustomCircleView(Context context) {
super(context);
// 初始化画笔
init();
}
// 初始化画笔的方法
private void init() {
// 创建一个新的画笔对象
paint = new Paint();
// 设置画笔颜色为蓝色
paint.setColor(Color.BLUE);
// 设置画笔样式为填充
paint.setStyle(Paint.Style.FILL);
}
// 重写 onMeasure 方法,用于测量 View 的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 期望的大小,可根据实际情况调整
int desiredSize = 200;
// 获取宽度的测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// 获取宽度的测量大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
// 获取高度的测量模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 获取高度的测量大小
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
// 根据宽度的测量模式确定最终宽度
if (widthMode == MeasureSpec.EXACTLY) {
// 如果是精确模式,使用测量大小
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
// 如果是最大模式,取期望大小和测量大小的最小值
width = Math.min(desiredSize, widthSize);
} else {
// 如果是未指定模式,使用期望大小
width = desiredSize;
}
// 根据高度的测量模式确定最终高度
if (heightMode == MeasureSpec.EXACTLY) {
// 如果是精确模式,使用测量大小
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
// 如果是最大模式,取期望大小和测量大小的最小值
height = Math.min(desiredSize, heightSize);
} else {
// 如果是未指定模式,使用期望大小
height = desiredSize;
}
// 设置测量好的宽度和高度
setMeasuredDimension(width, height);
}
// 重写 onDraw 方法,用于绘制 View 的内容
@Override
protected void onDraw(Canvas canvas) {
// 获取 View 宽度的一半,作为圆心的 x 坐标
int centerX = getWidth() / 2;
// 获取 View 高度的一半,作为圆心的 y 坐标
int centerY = getHeight() / 2;
// 取圆心 x 和 y 坐标的最小值作为半径
int radius = Math.min(centerX, centerY);
// 使用画笔在画布上绘制圆形
canvas.drawCircle(centerX, centerY, radius, paint);
}
}
从源码层面来看,View
类中的 onMeasure()
、onLayout()
、onDraw()
方法本身有默认实现,但这些实现可能不符合特定需求。例如,View
类的 onMeasure()
方法默认只是简单处理,没有考虑复杂的测量逻辑。自定义 View 时,重写这些方法就如同在模板方法模式中,子类根据自身需求实现父类定义的抽象步骤。CustomCircleView
类重写 onMeasure()
方法确定 View 的大小,重写 onDraw()
方法绘制圆形,父类控制算法结构,子类实现具体步骤,体现了模板方法模式。
面试题 2:在自定义 View 中如何运用策略模式实现不同的绘制效果
考点分析
该问题考查对策略模式的掌握,以及如何在自定义 View 中灵活切换不同的绘制策略。
回答内容
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。在自定义 View 中,可根据不同情况使用不同的绘制策略。
java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
// 绘制策略接口,定义了绘制的抽象方法
interface DrawingStrategy {
// 在画布上进行绘制的方法,接收画布、画笔、宽度和高度作为参数
void draw(Canvas canvas, Paint paint, int width, int height);
}
// 矩形绘制策略类,实现了 DrawingStrategy 接口
class RectangleDrawingStrategy implements DrawingStrategy {
// 实现绘制矩形的逻辑
@Override
public void draw(Canvas canvas, Paint paint, int width, int height) {
// 在画布上绘制矩形
canvas.drawRect(0, 0, width, height, paint);
}
}
// 圆形绘制策略类,实现了 DrawingStrategy 接口
class CircleDrawingStrategy implements DrawingStrategy {
// 实现绘制圆形的逻辑
@Override
public void draw(Canvas canvas, Paint paint, int width, int height) {
// 计算圆心的 x 坐标
int centerX = width / 2;
// 计算圆心的 y 坐标
int centerY = height / 2;
// 取圆心 x 和 y 坐标的最小值作为半径
int radius = Math.min(centerX, centerY);
// 在画布上绘制圆形
canvas.drawCircle(centerX, centerY, radius, paint);
}
}
// 自定义形状 View 类,继承自 View
public class CustomShapeView extends View {
// 当前使用的绘制策略
private DrawingStrategy drawingStrategy;
// 用于绘制的画笔对象
private Paint paint;
// 构造函数,接收上下文参数
public CustomShapeView(Context context) {
super(context);
// 创建一个新的画笔对象
paint = new Paint();
// 设置画笔颜色为红色
paint.setColor(Color.RED);
// 设置画笔样式为填充
paint.setStyle(Paint.Style.FILL);
// 默认使用矩形绘制策略
drawingStrategy = new RectangleDrawingStrategy();
}
// 设置绘制策略的方法
public void setDrawingStrategy(DrawingStrategy drawingStrategy) {
// 更新当前使用的绘制策略
this.drawingStrategy = drawingStrategy;
// 通知 View 重绘
invalidate();
}
// 重写 onDraw 方法,用于绘制 View 的内容
@Override
protected void onDraw(Canvas canvas) {
// 获取 View 的宽度
int width = getWidth();
// 获取 View 的高度
int height = getHeight();
// 如果绘制策略不为空
if (drawingStrategy != null) {
// 调用当前绘制策略的 draw 方法进行绘制
drawingStrategy.draw(canvas, paint, width, height);
}
}
}
从源码层面看,策略模式将不同的绘制算法封装在不同的策略类中,如 RectangleDrawingStrategy
和 CircleDrawingStrategy
。CustomShapeView
类通过持有 DrawingStrategy
接口的引用,实现了绘制策略的切换。当调用 setDrawingStrategy()
方法时,只需传入不同的策略对象,就可以改变绘制行为,而不需要修改 CustomShapeView
类的核心逻辑。这种设计使得代码的可维护性和扩展性得到了提高。
面试题 3:简述观察者模式在自定义 View 中的应用场景及实现方式
考点分析
此问题考查对观察者模式的理解,以及如何在自定义 View 中实现状态监听和通知机制。
回答内容
观察者模式定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知并更新。在自定义 View 中,可用于监听 View 的状态变化。
java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
// 进度改变监听器接口,定义了进度改变时的回调方法
interface ProgressChangeListener {
// 当进度改变时调用的方法,接收新的进度值作为参数
void onProgressChanged(int progress);
}
// 自定义进度条 View 类,继承自 View
public class CustomProgressBar extends View {
// 当前的进度值
private int progress;
// 存储进度改变监听器的列表
private List<ProgressChangeListener> listeners;
// 用于绘制的画笔对象
private Paint paint;
// 构造函数,接收上下文参数
public CustomProgressBar(Context context) {
super(context);
// 初始化进度为 0
progress = 0;
// 创建一个新的监听器列表
listeners = new ArrayList<>();
// 创建一个新的画笔对象
paint = new Paint();
// 设置画笔颜色为绿色
paint.setColor(Color.GREEN);
// 设置画笔样式为填充
paint.setStyle(Paint.Style.FILL);
}
// 添加进度改变监听器的方法
public void addProgressChangeListener(ProgressChangeListener listener) {
// 将监听器添加到列表中
listeners.add(listener);
}
// 移除进度改变监听器的方法
public void removeProgressChangeListener(ProgressChangeListener listener) {
// 从列表中移除指定的监听器
listeners.remove(listener);
}
// 设置进度的方法
public void setProgress(int progress) {
// 更新当前的进度值
this.progress = progress;
// 通知所有监听器进度已改变
notifyListeners();
// 通知 View 重绘
invalidate();
}
// 通知所有监听器进度已改变的方法
private void notifyListeners() {
// 遍历监听器列表
for (ProgressChangeListener listener : listeners) {
// 调用每个监听器的 onProgressChanged 方法
listener.onProgressChanged(progress);
}
}
// 重写 onDraw 方法,用于绘制进度条
@Override
protected void onDraw(Canvas canvas) {
// 获取 View 的宽度
int width = getWidth();
// 获取 View 的高度
int height = getHeight();
// 根据当前进度计算进度条的宽度
int progressWidth = (int) (width * ((float) progress / 100));
// 在画布上绘制进度条
canvas.drawRect(0, 0, progressWidth, height, paint);
}
}
从源码层面来看,CustomProgressBar
类维护了一个 ProgressChangeListener
列表,当进度发生变化时,调用 notifyListeners()
方法遍历列表,通知所有监听器进度已改变。这类似于 Android 系统中 LiveData
的实现机制,LiveData
也是通过维护一个观察者列表,当数据发生变化时通知所有观察者。在自定义 View 中使用观察者模式,可以实现 View 状态变化的监听和响应,提高代码的可维护性和扩展性。
面试题 4:请说明组合模式在自定义 ViewGroup 中的体现
考点分析
该问题考查对组合模式的认识,以及如何在自定义 ViewGroup 中构建 "部分 - 整体" 的层次结构。
回答内容
组合模式将对象组合成树形结构以表示 "部分 - 整体" 的层次结构,用户对单个对象和组合对象的使用具有一致性。在 Android 中,ViewGroup
是组合模式的典型应用。
java
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
// 自定义线性布局 ViewGroup 类,继承自 ViewGroup
public class CustomLinearLayout extends ViewGroup {
// 构造函数,接收上下文参数
public CustomLinearLayout(Context context) {
super(context);
}
// 重写 onLayout 方法,用于布局子 View
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 获取子 View 的数量
int childCount = getChildCount();
// 当前子 View 的顶部位置
int currentTop = 0;
// 遍历所有子 View
for (int i = 0; i < childCount; i++) {
// 获取当前子 View
View child = getChildAt(i);
// 获取子 View 的测量宽度
int childWidth = child.getMeasuredWidth();
// 获取子 View 的测量高度
int childHeight = child.getMeasuredHeight();
// 布局子 View 的位置
child.layout(0, currentTop, childWidth, currentTop + childHeight);
// 更新当前顶部位置
currentTop += childHeight;
}
}
// 重写 onMeasure 方法,用于测量子 View 和自身的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取子 View 的数量
int childCount = getChildCount();
// 子 View 的总高度
int totalHeight = 0;
// 子 View 的最大宽度
int maxWidth = 0;
// 遍历所有子 View
for (int i = 0; i < childCount; i++) {
// 获取当前子 View
View child = getChildAt(i);
// 测量子 View 的大小
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 累加子 View 的高度
totalHeight += child.getMeasuredHeight();
// 更新最大宽度
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
}
// 设置自身的测量宽度和高度
setMeasuredDimension(maxWidth, totalHeight);
}
}
从源码层面看,ViewGroup
类本身就体现了组合模式的思想。ViewGroup
可以包含多个子 View
或 ViewGroup
,形成一个树形结构。CustomLinearLayout
继承自 ViewGroup
,重写 onMeasure()
方法测量子 View
的大小并确定自身大小,重写 onLayout()
方法布局子 View
的位置。用户可以像操作单个 View
一样操作 CustomLinearLayout
,而不需要关心其内部子 View
的具体实现。这种设计使得代码的结构更加清晰,易于维护和扩展。