OpenAtom OpenHarmony(以下简称“OpenHarmony”)提供了Image组件支持GIF动图的播放,但是缺乏扩展能力,不支持播放控制等。今天介绍一款三方库——ohos-gif-drawable三方组件,带大家一起玩转GIF的数据渲染,搞定GIF动图的各种需求。

效果演示

本文将从5个小节来带领大家使用ohos-gif-drawable这一款三方库,其中1、2、3这3个小节,主要介绍了ohos-gif-drawable的核心能力、GIF软解码和GIF绘制。4和5小节主要是扩展讨论,如何添加滤镜效果和软解码遇到的耗时问题。

1.GIF的文件格式理论基础

工欲善其事必先利其器。首先我们需要为自己打下理论基础。了解GIF的数据格式,为后续解码GIF提供理论支持。

通过学习GIF的文件格式,我们对于GIF的组成格式有了一定的了解,并且有助于理解后面GIF的解码。

在开始介绍之前,我想让大家了解一下整体的结构思路如下图:

其中gifuct-js三方库主要完成了解码的工作。

ohos-gif-drawable三方库则是在gifuct-js的三方库之上,进行了封装。并结合了OpenHarmony的Canvas绘制能力,达到了播放和控制GIF的能力。

2.GIF软解码:gifuct-js三方库介绍

GIF解码我们使用了gifuct-js这个库,它是一个纯JavaScript的GIF解码库。首先我们需要了解基础用法。

2.1 参考样例将一个文件ArrayBuffer转换为GIF解码后的帧数据数组。

//javascript
var gif = parseGIF(arraybuffer)
var frames = decompressFrames(gif, true)

  

2.2 由于OpenHarmony的Image生成PixelMap需要的数据是BGRA数据,而2.1生成的frames所有数组中的patch字段则是RGBA数据,所以我们需要使用

//javascript
var gif = parseGIF(arraybuffer)
var frames = decompressFrames(gif, false)

  

然后将frame目前还未生成的patch字段数据,通过generatePatch 函数,将RGBA的数据更换为BGRA即可,如下代码所示:

//javascript
const generatePatch = image => {
const totalPixels = image.pixels.length
const patchData = new Uint8ClampedArray(totalPixels * 4)
for (var i = 0; i < totalPixels; i++) {
const pos = i * 4
const colorIndex = image.pixels[i]
const color = image.colorTable[colorIndex] || [0, 0, 0]
patchData[pos] = color[2] // B
patchData[pos + 1] = color[1]// G
patchData[pos + 2] = color[0] // R
patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0//A
}
return patchData
}

  

generatePatch函数,在这里会根据颜色表colorTable和基于颜色表的图像数据pixels以及透明度transparentIndex生成BGRA格式的patchData,这个数据和Canvas中getImageData获取的ImageData数据是一致的,都是Uint8ClampedArray类型,可以直接使用putImageData让canvas绘制。

最后,生成的patchData赋值给Frame的patch字段。

这里我们并没有直接使用Canvas的putImageData直接绘制。为了提升扩展性,我们使用了Image的能力来生成PixelMap,这样处理为后续滤镜效果提供了可能,也方便后续绘制流程。

好了,到这里我们就基本上把gifuct-js库的基础使用简单介绍完了。

如何使用GIF:ohos-gif-drawable三方库的介绍。

我们先来看看整个ohos-gif-drawable组件的模型图,通过模型图,我们可以看到,用户只要关注GIFComponent组件,和GIFComponent.ControllerOptions配置参数以及控制参数autoPlay和resetGif即可,非常简单!

1. 支持的功能列表如下

● 支持播放GIF图片。

● 支持控制GIF播放/暂停。

● 支持重置GIF播放动画。

● 支持调节GIF播放速率。

● 支持监听GIF所有帧显示完成后的回调。

● 支持设置显示大小。

● 支持7种不同的展示类型。

● 支持设置显示区域背景颜色。

2. 如何使用ohos-gif-drawable

首先需要使用npm下载ohos-gif-drawable三方库

npm install @ohos/ohos-gif-drawable --save

  

接下来我们需要配置一个worker给gifuct-js解码使用。

