由于项目中有实现毛笔的效果的要求,所以在网上找了一个Android的demo,然后将其改成了Java FX版本的,算法来自网上某个Android的demo(不好意思忘记了原来的url,如果原版作者有意见可以联系我)。

package brush;

import javafx.scene.paint.Color;

/**

* @des  笔的设置,但是有些笔的设置最好不要放在这里,不要笔的颜色和宽度

*/

public interface IPenConfig {

/**

* 清除画布

*/

int STROKE_TYPE_ERASER = 0;

/**

* 钢笔

*/

int STROKE_TYPE_PEN = 1;// 钢笔

/**

* 毛笔

*/

int STROKE_TYPE_BRUSH = 2;// 毛笔

//设置笔的宽度

int PEN_WIDTH=60;

//笔的颜色

Color PEN_CORLOUR= Color.valueOf("#FF4081");

//这个控制笔锋的控制值

float DIS_VEL_CAL_FACTOR = 0.02f;

//手指在移动的控制笔的变化率  这个值越大,线条的粗细越加明显

//float WIDTH_THRES_MAX = 0.6f;

float WIDTH_THRES_MAX = 10f;

//绘制计算的次数,数值越小计算的次数越多,需要折中

int STEPFACTOR = 10;

}

package brush;

/**

* @des 每个点的控制,关心三个因素:笔的宽度,坐标,透明数值

*/

public class ControllerPoint {

public float x;

public float y;

public float width;

public int alpha = 255;

public ControllerPoint() {

}

public ControllerPoint(float x, float y) {

this.x = x;

this.y = y;

}

public void set(float x, float y, float w) {

this.x = x;

this.y = y;

this.width = w;

}

public void set(ControllerPoint point) {

this.x = point.x;

this.y = point.y;

this.width = point.width;

}

public String toString() {

String str = "X = " + x + "; Y = " + y + "; W = " + width;

return str;

}

}

package brush;

/**

* @des  对点的位置和宽度控制的bezier曲线,主要是两个点,都包含了宽度和点的坐标

*/

public class Bezier {

//控制点的,

private ControllerPoint mControl = new ControllerPoint();

//距离

private ControllerPoint mDestination = new ControllerPoint();

//下一个需要控制点

private ControllerPoint mNextControl = new ControllerPoint();

//资源的点

private ControllerPoint mSource = new ControllerPoint();

public Bezier() {

}

/**

* 初始化两个点,

* @param last 最后的点的信息

* @param cur 当前点的信息,当前点的信息,当前点的是根据事件获得,同时这个当前点的宽度是经过计算的得出的

*/

public void init(ControllerPoint last, ControllerPoint cur)

{

init(last.x, last.y, last.width, cur.x, cur.y, cur.width);

}

public void init(float lastx, float lasty, float lastWidth, float x, float y, float width)

{

//资源点设置,最后的点的为资源点

mSource.set(lastx, lasty, lastWidth);

float xmid = getMid(lastx, x);

float ymid = getMid(lasty, y);

float wmid = getMid(lastWidth, width);

//距离点为平均点

mDestination.set(xmid, ymid, wmid);

//控制点为当前的距离点

mControl.set(getMid(lastx,xmid),getMid(lasty,ymid),getMid(lastWidth,wmid));

//下个控制点为当前点

mNextControl.set(x, y, width);

}

public void addNode(ControllerPoint cur){

addNode(cur.x, cur.y, cur.width);

}

/**

* 替换就的点,原来的距离点变换为资源点,控制点变为原来的下一个控制点,距离点取原来控制点的和新的的一半

* 下个控制点为新的点

* @param x 新的点的坐标

* @param y 新的点的坐标

* @param width

*/

public void addNode(float x, float y, float width){

mSource.set(mDestination);

mControl.set(mNextControl);

mDestination.set(getMid(mNextControl.x, x), getMid(mNextControl.y, y), getMid(mNextControl.width, width));

mNextControl.set(x, y, width);

}

/**

* 结合手指抬起来的动作,告诉现在的曲线控制点也必须变化,其实在这里也不需要结合着up事件使用

* 因为在down的事件中,所有点都会被重置,然后设置这个没有多少意义,但是可以改变下个事件的朝向改变

* 先留着,因为后面如果需要控制整个颜色的改变的话,我的依靠这个方法,还有按压的时间的变化

*/

public void end() {

mSource.set(mDestination);

float x = getMid(mNextControl.x, mSource.x);

float y = getMid(mNextControl.y, mSource.y);

float w = getMid(mNextControl.width, mSource.width);

mControl.set(x, y, w);

mDestination.set(mNextControl);

}

/**

*

* @param t 控制点

* @return

*/

public ControllerPoint getPoint(double t){

float x = (float)getX(t);

float y = (float)getY(t);

float w = (float)getW(t);

ControllerPoint point = new ControllerPoint();

point.set(x,y,w);

return point;

}

/**

* 三阶曲线的控制点

* @param p0

* @param p1

* @param p2

* @param t

* @return

*/

private double getValue(double p0, double p1, double p2, double t){

double A = p2 - 2 * p1 + p0;

double B = 2 * (p1 - p0);

double C = p0;

return A * t * t + B * t + C;

}

private double getX(double t) {

return getValue(mSource.x, mControl.x, mDestination.x, t);

}

private double getY(double t) {

return getValue(mSource.y, mControl.y, mDestination.y, t);

}

private double getW(double t){

return getWidth(mSource.width, mDestination.width, t);

}

/**

*

* @param x1 一个点的x

* @param x2 一个点的x

* @return

*/

private float getMid(float x1, float x2) {

return (float)((x1 + x2) / 2.0);

}

private double getWidth(double w0, double w1, double t){

return w0 + (w1 - w0) * t;

}

}

