在掘金上看到一篇实现汉字笔顺动画的文章很感兴趣,其代码采用kotlin实现,阅读不便,把相关实现类提出来翻译成了Java,有兴趣的可以看下。
先上原文链接:
Android修炼系列(41),简单实现个汉字笔顺动画https://juejin.cn/post/7103192601515425823
改写后,相关实现类只保留一个Activity和一个自定义View,方便阅读。直接贴源码和效果:
1.主Activity
package com.example.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class StrokeOrderActivityNew extends Activity {
String svgSix = null;
String svgOne = null;
StrokeOrderView strokeOrderView1;
StrokeOrderView strokeOrderView2;
Button btnSix;
Button btnOne;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stroke_order_layout);
strokeOrderView1 = (StrokeOrderView)findViewById(R.id.stroke_order_view1);
strokeOrderView2 = (StrokeOrderView)findViewById(R.id.stroke_order_view2);
btnSix = (Button)findViewById(R.id.btn_load_svg_six);
btnSix.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String name = "六.json"; // 需要将 svg.json 放在 assets 或特定路径下
svgSix = loadSvgJson(name);
strokeOrderView1.setStrokesBySvg(svgSix);
}
});
btnOne = (Button)findViewById(R.id.btn_load_svg_one);
btnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String name = "一.json";
svgOne = loadSvgJson(name);
strokeOrderView2.setStrokesBySvg(svgOne);
}
});
}
private String loadSvgJson(String file) {
BufferedReader reader = null;
InputStreamReader inputStreamReader = null;
try {
InputStream inputStream = getAssets().open(file);
inputStreamReader = new InputStreamReader(inputStream);
reader = new BufferedReader(inputStreamReader);
String line;
StringBuilder entity = new StringBuilder();
while ((line = reader.readLine()) != null) {
entity.append(line);
}
return entity.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
inputStreamReader.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
2.自定义View,负责实现动画绘制效果
package com.example.myapplication;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.*;
import android.graphics.PathMeasure;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.core.graphics.PathParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class StrokeOrderView extends View {
static float SVG_STROKE_WIDTH = 1024F;
static float SVG_STROKE_HEIGHT = 1024F;
private ArrayList strokePaths = new ArrayList();
private ArrayList medians = new ArrayList();
private Paint strokePaint = new Paint();
private Paint medianPaint = new Paint();
private ArrayList medianMeasures = new ArrayList();
private Path tempPath = new Path();
private float progress = 0F;
private int currIndex = 0;
private ArrayList points = new ArrayList();
Bitmap srcBmp = null;
Canvas srcCanvas = null;
Paint srcPaint = new Paint();
Bitmap dstBmp = null;
Canvas dstCanvas = null;
Paint dstPaint = new Paint();
PorterDuffXfermode clearMode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
PorterDuffXfermode srcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
PorterDuffXfermode porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
public StrokeOrderView(Context context) {
super(context);
}
public StrokeOrderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
strokePaint.setAntiAlias(true);
strokePaint.setStyle(Paint.Style.FILL);
strokePaint.setColor(Color.RED);
medianPaint.setAntiAlias(true);
medianPaint.setStyle(Paint.Style.FILL);
medianPaint.setColor(Color.BLACK);
srcPaint.setAntiAlias(true);
srcPaint.setStrokeWidth(100f);
srcPaint.setStyle(Paint.Style.STROKE);
srcPaint.setColor(Color.BLACK);
dstPaint.setAntiAlias(true);
dstPaint.setStyle(Paint.Style.FILL);
dstPaint.setColor(Color.BLACK);
}
public StrokeOrderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public StrokeOrderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
void setStrokesBySvg(String svgJson){
strokePaths.clear();
medians.clear();
medianMeasures.clear();
points.clear();
ArrayList strokes = new ArrayList();
parseSvgJson(svgJson, strokes, medians, points);
for(int i=0; i=0; i--){
if(i <= currIndex && currIndex < strokePaths.size()){
dstCanvas.drawPath(strokePaths.get(i), dstPaint);
}
}
dstCanvas.restoreToCount(c1);
dstPaint.setXfermode(null);
canvas.drawBitmap(dstBmp, 0F, 0F, medianPaint);
// 在两者相交的地方绘制源图像
medianPaint.setXfermode(porterDuffXfermode);
// src bitmap
if (srcBmp == null) {
srcBmp = Bitmap.createBitmap((int)w, (int)h, Bitmap.Config.ARGB_8888);
srcCanvas = new Canvas(srcBmp);
}
srcPaint.setXfermode(srcMode);// 只保留 srcBmp 的 alpha 和 color ,所以绘制出来只有源图
int c2 = srcCanvas.save();
srcCanvas.scale(1F, -1F);
srcCanvas.translate(0F, -SVG_STROKE_HEIGHT * 7 / 8);
srcCanvas.scale(xTmp, yTmp, 0F, SVG_STROKE_HEIGHT * 7 / 8);
if (!medianMeasures.isEmpty()) { // 绘制当前进度下的笔画
// 起笔落笔 都添加一个圆,防止不完整
drawBackbonePointCircle(currIndex * 2, 20F);
if (progress > 0.99) {
drawBackbonePointCircle(currIndex * 2 + 1, 30F);
}
tempPath.reset();
PathMeasure m = medianMeasures.get(currIndex);
m.getSegment(0F, m.getLength() * progress, tempPath, true);
srcCanvas.drawPath(tempPath, srcPaint);
}
srcCanvas.restoreToCount(c2);
srcPaint.setXfermode(null);
canvas.drawBitmap(srcBmp, 0F, 0F, medianPaint);
medianPaint.setXfermode(null);
canvas.restoreToCount(layer);
}
void parseSvgJson(String json, ArrayList list, ArrayList paths, ArrayList points){
try {
JSONObject obj = new JSONObject(json);
JSONArray array = obj.getJSONArray("strokes");
for(int i=0; i animators = new ArrayList();
for(int i=0; i
3.补全布局文件:
4.注意Assert下放置汉字的json文件,json文件不方便贴,大家可以从github上获取。
hanzi-writer-datahttps://github.com/chanind/hanzi-writer-data