配置worker,在应用工程的entry/src/main/ets/pages目录下新建workers文件夹,并且创建文件 gifParseWorker.ts ,文件内容如下:

import arkWorker from '@ohos.worker';
import { handler } from '@ohos/ohos-gif-drawable/src/main/ets/components/gif/worker/GifWorker'
// handler封装了子线程逻辑,但worker目前只能在entry中进行创建arkWorker.parentPort.onmessage = handler;

  

然后在entry目录的build-profile.json5文件中,添加如下内容:

"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/pages/workers/gifParseWorker.ts"
]
}
},

  

到这里我们worker就配置好了。

下面就到了正式使用环节,我们只要在UI界面需要的地方写上自定义控件GIFComponent,然后传入GIFComponent.ControllerOptions,gifAutoPlay,gifReset这三个参数就能控制gif动画。

import { GIFComponent, ResourceLoader } from '@ohos/ohos-gif-drawable'
// gif绘制组件用户属性设置
@State model:GIFComponent.ControllerOptions = new GIFComponent.ControllerOptions();
// 是否自动播放
@State gifAutoPlay:boolean = true;
// 重置GIF播放,每次取反都能生效
@State gifReset:boolean = true;
// 在ARKUI的其他容器组件中添加该组件
GIFComponent({model:$model, autoPlay:$gifAutoPlay, resetGif:this.gifReset})

  

举个简单的例子说明一下

// 创建worker
let worker = new ArkWorker.Worker('entry/ets/pages/workers/gifParseWorker.ts', {type: 'classic',name: 'loadUrlByWorker'})
// 关闭动画
this.gifAutoPlay = false;
// 销毁上一次资源
this.model.destroy();
// 新创建一个modelx,用于配置用户参数
let modelx = new GIFComponent.ControllerOptions()
modelx
// 配置回调动画结束监听,和耗时监听
.setLoopFinish((loopTime) => {
this.gifLoopCount++;
this.loopHint = '当前gif循环了' + this.gifLoopCount + '次,耗时=' + loopTime + 'ms'
})
// 设置组件大小
.setSize({ width: this.compWidth, height: this.compHeight })
// 设置图像和组件的适配类型
.setScaleType(this.scaleType)
// 设置播放速率
.setSpeedFactor(this.speedFactor)
// 设置背景
.setBackgroundColor(Color.Grey)
// 加载网络图片,getContext(this)中的this指向page页面或者组件都可以ResourceLoader.downloadDataWithContext(getContext(this), { url: 'https://pic.ibaotu.com/gif/18/17/16/51u888piCtqj.gif!fwpaa70/fw/700' }, (sucBuffer) => {
// 网络资源sucBuffer返回后处理
modelx.loadBuffer(sucBuffer, () => { console.log('网络加载解析成功回调绘制!')
// 开启自动播放
this.gifAutoPlay = true;
// 给组件数据赋新的用户配置参数,达到后续gif动画效果
this.model = modelx; }, worker)}, (err) => {
// 用户根据返回的错误信息,进行业务处理(展示一张失败占位图、再次加载一次、加载其他图片等)
})

  

这里ResourceLoader内置了加载网络资源GIF,本地工程资源GIF和本地路径资源GIF文件数据的能力。

如果你已经有了GIF文件的arraybuffer数据,也可以直接调用modelx.loadBuffer(buffer: ArrayBuffer, readyRender: (err?) => void, worker: any)进行GIF播放。

甚至你已经生成了GIF解析数据,比如调用了2.2中的解码代码,那么你也可以直接调用modelx.setFrames(images?: GIFFrame[])来进行gif播放。

1.控制GIF的播放与暂停:

this.gifAutoPlay = true 开启动画
this.gifAutoPlay = false 暂停动画

  

组件内部会监听该参数的变化,用户只要改变值即可达到控制效果

2. 重置GIF的播放

this.gifReset = !this.gifReset 每次变化都会重置gif播放。

  

由于重置不需要状态管理,所以组件内监听到数据变化就会重置gif播放

3. 设置GIF动画播放速度


let modelx = new GIFComponent.ControllerOptions()
modelx.setSpeedFactor(2)// 将速率提升到2倍

  

