首页 > 分享 > Android简单实现汉字笔顺动画——Java版

Android简单实现汉字笔顺动画——Java版

在掘金上看到一篇实现汉字笔顺动画的文章很感兴趣,其代码采用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

所属分类:花卉
上一篇: 小熊汉字笔顺学习软件,查询汉字笔
下一篇: 毛字笔顺笔画顺序