1. 复杂SVG渲染View实现
public class AdvancedSvgView extends View {
private static final String TAG = "AdvancedSvgView";
private SVG svg;
private Map<String, SvgElement> elementMap = new HashMap<>();
private Map<String, Region> hitRegionMap = new HashMap<>();
private Map<String, Paint> elementPaintMap = new HashMap<>();
private Map<String, Object> originalStyles = new HashMap<>();
// 变换参数
private float scaleFactor = 1.0f;
private float translateX = 0f;
private float translateY = 0f;
private final Matrix renderMatrix = new Matrix();
// 交互相关
private GestureDetector gestureDetector;
private OnSvgElementClickListener elementClickListener;
private String selectedElementId;
// 渐变色缓存
private final Map<String, Shader> gradientCache = new HashMap<>();
private final Map<String, Paint> paintCache = new HashMap<>();
// 绘制参数
private final Paint defaultPaint;
private final Paint selectionPaint;
private boolean showHitRegions = false; // 调试用
public interface OnSvgElementClickListener {
void onSvgElementClick(String elementId, SvgElement element);
}
public AdvancedSvgView(Context context) {
super(context);
init();
defaultPaint = createDefaultPaint();
selectionPaint = createSelectionPaint();
}
public AdvancedSvgView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
defaultPaint = createDefaultPaint();
selectionPaint = createSelectionPaint();
}
public AdvancedSvgView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
defaultPaint = createDefaultPaint();
selectionPaint = createSelectionPaint();
}
private void init() {
setupGestureDetector();
setLayerType(LAYER_TYPE_SOFTWARE, null); // 确保兼容性
}
private Paint createDefaultPaint() {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setStyle(Paint.Style.FILL);
return paint;
}
private Paint createSelectionPaint() {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3f);
paint.setColor(Color.RED);
return paint;
}
private void setupGestureDetector() {
gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
} public boolean onSingleTapUp(MotionEvent e) {
handleTap(e.getX(), e.getY());
return true;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
}
// SVG加载方法
public void loadSvgFromResource(int resId) {
try {
svg = SVG.getFromResource(getContext(), resId);
processSvgDocument();
requestLayout();
invalidate();
} catch (SVGParseException e) {
Log.e(TAG, "Error parsing SVG resource", e);
}
}
public void loadSvgFromAssets(String filename) {
try {
svg = SVG.getFromAsset(getContext().getAssets(), filename);
processSvgDocument();
requestLayout();
invalidate();
} catch (SVGParseException | IOException e) {
Log.e(TAG, "Error loading SVG from assets", e);
}
}
public void loadSvgFromString(String svgData) {
try {
svg = SVG.getFromString(svgData);
processSvgDocument();
requestLayout();
invalidate();
} catch (SVGParseException e) {
Log.e(TAG, "Error parsing SVG string", e);
}
}
/**
* 处理SVG文档,提取所有元素和样式
*/
private void processSvgDocument() {
elementMap.clear();
hitRegionMap.clear();
elementPaintMap.clear();
originalStyles.clear();
gradientCache.clear();
paintCache.clear();
if (svg == null || svg.getRootElement() == null) {
return;
}
// 遍历所有元素
traverseElements(svg.getRootElement(), "");
// 预计算点击区域
calculateHitRegions();
}
/**
* 递归遍历SVG元素
*/
private void traverseElements(SvgElement element, String parentPath) {
String elementId = element.getId();
String fullPath = parentPath + (elementId != null ? "." + elementId : "");
if (elementId != null && !elementId.isEmpty()) {
elementMap.put(elementId, element);
storeOriginalStyle(elementId, element);
}
// 处理子元素
if (element instanceof Group) {
Group group = (Group) element;
for (SvgObject child : group.getChildren()) {
if (child instanceof SvgElement) {
traverseElements((SvgElement) child, fullPath);
}
}
} else if (element instanceof SvgElement) {
for (SvgObject child : element.getChildren()) {
if (child instanceof SvgElement) {
traverseElements((SvgElement) child, fullPath);
}
}
}
}
/**
* 存储元素的原始样式
*/
private void storeOriginalStyle(String elementId, SvgElement element) {
Map<String, Object> style = new HashMap<>();
// 存储填充信息
if (element.getFill() != null) {
style.put("fill", element.getFill());
}
// 存储描边信息
if (element.getStroke() != null) {
style.put("stroke", element.getStroke());
}
if (element.getStrokeWidth() != null) {
style.put("strokeWidth", element.getStrokeWidth());
}
originalStyles.put(elementId, style);
}
/**
* 计算点击区域
*/
private void calculateHitRegions() {
for (Map.Entry<String, SvgElement> entry : elementMap.entrySet()) {
String elementId = entry.getKey();
SvgElement element = entry.getValue();
if (element instanceof Path) {
Path pathElement = (Path) element;
android.graphics.Path path = pathElement.getPath();
// 创建点击区域
Region region = new Region();
RectF bounds = new RectF();
path.computeBounds(bounds, true);
// 应用变换矩阵
android.graphics.Path transformedPath = new android.graphics.Path();
path.transform(getRenderMatrix(), transformedPath);
region.setPath(transformedPath, new Region(
(int) Math.floor(bounds.left),
(int) Math.floor(bounds.top),
(int) Math.ceil(bounds.right),
(int) Math.ceil(bounds.bottom)
));
hitRegionMap.put(elementId, region);
}
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
calculateViewport();
calculateHitRegions();
}
/**
* 计算视口变换
*/
private void calculateViewport() {
if (svg == null) return;
float svgWidth = svg.getDocumentWidth();
float svgHeight = svg.getDocumentHeight();
float viewWidth = getWidth();
float viewHeight = getHeight();
if (svgWidth > 0 && svgHeight > 0) {
// 计算缩放比例,保持宽高比
float scaleX = viewWidth / svgWidth;
float scaleY = viewHeight / svgHeight;
scaleFactor = Math.min(scaleX, scaleY);
// 计算居中的偏移量
translateX = (viewWidth - svgWidth * scaleFactor) / 2;
translateY = (viewHeight - svgHeight * scaleFactor) / 2;
// 更新渲染矩阵
renderMatrix.setTranslate(translateX, translateY);
renderMatrix.preScale(scaleFactor, scaleFactor);
}
}
/**
* 获取渲染矩阵
*/
private Matrix getRenderMatrix() {
return renderMatrix;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (svg == null || svg.getRootElement() == null) {
return;
}
canvas.save();
canvas.concat(renderMatrix);
// 渲染SVG
renderSvgElement(canvas, svg.getRootElement());
// 绘制选中状态
if (selectedElementId != null) {
drawSelectionHighlight(canvas, selectedElementId);
}
// 调试:绘制点击区域
if (showHitRegions) {
drawHitRegions(canvas);
}
canvas.restore();
}
/**
* 渲染SVG元素
*/
private void renderSvgElement(Canvas canvas, SvgElement element) {
if (element instanceof Path) {
renderPathElement(canvas, (Path) element);
} else if (element instanceof Group) {
Group group = (Group) element;
for (SvgObject child : group.getChildren()) {
if (child instanceof SvgElement) {
renderSvgElement(canvas, (SvgElement) child);
}
}
} else if (element instanceof SvgElement) {
for (SvgObject child : element.getChildren()) {
if (child instanceof SvgElement) {
renderSvgElement(canvas, (SvgElement) child);
}
}
}
}
/**
* 渲染路径元素
*/
private void renderPathElement(Canvas canvas, Path pathElement) {
String elementId = pathElement.getId();
Paint paint = getPaintForElement(pathElement, elementId);
if (paint != null) {
canvas.drawPath(pathElement.getPath(), paint);
}
}
/**
* 获取元素的绘制Paint
*/
private Paint getPaintForElement(SvgElement element, String elementId) {
// 检查缓存
if (paintCache.containsKey(elementId)) {
return paintCache.get(elementId);
}
Paint paint = new Paint(defaultPaint);
boolean hasFill = false;
// 处理填充
if (element.getFill() != null) {
hasFill = applyFillStyle(paint, element.getFill(), elementId);
}
// 处理描边
if (element.getStroke() != null) {
applyStrokeStyle(paint, element);
}
// 如果没有填充也没有描边,使用默认颜色
if (!hasFill && paint.getStyle() == Paint.Style.FILL) {
paint.setColor(Color.GRAY);
}
paintCache.put(elementId, paint);
return paint;
}
/**
* 应用填充样式
*/
private boolean applyFillStyle(Paint paint, SvgPaint fill, String elementId) {
if (fill instanceof Colour) {
// 纯色填充
Colour colour = (Colour) fill;
paint.setColor(colour.getValue());
paint.setStyle(Paint.Style.FILL);
return true;
} else if (fill instanceof SvgLinearGradient) {
// 线性渐变
SvgLinearGradient gradient = (SvgLinearGradient) fill;
LinearGradient linearGradient = createLinearGradient(gradient, elementId);
if (linearGradient != null) {
paint.setShader(linearGradient);
paint.setStyle(Paint.Style.FILL);
return true;
}
} else if (fill instanceof SvgRadialGradient) {
// 径向渐变
SvgRadialGradient gradient = (SvgRadialGradient) fill;
RadialGradient radialGradient = createRadialGradient(gradient, elementId);
if (radialGradient != null) {
paint.setShader(radialGradient);
paint.setStyle(Paint.Style.FILL);
return true;
}
}
return false;
}
/**
* 应用描边样式
*/
private void applyStrokeStyle(Paint paint, SvgElement element) {
if (element.getStroke() instanceof Colour) {
Colour strokeColour = (Colour) element.getStroke();
paint.setColor(strokeColour.getValue());
paint.setStyle(Paint.Style.STROKE);
if (element.getStrokeWidth() != null) {
paint.setStrokeWidth(element.getStrokeWidth());
} else {
paint.setStrokeWidth(1f);
}
}
}
/**
* 创建线性渐变
*/
private LinearGradient createLinearGradient(SvgLinearGradient gradient, String elementId) {
String cacheKey = "linear_" + elementId;
if (gradientCache.containsKey(cacheKey)) {
Shader shader = gradientCache.get(cacheKey);
if (shader instanceof LinearGradient) {
return (LinearGradient) shader;
}
}
try {
float x1 = gradient.getX1() != null ? gradient.getX1() : 0f;
float y1 = gradient.getY1() != null ? gradient.getY1() : 0f;
float x2 = gradient.getX2() != null ? gradient.getX2() : 1f;
float y2 = gradient.getY2() != null ? gradient.getY2() : 0f;
int stopCount = gradient.getStopCount();
if (stopCount == 0) return null;
int[] colors = new int[stopCount];
float[] positions = new float[stopCount];
for (int i = 0; i < stopCount; i++) {
SVG.SvgStop stop = gradient.getStop(i);
if (stop.colour instanceof Colour) {
colors[i] = ((Colour) stop.colour).getValue();
} else {
colors[i] = Color.BLACK;
}
positions[i] = stop.offset;
}
LinearGradient linearGradient = new LinearGradient(
x1, y1, x2, y2, colors, positions, Shader.TileMode.CLAMP
);
gradientCache.put(cacheKey, linearGradient);
return linearGradient;
} catch (Exception e) {
Log.e(TAG, "Error creating linear gradient", e);
return null;
}
}
/**
* 创建径向渐变
*/
private RadialGradient createRadialGradient(SvgRadialGradient gradient, String elementId) {
String cacheKey = "radial_" + elementId;
if (gradientCache.containsKey(cacheKey)) {
Shader shader = gradientCache.get(cacheKey);
if (shader instanceof RadialGradient) {
return (RadialGradient) shader;
}
}
try {
float centerX = gradient.getCx() != null ? gradient.getCx() : 0.5f;
float centerY = gradient.getCy() != null ? gradient.getCy() : 0.5f;
float radius = gradient.getR() != null ? gradient.getR() : 0.5f;
int stopCount = gradient.getStopCount();
if (stopCount == 0) return null;
int[] colors = new int[stopCount];
float[] positions = new float[stopCount];
for (int i = 0; i < stopCount; i++) {
SVG.SvgStop stop = gradient.getStop(i);
if (stop.colour instanceof Colour) {
colors[i] = ((Colour) stop.colour).getValue();
} else {
colors[i] = Color.BLACK;
}
positions[i] = stop.offset;
}
RadialGradient radialGradient = new RadialGradient(
centerX, centerY, radius, colors, positions, Shader.TileMode.CLAMP
);
gradientCache.put(cacheKey, radialGradient);
return radialGradient;
} catch (Exception e) {
Log.e(TAG, "Error creating radial gradient", e);
return null;
}
}
/**
* 处理点击事件
*/
private void handleTap(float x, float y) {
String tappedElementId = findElementAt(x, y);
if (tappedElementId != null) {
selectedElementId = tappedElementId;
SvgElement element = elementMap.get(tappedElementId);
if (elementClickListener != null) {
elementClickListener.onSvgElementClick(tappedElementId, element);
}
invalidate();
}
}
/**
* 查找指定坐标下的元素
*/
private String findElementAt(float x, float y) {
// 将屏幕坐标转换为SVG坐标
float[] point = {x, y};
Matrix inverseMatrix = new Matrix();
if (renderMatrix.invert(inverseMatrix)) {
inverseMatrix.mapPoints(point);
}
int screenX = (int) point[0];
int screenY = (int) point[1];
for (Map.Entry<String, Region> entry : hitRegionMap.entrySet()) {
if (entry.getValue().contains(screenX, screenY)) {
return entry.getKey();
}
}
return null;
}
/**
* 绘制选中状态高亮
*/
private void drawSelectionHighlight(Canvas canvas, String elementId) {
SvgElement element = elementMap.get(elementId);
if (element instanceof Path) {
Path path = (Path) element;
canvas.drawPath(path.getPath(), selectionPaint);
}
}
/**
* 绘制点击区域(调试用)
*/
private void drawHitRegions(Canvas canvas) {
Paint debugPaint = new Paint();
debugPaint.setColor(Color.argb(100, 255, 0, 0));
debugPaint.setStyle(Paint.Style.FILL);
for (Region region : hitRegionMap.values()) {
Rect bounds = region.getBounds();
canvas.drawRect(bounds.left, bounds.top, bounds.right, bounds.bottom, debugPaint);
}
}
// 公共API方法
public void setOnSvgElementClickListener(OnSvgElementClickListener listener) {
this.elementClickListener = listener;
}
public void setSelectedElement(String elementId) {
this.selectedElementId = elementId;
invalidate();
}
public void clearSelection() {
this.selectedElementId = null;
invalidate();
}
public void changeElementColor(String elementId, int color) {
SvgElement element = elementMap.get(elementId);
if (element != null) {
// 创建新的纯色填充
element.setFill(new Colour(color));
// 清除Paint缓存,强制重新创建
paintCache.remove(elementId);
invalidate();
}
}
public void changeElementGradient(String elementId, int[] colors, float[] positions) {
SvgElement element = elementMap.get(elementId);
if (element != null && colors != null && positions != null && colors.length == positions.length) {
// 创建新的线性渐变
SvgLinearGradient gradient = new SvgLinearGradient();
gradient.setGradientUnits(SvgLinearGradient.GradientUnits.OBJECT_BOUNDING_BOX);
gradient.setX1(0f);
gradient.setY1(0f);
gradient.setX2(1f);
gradient.setY2(0f);
// 添加颜色停止点
for (int i = 0; i < colors.length; i++) {
SVG.SvgStop stop = new SVG.SvgStop(positions[i], new Colour(colors[i]));
// 这里需要调用gradient的addStop方法,但AndroidSVG API可能不支持直接修改
}
element.setFill(gradient);
// 清除缓存
paintCache.remove(elementId);
gradientCache.entrySet().removeIf(entry -> entry.getKey().contains(elementId));
invalidate();
}
}
public void resetElementStyle(String elementId) {
SvgElement element = elementMap.get(elementId);
Map<String, Object> originalStyle = (Map<String, Object>) originalStyles.get(elementId);
if (element != null && originalStyle != null) {
// 恢复原始样式
if (originalStyle.containsKey("fill")) {
element.setFill((SvgPaint) originalStyle.get("fill"));
}
if (originalStyle.containsKey("stroke")) {
element.setStroke((SvgPaint) originalStyle.get("stroke"));
}
// 清除缓存
paintCache.remove(elementId);
gradientCache.entrySet().removeIf(entry -> entry.getKey().contains(elementId));
invalidate();
}
}
public void resetAllStyles() {
for (String elementId : elementMap.keySet()) {
resetElementStyle(elementId);
}
}
public void setShowHitRegions(boolean show) {
this.showHitRegions = show;
invalidate();
}
public Set<String> getElementIds() {
return elementMap.keySet();
}
public SvgElement getElement(String elementId) {
return elementMap.get(elementId);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width, height;
if (svg != null) {
float svgWidth = svg.getDocumentWidth();
float svgHeight = svg.getDocumentHeight();
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = (int) Math.ceil(svgWidth);
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize);
}
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = (int) Math.ceil(svgHeight);
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
}
} else {
width = widthSize;
height = heightSize;
}
setMeasuredDimension(width, height);
}
}
2.使用
public class MainActivity extends AppCompatActivity {
private AdvancedSvgView svgView;
private TextView infoText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
svgView = findViewById(R.id.svgView);
infoText = findViewById(R.id.infoText);
// 加载SVG资源
svgView.loadSvgFromResource(R.raw.complex_gradient_svg);
// 设置点击监听器
svgView.setOnSvgElementClickListener(new AdvancedSvgView.OnSvgElementClickListener() {
@Override
public void onSvgElementClick(String elementId, SvgElement element) {
infoText.setText("点击了: " + elementId);
// 随机改变颜色
Random random = new Random();
int randomColor = Color.rgb(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256)
);
svgView.changeElementColor(elementId, randomColor);
}
});
// 设置控制按钮
setupControlButtons();
}
private void setupControlButtons() {
Button resetButton = findViewById(R.id.resetButton);
Button gradientButton = findViewById(R.id.gradientButton);
Button debugButton = findViewById(R.id.debugButton);
resetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
svgView.resetAllStyles();
infoText.setText("已重置所有样式");
}
});
gradientButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 为第一个元素应用渐变色
Set<String> elementIds = svgView.getElementIds();
if (!elementIds.isEmpty()) {
String firstElement = elementIds.iterator().next();
int[] colors = {Color.RED, Color.YELLOW, Color.GREEN};
float[] positions = {0f, 0.5f, 1f};
svgView.changeElementGradient(firstElement, colors, positions);
infoText.setText("为 " + firstElement + " 应用渐变");
}
}
});
debugButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean showDebug = !svgView.isShowHitRegions();
svgView.setShowHitRegions(showDebug);
infoText.setText(showDebug ? "显示点击区域" : "隐藏点击区域");
}
});
}
}
3.布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/infoText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="点击SVG元素测试交互"
android:textSize="16sp"
android:padding="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="@+id/resetButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="重置"
android:layout_margin="4dp" />
<Button
android:id="@+id/gradientButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="应用渐变"
android:layout_margin="4dp" />
<Button
android:id="@+id/debugButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="调试模式"
android:layout_margin="4dp" />
</LinearLayout>
<com.example.AdvancedSvgView
android:id="@+id/svgView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#f0f0f0" />
</LinearLayout>
4总结
核心特性
1. 渐变色支持
· 线性渐变:完整支持 SVG 线性渐变
· 径向渐变:完整支持 SVG 径向渐变
· 渐变缓存:优化性能,避免重复创建
2. 精确点击检测
· 区域映射:为每个路径创建精确的点击区域
· 坐标转换:正确处理屏幕坐标到SVG坐标的转换
· 层级支持:支持复杂的分组结构
3. 动态样式修改
· 颜色修改:实时修改元素颜色
· 渐变修改:动态应用新的渐变色
· 样式重置:恢复原始样式
4. 性能优化
· 缓存机制:Paint和Shader对象缓存
· 按需重绘:只重绘发生变化的元素
· 内存管理:合理管理Region和Path对象
使用说明
1. 添加依赖:确保在build.gradle中添加AndroidSVG依赖
2. 放置资源:将SVG文件放在res/raw目录或assets目录
3. 交互处理:通过点击监听器处理用户交互
4. 样式控制:使用提供的API方法动态修改样式
这个实现能够处理包含复杂渐变色效果的SVG资源,并提供完整的交互支持,适用于地图、图表、图标等需要动态样式修改的场景。