调用setSpeedFactor(speed: number)即可调整播放速度speed 为对比原始速率的乘积因子,比如设置0.5即为原始速率的0.5倍,设置为2即为原始速率的2倍。

4. 监听GIF动画播放回调(比如第一次动画结束)和获取动画实际播放总时长

let modelx = new GIFComponent.ControllerOptions()
modelx.setLoopFinish((loopTime?) => {
// loopTime为GIF动画一周期耗时,回调时间为GIF动画一周期结束时间节点
})

  

调用setLoopFinish(fn: (loopTime?) => void)可以通过回调得到GIF动画运行一周期耗时和一周期结束时间节点。

5. 显示GIF任意一帧

let modelx = new GIFComponent.ControllerOptions()
modelx.setSeekTo(5) // 直接展示该gif第5帧图像

  

调用setSeekTo(gifPosition: number)可以直接展示该gif的某一帧图像。

到这里ohos-gif-drawable三方库的主要能力都介绍完了,是不是很简单呢!

6. 适配组件的大小

let modelx = new GIFComponent.ControllerOptions()

  

modelx.setScaleType(ScaleType.FIT_CENTER) // 将图像缩放适配组件大小调用setScaleType(scaletype: ScaleType)可以将图像和组件大小进行适配。

目前支持的类型如下图所示:

GIFComponent.ScaleType

为什么要配置worker

在具体实践过程中我们会发现,当我们按下解码按钮的时候,主界面会有一点卡顿的情况。特别是大的GIF文件进行解码的时候效果更明显。这是因为我们在主线程中进行了CPU的密集型计算,这是一个耗时且占用CPU的操作。主线程中是不能执行耗时操作的。但是JavaScript只有一个线程啊?那么解码这一块操作该如何处理会比较好呢?带着疑惑,我去查阅了资料发现JavaScript虽然属于单线程环境。但是通过引入Worker的能力,引入子线程worker,可以实现JavaScript的“多线程”技术。

OpenHarmony如何在子线程中处理耗时任务

为了争取良好的用户体验,我们需要将耗时操作封装至子线程中。

这里简单描述一下worker的能力:

能够让主页面运行的JavaScript线程中加载运行另外单独的一个或者多个JavaScript线程,但是它的多线程编程能力区别于传统意义上的多线程编程。主线程和Worker线程之间,不会共享任何作用域和资源,他们的通信方式是基于事件监听机制的 message。

接下来我们参考OpenHarmony文档下的worker能力

1. OpenHarmony环境下Worker的API接口列表

2. Worker的使用简单案例

经过了解之后,我们可以把解码的耗时封装到worker中处理,避免主线程耗时操作占用CPU导致卡顿问题。提升用户体验。

这也是使用ohos-gif-drawable三方库需要配置worker的原因。

扩展部分

GIF的滤镜效果

1. 灰白滤镜

//javascript
// 重点代码更改
let avg = (color[0] + color[1] + color[2]) / 3
patchData[pos] = avg;
patchData[pos + 1] = avg;
patchData[pos + 2] = avg;
patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;

  

2. 反转滤镜

//javascript
// 重点代码更改
patchData[pos] = 255 - color[0];
patchData[pos + 1] = 255 - color[1];
patchData[pos + 2] = 255 - color[2];
patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;

  

3. 高级滤镜效果

假设我们这边已经拿到了patch: Uint8ClampedArray像素数据,这里我需要先将其变换为一张PixelMap数据,参考GIFComponent中patch数据转换为PixelMap的代码。

//typescript
import image from "@ohos.multimedia.image"
let colorBuffer = patch.buffer
let pixelmap = await image.createPixelMap(colorBuffer, {
'size': {
'height': frame.dims.height as number,
'width': frame.dims.width as number
}
})

  

4. 高斯模糊

然后对PixelMap像素数据进行高斯模糊, 调用 `blur(pixelmap,10,true, (outPixelMap)=>{ // 模糊后的pixelmap数据})`在回调中获取模糊后的pixelmap。以下是模糊处理的算法:

export async function blur(bitmap: any, radius: number, canReuseInBitmap: boolean, func: AsyncTransform<PixelMap>) {
if (radius < 1) {
func("error,radius must be greater than 1 ", null);
return;
} let imageInfo = await bitmap.getImageInfo();
let size = {
width: imageInfo.size.width,
height: imageInfo.size.height
} if (!size) {
func(new Error("fastBlur The image size does not exist."), null)
return;
} let w = size.width;
let h = size.height;
var pixEntry: Array<PixelEntry> = new Array()
var pix: Array<number> = new Array() let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let dataArray = new Uint8Array(bufferData); for (let index = 0; index < dataArray.length; index+=4) {
const r = dataArray[index];
const g = dataArray[index+1];
const b = dataArray[index+2];
const f = dataArray[index+3]; let entry = new PixelEntry();
entry.a = 0;
entry.b = b;
entry.g = g;
entry.r = r;
entry.f = f;
entry.pixel = ColorUtils.rgb(entry.r, entry.g, entry.b);
pixEntry.push(entry);
pix.push(ColorUtils.rgb(entry.r, entry.g, entry.b));
} let wm = w - 1;
let hm = h - 1;
let wh = w * h;
let div = radius + radius + 1; let r = CalculatePixelUtils.createIntArray(wh);
let g = CalculatePixelUtils.createIntArray(wh);
let b = CalculatePixelUtils.createIntArray(wh); let rsum, gsum, bsum, x, y, i, p, yp, yi, yw: number;
let vmin = CalculatePixelUtils.createIntArray(Math.max(w, h)); let divsum = (div + 1) >> 1;
divsum *= divsum;
let dv = CalculatePixelUtils.createIntArray(256 * divsum);
for (i = 0; i < 256 * divsum; i++) {
dv[i]=(i / divsum);
} yw = yi =0;
let stack = CalculatePixelUtils.createInt2DArray(div,3);
let stackpointer, stackstart, rbs, routsum, goutsum, boutsum, rinsum, ginsum, binsum: number;
let sir: Array<number>;
let r1 = radius +1;
for(y =0; y < h; y++){
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum =0;
for(i =-radius; i <= radius; i++){
p = pix[yi + Math.min(wm, Math.max(i,0))];
sir = stack[i + radius];
sir[0]=(p &0xff0000)>>16;
sir[1]=(p &0x00ff00)>>8;
sir[2]=(p &0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0]* rbs;
gsum += sir[1]* rbs;
bsum += sir[2]* rbs;
if(i >0){
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
}else{
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius; for(x =0; x < w; x++){ r[yi]= dv[rsum];
g[yi]= dv[gsum];
b[yi]= dv[bsum]; rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum; stackstart = stackpointer - radius + div;
sir = stack[stackstart % div]; routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2]; if(y ==0){
vmin[x]= Math.min(x + radius +1, wm);
}
p = pix[yw + vmin[x]]; sir[0]=(p &0xff0000)>>16;
sir[1]=(p &0x00ff00)>>8;
sir[2]=(p &0x0000ff); rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2]; rsum += rinsum;
gsum += ginsum;
bsum += binsum; stackpointer =(stackpointer +1)% div;
sir = stack[(stackpointer)% div]; routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2]; rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2]; yi++;
}
yw += w;
}
for(x =0; x < w; x++){
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum =0;
yp =-radius * w;
for(i =-radius; i <= radius; i++){
yi = Math.max(0, yp)+ x; sir = stack[i + radius]; sir[0]= r[yi];
sir[1]= g[yi];
sir[2]= b[yi]; rbs = r1 - Math.abs(i); rsum += r[yi]* rbs;
gsum += g[yi]* rbs;
bsum += b[yi]* rbs; if(i >0){
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
}else{
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
} if(i < hm){
yp += w;
}
}
yi = x;
stackpointer = radius;
for(y =0; y < h; y++){
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi]=(0xff000000& pix[Math.round(yi)])|(dv[Math.round(rsum)]<<16)|(dv[
Math.round(gsum)]<<8)| dv[Math.round(bsum)]; rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum; stackstart = stackpointer - radius + div;
sir = stack[stackstart % div]; routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2]; if(x ==0){
vmin[y]= Math.min(y + r1, hm)* w;
}
p = x + vmin[y]; sir[0]= r[p];
sir[1]= g[p];
sir[2]= b[p]; rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2]; rsum += rinsum;
gsum += ginsum;
bsum += binsum; stackpointer =(stackpointer +1)% div;
sir = stack[stackpointer]; routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2]; rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2]; yi += w;
}
} let bufferNewData =newArrayBuffer(bitmap.getPixelBytesNumber());
let dataNewArray =newUint8Array(bufferNewData);
let index =0; for(let i =0; i < dataNewArray.length; i +=4){
dataNewArray[i]= ColorUtils.red(pix[index]);
dataNewArray[i+1]= ColorUtils.green(pix[index]);
dataNewArray[i+2]= ColorUtils.blue(pix[index]);
dataNewArray[i+3]= pixEntry[index].f;
index++;
}
await bitmap.writeBufferToPixels(bufferNewData);
if(func){
func("success", bitmap);
}
}

  