package brush;

import java.util.ArrayList;

import javafx.scene.input.MouseEvent;

import javafx.scene.layout.Pane;

import javafx.scene.shape.Ellipse;

public  class BasePenExtend  {

public ArrayList mHWPointList = new ArrayList<>();

public ArrayList mPointList = new ArrayList();

public ControllerPoint mLastPoint = new ControllerPoint(0, 0);

//笔的宽度信息

public double mBaseWidth;

public double mLastVel;

public double mLastWidth;

public Bezier mBezier = new Bezier();

protected ControllerPoint mCurPoint;

public BasePenExtend() {

}

public void setPaint(double width) {

mBaseWidth = width;

}

public void draw(Pane pane) {

//点的集合少 不去绘制

if (mHWPointList == null || mHWPointList.size() < 1)

return;

//当控制点的集合很少的时候,需要画个小圆,但是需要算法

if (mHWPointList.size() < 2) {

ControllerPoint point = mHWPointList.get(0);

//由于此问题在算法上还没有实现,所以暂时不给他画圆圈

//canvas.drawCircle(point.x, point.y, point.width, mPaint);

} else {

mCurPoint = mHWPointList.get(0);

drawNeetToDo(pane);

}

}

public boolean onTouchEvent(int mouseType,MouseEvent event, Pane canvas) {

// event会被下一次事件重用,这里必须生成新的,否则会有问题

switch (mouseType) {

case 0:

onDown(event);

return true;

case 1:

onMove(event);

return true;

case 2:

onUp(event, canvas);

return true;

default:

break;

}

return false;

}

/**

* 按下的事件

*

* @param mElement

*/

public void onDown(MouseEvent mElement) {

mPointList.clear();

//如果在brush字体这里接受到down的事件,把下面的这个集合清空的话,那么绘制的内容会发生改变

//不清空的话,也不可能

mHWPointList.clear();

//记录down的控制点的信息

ControllerPoint curPoint = new ControllerPoint((float)mElement.getSceneX(), (float)mElement.getSceneY());

//如果用笔画的画我的屏幕,记录他宽度的和压力值的乘,但是哇,

/*if (mElement.tooltype == MotionEvent.TOOL_TYPE_STYLUS) {

mLastWidth = mElement.pressure * mBaseWidth;

} else {*/

//如果是手指画的,我们取他的0.8

mLastWidth = 0.8 * mBaseWidth;

/* }*/

//down下的点的宽度

curPoint.width = (float) mLastWidth;

mLastVel = 0;

mPointList.add(curPoint);

//记录当前的点

mLastPoint = curPoint;

}

/**

* 手指移动的事件

*

* @param mElement

*/

public void onMove(MouseEvent mElement) {

ControllerPoint curPoint = new ControllerPoint((float)mElement.getSceneX(), (float)mElement.getSceneY());

double deltaX = curPoint.x - mLastPoint.x;

double deltaY = curPoint.y - mLastPoint.y;

//deltaX和deltay平方和的二次方根 想象一个例子 1+1的平方根为1.4 (x²+y²)开根号

//同理,当滑动的越快的话,deltaX+deltaY的值越大,这个越大的话,curDis也越大

double curDis = Math.hypot(deltaX, deltaY);

//我们求出的这个值越小,画的点或者是绘制椭圆形越多,这个值越大的话,绘制的越少,笔就越细,宽度越小

double curVel = curDis * IPenConfig.DIS_VEL_CAL_FACTOR;

double curWidth;

//点的集合少,我们得必须改变宽度,每次点击的down的时候,这个事件

if (mPointList.size() < 2) {

/*if (mElement.tooltype == MotionEvent.TOOL_TYPE_STYLUS) {

curWidth = mElement.pressure * mBaseWidth;

} else {*/

curWidth = calcNewWidth(curVel, mLastVel, curDis, 1.5,

mLastWidth);

/*  }*/

curPoint.width = (float) curWidth;

mBezier.init(mLastPoint, curPoint);

} else {

mLastVel = curVel;

/*  if (mElement.tooltype == MotionEvent.TOOL_TYPE_STYLUS) {

curWidth = mElement.pressure * mBaseWidth;

} else {*/

//由于我们手机是触屏的手机,滑动的速度也不慢,所以,一般会走到这里来

//阐明一点,当滑动的速度很快的时候,这个值就越小,越慢就越大,依靠着mlastWidth不断的变换

curWidth = calcNewWidth(curVel, mLastVel, curDis, 1.5,

mLastWidth);

/*}*/

curPoint.width = (float) curWidth;

mBezier.addNode(curPoint);

}

//每次移动的话,这里赋值新的值

mLastWidth = curWidth;

mPointList.add(curPoint);

moveNeetToDo(curDis);

mLastPoint = curPoint;

}

/**

* 手指抬起来的事件

*

* @param mElement

* @param canvas

*/

public void onUp(MouseEvent mElement, Pane canvas) {

mCurPoint = new ControllerPoint((float)mElement.getSceneX(), (float)mElement.getSceneY());

double deltaX = mCurPoint.x - mLastPoint.x;

double deltaY = mCurPoint.y - mLastPoint.y;

double curDis = Math.hypot(deltaX, deltaY);

//如果用笔画的画我的屏幕,记录他宽度的和压力值的乘,但是哇,这个是不会变的

/* if (mElement.tooltype == MotionEvent.TOOL_TYPE_STYLUS) {

mCurPoint.width = (float) (mElement.pressure * mBaseWidth);

} else {*/

mCurPoint.width = 0;

/* }*/

mPointList.add(mCurPoint);

mBezier.addNode(mCurPoint);

int steps = 1 + (int) curDis / IPenConfig.STEPFACTOR;

double step = 1.0 / steps;

for (double t = 0; t < 1.0; t += step) {

ControllerPoint point = mBezier.getPoint(t);

mHWPointList.add(point);

}

//

mBezier.end();

for (double t = 0; t < 1.0; t += step) {

ControllerPoint point = mBezier.getPoint(t);

mHWPointList.add(point);

}

// 手指up 我画到纸上上

draw(canvas);

//每次抬起手来,就把集合清空,在水彩笔的那个地方,如果啊,我说如果不清空的话,每次抬起手来,

// 在onDown下去的话,最近画的线的透明度有改变,所以这里clear下线的集合

clear();

}

/**

* @param curVel

* @param lastVel

* @param curDis

* @param factor

* @param lastWidth

* @return

*/

public double calcNewWidth(double curVel, double lastVel, double curDis,

double factor, double lastWidth) {

double calVel = curVel * 0.6 + lastVel * (1 - 0.6);

//返回指定数字的自然对数

//手指滑动的越快,这个值越小,为负数

double vfac = Math.log(factor * 2.0f) * (-calVel);

//此方法返回值e,其中e是自然对数的基数。

//Math.exp(vfac) 变化范围为0 到1 当手指没有滑动的时候 这个值为1 当滑动很快的时候无线趋近于0

//在次说明下,当手指抬起来,这个值会变大,这也就说明,抬起手太慢的话,笔锋效果不太明显

//这就说明为什么笔锋的效果不太明显

double calWidth = mBaseWidth * Math.exp(vfac);

//滑动的速度越快的话,mMoveThres也越大

double mMoveThres = curDis * 0.01f;

//对之值最大的地方进行控制

if (mMoveThres > IPenConfig.WIDTH_THRES_MAX) {

mMoveThres = IPenConfig.WIDTH_THRES_MAX;

}

// TODO: 2018/2/24   以下的方法 可以删除掉  原因是抽取了一下 ,本来不应该在这里的出现的  不好意思

//        //滑动越慢的情况下,得到的calWidth 和上面的calwidth 相差的值不一样

//

//        //滑动的越快的话,第一个判断会走

//        if (Math.abs(calWidth - mBaseWidth) / mBaseWidth > mMoveThres) {

//            if (calWidth > mBaseWidth) {

//                calWidth = mBaseWidth * (1 + mMoveThres);

//            } else {

//                calWidth = mBaseWidth * (1 - mMoveThres);

//            }

//            //滑动的越慢的话,第二个判断会走  基本上在屏幕上手指基本上没有走动的时候 ,就会走这个方法

//        } else if (Math.abs(calWidth - lastWidth) / lastWidth > mMoveThres) {

//            if (calWidth > lastWidth) {

//                calWidth = lastWidth * (1 + mMoveThres);

//            } else {

//                calWidth = lastWidth * (1 - mMoveThres);

//            }

//        }

return calWidth;

}

/**

* event.getPressure(); //LCD可以感应出用户的手指压力,当然具体的级别由驱动和物理硬件决定的,我的手机上为1

*

* @param motionEvent

* @return

*/

/* public MotionElement createMotionElement(MotionEvent motionEvent) {

MotionElement motionElement = new MotionElement(motionEvent.getX(), motionEvent.getY(),

motionEvent.getPressure(), motionEvent.getToolType(0));

return motionElement;

}*/

public void clear() {

mPointList.clear();

mHWPointList.clear();

}

/**

* 当现在的点和触摸点的位置在一起的时候不用去绘制

* 但是这里也可以优化,当一直处于onDown事件的时候,其实这个方法一只在走

*

* @param canvas

* @param point

* @param paint

*/

// TODO: 2017/10/18  这里可以优化 当一直处于onDown事件的时候,其实这个方法一直在走,优化的点是,处于down事件,这里不需要走

protected void drawToPoint(Pane canvas, ControllerPoint point) {

if ((mCurPoint.x == point.x) && (mCurPoint.y == point.y)) {

return;

}

//水彩笔的效果和钢笔的不太一样,交给自己去实现

doNeetToDo(canvas, point);

}

/**

* 判断笔是否为空 节约性能,每次切换笔的时候就不用重复设置了

*

* @return

*/

/* public boolean isNull() {

return mPaint == null;

}*/

protected void drawNeetToDo(Pane pane) {

for (int i = 1; i < mHWPointList.size(); i++) {

ControllerPoint point = mHWPointList.get(i);

drawToPoint(pane, point);

mCurPoint = point;

}

}

protected void moveNeetToDo(double curDis) {

int steps = 1 + (int) curDis / IPenConfig.STEPFACTOR;

double step = 1.0 / steps;

for (double t = 0; t < 1.0; t += step) {

ControllerPoint point = mBezier.getPoint(t);

mHWPointList.add(point);

}

}

protected void doNeetToDo(Pane canvas, ControllerPoint point) {

drawLine(canvas, mCurPoint.x, mCurPoint.y, mCurPoint.width, point.x,

point.y, point.width);

}

/**

* 其实这里才是关键的地方,通过画布画椭圆,每一个点都是一个椭圆,这个椭圆的所有细节,逐渐构建出一个完美的笔尖

* 和笔锋的效果,我觉得在这里需要大量的测试,其实就对低端手机进行排查,看我们绘制的笔的宽度是多少,绘制多少个椭圆

* 然后在低端手机上不会那么卡,当然你哪一个N年前的手机给我,那也的卡,只不过需要适中的范围里面

*

* @param canvas

* @param x0

* @param y0

* @param w0

* @param x1

* @param y1

* @param w1

* @param paint

*/

private void drawLine(Pane canvas, double x0, double y0, double w0, double x1, double y1, double w1) {

//求两个数字的平方根 x的平方+y的平方在开方记得X的平方+y的平方=1,这就是一个园

double curDis = Math.hypot(x0 - x1, y0 - y1);

int steps = 1;

if (mBaseWidth < 6) {

steps = 1 + (int) (curDis / 2);

} else if (mBaseWidth > 60) {

steps = 1 + (int) (curDis / 4);

} else {

steps = 1 + (int) (curDis / 3);

}

double deltaX = (x1 - x0) / steps;

double deltaY = (y1 - y0) / steps;

double deltaW = (w1 - w0) / steps;

double x = x0;

double y = y0;

double w = w0;

for (int i = 0; i < steps; i++) {

//都是用于表示坐标系中的一块矩形区域,并可以对其做一些简单操作

//精度不一样。Rect是使用int类型作为数值,RectF是使用float类型作为数值。

//            Rect rect = new Rect();

//            RectF oval = new RectF();

Ellipse ellipse = new Ellipse();

float centerX=((float) (x - w / 4.0f)+(float) (x + w / 4.0f))/2;

float centerY=((float) (y - w / 2.0f)+(float) (y + w / 2.0f))/2;

ellipse.setCenterX(centerX);

ellipse.setCenterY(centerY);

ellipse.setRadiusX((Math.abs((float) (x - w / 4.0f)-(float) (x + w / 4.0f)))/2);

ellipse.setRadiusY(Math.abs(((float) (y - w / 2.0f)-(float) (y + w / 2.0f))/2));

/*  oval.set((float) (x - w / 4.0f), (float) (y - w / 2.0f), (float) (x + w / 4.0f), (float) (y + w / 2.0f));

// oval.set((float)(x+w/4.0f), (float)(y+w/4.0f), (float)(x-w/4.0f), (float)(y-w/4.0f));

*/            //最基本的实现,通过点控制线,绘制椭圆

canvas.getChildren().add(ellipse);

x += deltaX;

y += deltaY;

w += deltaW;

}

}

}

