Android学习总结之自定义view设计模式理解
面试题 1:请举例说明自定义 View 中模板方法模式的应用
考点分析
此问题主要考查对模板方法模式的理解,以及该模式在 Android 自定义 View 生命周期方法里的实际运用。
回答内容
模板方法模式定义了一个操作的算法骨架,把一些步骤的实现延迟到子类。在 Android 自定义 View 中,View
类提供了一系列生命周期方法,像 onMeasure()
、onLayout()
、onDraw()
等,这些构成了绘制 View 的算法骨架,开发者可重写这些方法实现特定逻辑。
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 的大小@Overrideprotected 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 的内容@Overrideprotected 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 中,可根据不同情况使用不同的绘制策略。
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 {// 实现绘制矩形的逻辑@Overridepublic void draw(Canvas canvas, Paint paint, int width, int height) {// 在画布上绘制矩形canvas.drawRect(0, 0, width, height, paint);}
}// 圆形绘制策略类,实现了 DrawingStrategy 接口
class CircleDrawingStrategy implements DrawingStrategy {// 实现绘制圆形的逻辑@Overridepublic 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 的内容@Overrideprotected 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 的状态变化。
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);// 初始化进度为 0progress = 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 方法,用于绘制进度条@Overrideprotected 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
是组合模式的典型应用。
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@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {// 获取子 View 的数量int childCount = getChildCount();// 当前子 View 的顶部位置int currentTop = 0;// 遍历所有子 Viewfor (int i = 0; i < childCount; i++) {// 获取当前子 ViewView 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 和自身的大小@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 获取子 View 的数量int childCount = getChildCount();// 子 View 的总高度int totalHeight = 0;// 子 View 的最大宽度int maxWidth = 0;// 遍历所有子 Viewfor (int i = 0; i < childCount; i++) {// 获取当前子 ViewView 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
的具体实现。这种设计使得代码的结构更加清晰,易于维护和扩展。