如果需要高级滤镜效果可以参考ImageKnife组件的transform部分,这里仅仅展示模糊效果。

由于滤镜效果目前ohos-gif-drawable三方库并没有开发接口提供出来,所以开发者可以根据实际需求重写自定义组件GIFComponent.,只需要在生成PixelMap的代码片段中加入滤镜代码,即可利用滤镜效果开发更多精彩的应用。

参考资料

1.《GIF文件格式解析》

https://segmentfault.com/a/1190000022866045

2.GIF解码库gifuct-js

https://github.com/matt-way/gifuct-js

3.GIF解码库底层逻辑jsBinarySchemaParser

https://github.com/matt-way/jsBinarySchemaParser

4.高级滤镜算法借鉴

https://gitee.com/openharmony-tpc/ImageKnife/tree/master/imageknife/src/main/ets/components/imageknife/transform

5.OpenHarmony环境下Worker的API接口列表

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-worker.md

6.Worker的使用简单案例

https://gitee.com/wang_zhaoyong/js_worker_module/wikis/Worker%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8

7.Web Worker API参考

https://developer.mozilla.org/zh-CN/docs/Web/API/Worker

8.OpenHarmony的Canvas文档

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-components-canvas-canvas.md

9.OpenHarmony的CanvasRenderingContext2D对象文档

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-canvasrenderingcontext2d.md

