在掘金上看到一篇实现汉字笔顺动画的文章很感兴趣,其代码采用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";
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<Path> strokePaths = new ArrayList<Path>();
private ArrayList<Path> medians = new ArrayList<Path>();
private Paint strokePaint = new Paint();
private Paint medianPaint = new Paint();
private ArrayList<PathMeasure> medianMeasures = new ArrayList<PathMeasure>();
private Path tempPath = new Path();
private float progress = 0F;
private int currIndex = 0;
private ArrayList<Point> points = new ArrayList<Point>();
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<String> strokes = new ArrayList<String>();
parseSvgJson(svgJson, strokes, medians, points);
for(int i=0; i<strokes.size(); i++){
strokePaths.add(PathParser.createPathFromPathData(strokes.get(i)));
}
for(int i=0; i<medians.size(); i++){
medianMeasures.add(new PathMeasure(medians.get(i), false));
}
startAnimation();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (0 == currIndex) {
dstPaint.setXfermode(clearMode);
if(dstCanvas != null){
dstCanvas.drawPaint(dstPaint);
}
srcPaint.setXfermode(clearMode);
if(srcCanvas != null){
srcCanvas.drawPaint(srcPaint);
}
}
float xTmp = getMeasuredWidth() / SVG_STROKE_WIDTH;
float yTmp = getMeasuredHeight() / SVG_STROKE_HEIGHT;
int restore = canvas.save();
canvas.scale(1F, -1F);
canvas.translate(0F, -SVG_STROKE_HEIGHT * 7 / 8);
canvas.scale(xTmp, yTmp, 0F, SVG_STROKE_HEIGHT * 7 / 8);
for(int i=0; i<strokePaths.size(); i++){
canvas.drawPath(strokePaths.get(i), strokePaint);
}
canvas.restoreToCount(restore);
float w = getMeasuredWidth();
float h = getMeasuredHeight();
int layer = canvas.saveLayer(0F, 0F, w, h, null);
if (dstBmp == null) {
dstBmp = Bitmap.createBitmap((int)w, (int)h, Bitmap.Config.ARGB_8888);
dstCanvas = new Canvas(dstBmp);
}
dstPaint.setXfermode(srcMode);
int c1 = dstCanvas.save();
dstCanvas.scale(1F, -1F);
dstCanvas.translate(0F, -SVG_STROKE_HEIGHT * 7 / 8);
dstCanvas.scale(xTmp, yTmp, 0F, SVG_STROKE_HEIGHT * 7 / 8);
for(int i = strokePaths.size() - 1; 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);
if (srcBmp == null) {
srcBmp = Bitmap.createBitmap((int)w, (int)h, Bitmap.Config.ARGB_8888);
srcCanvas = new Canvas(srcBmp);
}
srcPaint.setXfermode(srcMode);
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<String> list, ArrayList<Path> paths, ArrayList<Point> points){
try {
JSONObject obj = new JSONObject(json);
JSONArray array = obj.getJSONArray("strokes");
for(int i=0; i<array.length(); i++){
list.add(array.getString(i));
}
JSONArray array2 = obj.getJSONArray("medians");
for(int i=0; i<array2.length(); i++){
JSONArray array3 = array2.getJSONArray(i);
Path path = new Path();
for(int j=0; j<array3.length(); j++){
JSONArray array4 = array3.getJSONArray(j);
int x = array4.getInt(0);
int y = array4.getInt(1);
if(0 == j){
path.moveTo(x,y);
points.add(new Point(x,y));
}else{
path.lineTo(x,y);
}
if (array3.length() - 1 == j) {
points.add(new Point(x, y));
}
}
paths.add(path);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void drawBackbonePointCircle(int index, float radius) {
srcPaint.setStyle(Paint.Style.FILL);
srcCanvas.drawCircle((float)points.get(index).x, (float)points.get(index).y, radius, srcPaint);
srcPaint.setStyle(Paint.Style.STROKE);
}
private AnimatorSet createAnimation(){
AnimatorSet set = new AnimatorSet();
ArrayList<Animator> animators = new ArrayList<Animator>();
for(int i=0; i<medians.size(); i++){
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
final int index = i;
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
currIndex = index;
progress = (float)valueAnimator.getAnimatedValue();
if(progress <= 1){
sleepAnimation();
postInvalidate();
}
}
});
animator.setDuration(1000);
animators.add(animator);
}
set.playSequentially(animators);
return set;
}
private void sleepAnimation() {
try {
Thread.sleep(15);
} catch (Exception e) {
e.printStackTrace();
}
}
private void startAnimation(){
AnimatorSet animator = createAnimation();
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
progress = 0F;
currIndex = 0;
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
progress = 0F;
currIndex = 0;
}
});
animator.start();
}
}
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="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/btn_load_svg_six"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="六"
android:textAllCaps="false"
/>
<Button
android:id="@+id/btn_load_svg_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="一"
android:textAllCaps="false"
/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal">
<com.example.myapplication.StrokeOrderView
android:id="@+id/stroke_order_view1"
android:layout_width="120dp"
android:layout_height="120dp"
android:background="#aaa"/>
<com.example.myapplication.StrokeOrderView
android:id="@+id/stroke_order_view2"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginStart="24dp"
android:background="#aaa"/>
</LinearLayout>
</LinearLayout>
4.注意Assert下放置汉字的json文件,json文件不方便贴,大家可以从github上获取。
hanzi-writer-datahttps://github.com/chanind/hanzi-writer-data
相关知识
「花」字笔顺详解,动画演示,字帖下载
《花》字笔画、笔顺、笔划
使用java编写一只玫瑰花
花笔顺
Android APP打开另一个APP完整逻辑实现
基于Android平台智能花卉养护系统.doc
java/jsp/ssm鲜花销售管理系统【2024年毕设】
基于JAVA的鲜花销售系统(源码+开题)
Java花卉销售与管理系统(开题+源码)
在线识别汉字
网址: Android简单实现汉字笔顺动画——Java版 https://m.huajiangbk.com/newsview564976.html
上一篇: 小熊汉字笔顺学习软件,查询汉字笔 |
下一篇: 毛字笔顺笔画顺序 |