/**

*

*/

package brush;

import javafx.application.Application;

import javafx.scene.Scene;

import javafx.scene.layout.Pane;

import javafx.stage.Stage;

/**

* @author gj

*

* 2018年6月6日

*/

public class Test extends Application {

/* (non-Javadoc)

* @see javafx.application.Application#start(javafx.stage.Stage)

*/

@Override

public void start(Stage arg0) throws Exception {

// TODO Auto-generated method stub

Pane pane = new Pane();

pane.setPrefSize(800, 900);

BasePenExtend basePenExtend=new BasePenExtend();

basePenExtend.setPaint(50);

basePenExtend.draw(pane);

pane.setOnMousePressed(e->{

basePenExtend.onTouchEvent(0, e, pane);

});

pane.setOnMouseDragged(e->{

basePenExtend.onTouchEvent(1, e, pane);

basePenExtend.draw(pane);

});

pane.setOnMouseReleased(e->{

basePenExtend.onTouchEvent(2, e, pane);

});

arg0.setScene(new Scene(pane));

arg0.show();

}

public static void main(String[] args) {

launch(args);

}

}

java游戏主角用毛笔_Java FX版的毛笔效果相关推荐

  1. java游戏代码潜艇大战_java游戏之潜艇大战

    java游戏之潜艇大战 java 2020-6-15 下载地址 https://www.codedown123.com/25881.html java游戏之潜艇大战 资源下载此资源下载价格为2D币,请 ...

  2. 武林外史java游戏,武林外史手游官方版

    武林外史手游官方版是一款根据古龙经典武侠效果改编以<武林外史>为主要剧情的ARPG手游,在游戏中玩家将会成为一名初出茅庐的江湖侠客,并且跟随武功高强的主角沈浪展开一场场独特的武林冒险,而你 ...

  3. java游戏课程设计报告_java课程设计报告游戏_相关文章专题_写写帮文库

    时间:2019-05-14 00:00:44 作者:admin 课 程 设 计 课程名称 Java语言课程设计 题目名称 人事管理系统的设计与实现 学生学院 应用数学学院 专业班级 学 号 学生姓名 ...

  4. Java游戏用户登录注册_Java实现多用户注册登录的幸运抽奖

    本文实例为大家分享了Java实现简单幸运抽奖的具体代码,供大家参考,具体内容如下 代码模块: User类: package test1; public class User { private Str ...

  5. java游戏主角叶开,逸之老板的天机城与叶开大神女魃墓,决赛场上谁更抢眼?...

    精锐组:逸之老板的天机城与叶开大神女魃墓,决赛场上谁更抢眼? ①逸之老板150级无级别刀,出场效果到底是不是很一般? "吃货分队"逸之的天机城在精锐组也算是佼佼者,扛上150级无级 ...

  6. java游戏超级玛丽_超级玛丽_JAVA游戏免费版下载_7723手机游戏[www.7723.cn]

    三星 D608系列(240×320) D608 707SC 830W A717 A727 B109 B200 B250 B500 B5702C B600 B5712C C210 C3050C S350 ...

  7. java游戏主角叶开,《仙侠道》叶开深度解析

    <仙侠道>叶开深度解析 成也叶开,败也叶开. 高速的都想把叶开秒了,这样赢得几率大. 但是对手也会想法设法让你秒不了. 上叶开同等战力同样伙伴,低速度的占优势.低速阵印陷阱有两个回个,经脉 ...

  8. 来了超火爆的Java游戏羊了个羊_java开发游戏项目

    <羊了个羊>是一款网络上的卡通背景消除闯关游戏,游戏利用各种道具和提示来消除每一个关卡当中的障碍和陷阱. 游戏规则 羊了个羊在游戏下方共有7个槽位,玩家只要把3个相同方块点到槽位中就会消除 ...

  9. 【源码+图片素材+详细教程】Java游戏开发_Java开发经典游戏飞翔的小鸟_飞扬的小鸟_Java游戏项目Flappy Bird像素鸟游戏_Java课程设计项目

    课程目标: 1.通过本课程的学习巩固Java的相关基础知识,例如循环判断,数组和集合的使用,对象的继承,接口的实现,窗口的创建,事件监听,图形绘制. 2.完成小鸟的移动,管道自动生成.碰撞死亡,计分系 ...

  10. Java游戏开发框架LGame-0 2 8版发布(含JavaSE及Android版,已有文档)

    LGame是LoonFramework框架的一部分,也可简称做"LF"或"Loon". LGame框架的创立初衷在于,构建一个高效且完善的Java游戏开发体系, ...

最新文章

  1. 有赞MySQL自动化运维之路—ZanDB
  2. 华为鸿蒙搭载芯片,独立188天,荣耀50系列破冰!6nm芯片,不搭载鸿蒙
  3. 【29.70%】【codeforces 723D】Lakes in Berland
  4. sum 去重_Excel函数,用到什么学什么!多条件求和神器之SUMIFS和去重
  5. 本人使用abapgit遇到的一些错误
  6. java 语义_Java文件合并变得语义化
  7. 三极管的“非教科书式”解析,什么正偏、反偏都统统滚蛋!
  8. 请教各位 android activity之间切换的问题
  9. Java千百问_03基本语法(002)_java都有哪些关键字
  10. 传统IO与NIO的区别
  11. Impala和Inceptor的区别
  12. 【游戏策划】【碎碎念】关于横版跳跃类游戏的关卡设计
  13. PPT文件压缩方法有哪些?
  14. 强烈推荐几款IDEA插件,12款小白神器
  15. 二维码制作生成器有哪些?分享几个二维码制作生成器
  16. 因果故事:偷不走的命运!
  17. 曼谷旅游不可错过的游船体验:游览湄南河,赏两岸风光
  18. 吉大C语言程序设计作业,吉大19年9月《C语言程序设计》作业考核试题答案
  19. 王姨劝我学HarmonyOS鸿蒙2.0系列教程之六自定义View涂鸦项目实战!
  20. Matlab安装bav-io插件时报错:Either you are offline, a firewall is blocking EEGLAB from accessing itsplugin s

热门文章

  1. 麻将算法c语言,[转载]麻将胡牌的检测算法
  2. 想晋升Android架构师——学习这些核心技术够用吗?
  3. 计算股票收盘价的月日均值 matplotlib绘图
  4. Codeforces #319E: Ping-Pong 题解
  5. 【图解算法使用C++】1.2 生活中的算法
  6. E.03.17 Lou Ottens, Father of Countless ==Mixtapes==, Is Dead at 94
  7. 中标麒麟系统邮件客户端使用方法
  8. 用java判断闰年和平年
  9. 浩辰3D软件入门攻略:什么是有限元分析?
  10. 【eNSP 华为模拟器】三层交换技术及操作步骤【图文】