OpenHarmony轻松玩转GIF数据渲染的更多相关文章

  1. 完整版的CAD技巧!3天轻松玩转CAD,零基础也能学会

    最近有很多小伙伴反应,CAD图纸学起来有点小困难,也许你还没能掌握技巧,CAD大神带你3天轻松玩转CAD,零基础也能快速学会. 一.看懂图纸是关键 CAD制图首先得让自己知道要绘制什么,如果心中对图纸 ...

  2. 2021 .NET Conf China 主题分享之-轻松玩转.NET大规模版本升级

    去年.NET Conf China 技术大会上,我给大家分享了主题<轻松玩转.NET大规模版本升级>,今天把具体分享的内容整理成一篇博客,供大家研究参考学习. 一.先说一下技术挑战和业务背 ...

  3. 【日常笔记】datatables表格数据渲染

    现在有很多表格渲染方式 这里只是记录怎么使用datatables渲染数据 使用datatables可以更方便的来渲染数据 [中文api]http://datatables.club/index.htm ...

  4. 基于AppCan MAS系统,如何轻松实现移动应用数据服务?

    完成一个移动应用开发,前端提供页面展示,当它要与一些业务系统进行交互,又该如何实现呢?2016AppCan移动开发者大会上,AppCan前端开发经理杨庆,分享了AppCan轻松实现移动应用数据服务的方 ...

  5. json数据渲染表单元素出现的问题

    解析页面表单元素 parseForm: function () { var data = {}; $(this).find('input').each(function () { switch ($( ...

  6. easyUI datagrid 多行多列数据渲染异常缓慢原因以及解决方法

    原因 最近,在优化之前公司帮联想(外包)做的一个老的后台管理系统,由于项目是基于easy UI框架,页面是后台用jsp实现的,再加上在公司推行前后端分离的实践,大部分项目都基于vue采用前后端分离去实 ...

  7. 解决vue数据渲染过程中的闪动问题

    关键代码 主要解决vue双大括号{{}}在数据渲染和加载过程中的闪动问题,而影响客服体验. html代码: <span class="tableTitle selftab" ...

  8. template.js 数据渲染引擎

    template.js 数据渲染引擎 template.js是一款JavaScript模板引擎,用来渲染页面的. 原理:提前将Html代码放进编写模板 <script id="tpl& ...

  9. wpgcms---单片页数据渲染

    单片页数据渲染,使用Twig的标签语法: <h1> {{ contentInfo.title }} </h1> {% autoescape false %} {{ conten ...

  10. vue2.* 目录结构分析 数据绑定 循环渲染数据 数据渲染02

    一.目录 结构分析 node_modules:项目依赖文件(也可以说是模块) src:开发时所用的资源 assets:静态资源文件 App.vue:根组件(最基础的公共页面) main.js:实例化v ...

随机推荐

  1. unrar命令

    解压提取RAR压缩文件 语法格式:unrar 参数 压缩包 常用参数 e 将文件解压缩到当前目录 o - 不要覆盖现有文件 l 显示文件列表 p 设置压缩包密码 p 将文件显示到标准输出 r 递归处理 ...

  2. Mysql 删除binlog日志方法

    方法1 RESET MASTER; 解释: 该方法可以删除列于索引文件中的所有二进制日志,把二进制日志索引文件重新设置为空,并创建一个以.000001为后缀新的二进制日志文件. 该语法一般只用在主从环 ...

  3. mongo重启、远程连接

    1.查看当前mongo启动进程 ps -ef | grep mongo 2.修改mongo启动远程连接配制文件 vi /etc/mongod.conf 将 bind_ip=127.0.0.1 这一行注 ...

  4. 【Azure Notification Hub】创建Notification Hub失败,提示 unrecognized arguments: --sku Free

    问题描述 用Azure CLI命令创建 Notification Hub,报错不识别的参数 --Free SKU 问题解答 经测试发现,在创建Notification Hub前,需要创建 Notifi ...

  5. 【Azure 存储服务】存储在Azure Storage Table中的数据,如何按照条件进行删除呢?

    问题描述 如何按条件删除 Storage Table 中的数据,如果Table中有大量的条记录需要删除,Java代码如何按条件删除 Table中的数据(Entity)? (通过Azure Storag ...

  6. 图查询语言 nGQL 简明教程 vol.01 快速入门

    本文旨在让新手快速了解 nGQL,掌握方向,之后可以脚踩在地上借助文档写出任何心中的 NebulaGraph 图查询. 视频 本教程的视频版在B站这里. 准备工作 在正式开始 nGQL 实操之前,记得 ...

  7. Java 从键盘读入学生成绩 找出最高分 并输出学生等级成绩 * 成绩>=最高分-10 等级为’A‘ * 成绩>=最高分-20 等级为’B‘ * 成绩>=最高分-30 等级为'C' * 其余 等级为’D‘

    1 /* 2 * 从键盘读入学生成绩 找出最高分 并输出学生等级成绩 3 * 成绩>=最高分-10 等级为'A' 4 * 成绩>=最高分-20 等级为'B' 5 * 成绩>=最高分- ...

  8. win10图标异常显示空白,WiFi图标消失等情况解决方案

    出现WiFi图标异常不显示,但是网络却正常,以下为解决方案: Win + R 快捷键调出运行框,输入%USERPROFILE%\AppData\Local,找到IconCache.db文件并删除,之后 ...

  9. 十步带你用IDEA创建一个WEB项目及部署(Tomcat)

    部署一个web项目首先需要安装Tomcat,还没安装的朋友们可以看一下我这个博客: https://www.cnblogs.com/deyo/p/17241878.html 第一步:打开Idea-新建 ...

  10. go程序在mac下的交叉编译

    主页 微信公众号:密码应用技术实战 博客园首页:https://www.cnblogs.com/informatics/ 背景 go语言的一大优势就是跨平台,go语言是编译型语言,与Java.C#等语 ...