点击上方 "程序员小乐"关注, 星标或置顶一起成长

关注订阅号「程序员小乐」,收看更多精彩内容

每日英文

You can't change the past, but you can ruin the present by worrying about the future.

你改变不了昨天,但如果你过于忧虑明天,将会毁了今天。

每日掏心

不要抱怨生活对你不公平,因为生活根本就不知道你是谁。

来自:二娃_ | 责编:乐乐

链接:juejin.im/user/5bf67660e51d45218f3d0938

程序员小乐(ID:study_tech)第 1008 次推文

往日回顾: 看黄片,起诉网站,可尼玛太秀了

     

   正文   

自定义View:

1、花花绿绿的股票线是怎么画出来的?想怎么画就怎么画!

2、Android自定义View之区块选择器

/   直接上图   /

支持XML自定义属性:

  • rv_webRadius:雷达网的半径(该属性决定了View的宽高)

  • rv_webMaxProgress:各属性表示的最大进度

  • rv_webLineColor:雷达网的颜色

  • rv_webLineWidth:雷达网的线宽

  • rv_textArrayedColor:各属性文字的颜色

  • rv_textArrayedFontPath:各属性文字和中心处名字的字体路径

  • rv_areaColor:中心连接区域的颜色

  • rv_areaBorderColor:中心连接区域的边框颜色

  • rv_textCenteredName:中心处的名字

  • rv_textCenteredColor:中心文字的颜色

  • rv_textCenteredFontPath:中心数字文字的字体路径

  • rv_animateTime:动画执行时间

  • rv_animateMode:动画模式

    • TIME:时间一定,动画执行时间为rv_animateTime

    • SPEED:速度一定,动画执行速度为rv_webMaxProgress➗rv_animateTime

支持代码设置数据源:

  • setTextArray(textList: List<String>):设置各属性文字数组,元素个数不能小于3

  • setProgressList(progressList: List<Int>):设置各属性对应的进度数组,该数组元素默认都是0,元素个数必须与文字数组保持一致

  • setOldProgressList(oldProgressList: List<Int>):设置各属性执行动画前,对应的进度数组,该数组元素默认都是0,元素个数必须与文字数组保持一致

支持代码执行动画:

  • doInvalidate():各个属性的动画一起执行

  • doInvalidate(index: Int, block: ((Int) -> Unit)? = null):指定某属性执行动画,可传入参数接收动画结束的回调

/   起源   /

近来我司产品侧在重构一项业务,连带UI也有变动,其中就涉及到了雷达图,所以也就有了这次封装的RadarView,其主要特点是:

  1. 有丰富的自定义属性,可对雷达图外观进行设置

  2. 支持自由设置属性个数

  3. 支持两种动画模式(时间一定、速度一定)

  4. 支持指定某属性执行动画(从而满足UI稿的个性需求,见头图三)

头图三联是演示了该View的主要特点,然后结合局部UI稿,大家可以对比看下(还原度99%,✧(≖ ◡ ≖✿)嘿嘿嘿)。

/   思考分析   /

NOTE:

  1. 我们把六角形抽象成N角形,后文统一使用N角形表示

  2. 我们在绘制前会把坐标系原点移动到雷达图中心,后文统一使用原点表示

我们先来思考下关键技术点:

  • 绘制N角形雷达网

    • 在绘制完一条虚线后,紧接着绘制实线

    • 同样以雷达图中心为原点,将坐标系每向上移动雷达网半径/4后,并且顺时针旋转360/N/2度(为什么是这个值?大家可自行????下),此时的坐标系x轴刚好与对应的实线重合

    • 所以,直接从移动旋转后的新坐标系原点沿x轴绘制实线即可

    • 实线长度通过雷达网半径和相应角度的三角函数等可以算出来

    • 虚线可以给Paint设置DashPathEffect实现

    • 以雷达图中心为原点,将坐标系每逆时针旋转360/N度,从原点向上绘制长度为雷达网半径的虚线

    • 绘制虚线

    • 绘制实线

  • 绘制N个角的属性文字

    • 以雷达图中心为原点,以12点方向的角为第一个角,可以知道它的顶点坐标(0,-半径)

    • 通过圆上一点绕圆心(坐标原点)顺时针旋转α弧度得出另一点坐标的坐标公式,可以算出各角顶点的坐标(后面会推导该公式!)

    • 再结合文字与顶点的间距、Paint的setTextAlign()、以及微调绘制文字时的Y坐标就可以搞定啦

  • 绘制中心连接区域

    • 同样以12点方向属性(角)上的值的为第一个点,可以知道该点坐标为(0,-半径✖进度️)

    • 同样通过坐标公式可以算出各属性的进度值坐标

    • 使用Path连接各点构建出路径,然后绘制与描边就很好说啦

  • 绘制中心数字&名字

    • 就是很基本的绘制文字,但需要注意微调Y坐标,以提高与UI稿的还原度(细节!细节!细节!)

  • 添加动画效果

    • 动画的本质就是不断调整各属性值的坐标,然后重绘View

    • 实际代码我们使用属性动画ValueAnimator可以搞定,也很简单

整理下思路框架:

  1. 定义、初始化属性

    1. 自定义属性部分

    2. 计算属性部分

    3. 绘制依赖的属性部分

    4. 定义设置属性的API

  2. 绘制N角形雷达网

  3. 绘制N个角的属性文字

  4. 绘制中心连接区域

  5. 绘制中心数字&名字

  6. 添加动画效果

技术点、思路理好了,按道理就要着手开始代码了,不过我们先上道数学题热热身。

/   热身数学题   /

请听题:根据9年义务教育所学,推导出通过圆上一点绕圆心(坐标原点)顺时针旋转α弧度得出另一点坐标的坐标公式

设圆半径为r,圆上有一点坐标A(,),绕圆心顺时针旋转α弧度后得到坐标B(,),有公式如下:

=-

=+

推导过程如下:

数学上规定逆时针旋转为正方向

当一条射线从x轴的正方向(向右)开始逆时针方向旋转之后到了一个新位置(按顺序分别到达第一、二、三、四象限)得一个角.为了方便规定这个角是正角,所以逆时针方向也就相应规定为正方向了.

所以为了我们在代码中以顺时针为正方向,我们将作为带入上述公式

/   封装公式   /

现在我们就把上面的公式封装成工具代码,这可谓是一『利器』,日后我们自定义View中也会经常用到!

首先,我们要把360°的角度制(degree)转化为弧度制(radian),这样我们在绘制时直接使用角度制会方便很多。

/*** 角度制转弧度制*/
private fun Float.degree2radian(): Float {return (this / 180f * PI).toFloat()
}/*** 计算某角度的sin值*/
fun Float.degreeSin(): Float {return sin(this.degree2radian())
}/*** 计算某角度的cos值*/
fun Float.degreeCos(): Float {return cos(this.degree2radian())
}

然后,根据公式写代码即可得到我们的『利器』。这里我们需要外部传入PointF实例,而不是每次创建,以提升性能

/*** 计算一个点坐标,绕原点旋转一定角度后的坐标*/
fun PointF.degreePointF(outPointF: PointF, degree: Float) {outPointF.x = this.x * degree.degreeCos() - this.y * degree.degreeSin()outPointF.y = this.y * degree.degreeCos() + this.x * degree.degreeSin()
}

/   绘制过程   /

1. 定义、初始化属性

1.1 自定义属性部分

这一步比较容易,在attrs.xml中定义我们的属性,在Layout中声明变量,并做初始化即可。这里我们就只贴出声明变量的代码。

//********************************
//* 自定义属性部分
//********************************/*** 雷达网图半径*/
private var mWebRadius: Float = 0f/*** 雷达网图半径对应的最大进度*/
private var mWebMaxProgress: Int = 0/*** 雷达网线颜色*/
@ColorInt
private var mWebLineColor: Int = 0/*** 雷达网线宽度*/
private var mWebLineWidth: Float = 0f/*** 雷达图各定点文字颜色*/
@ColorInt
private var mTextArrayedColor: Int = 0/*** 雷达图文字数组字体路径*/
private var mTextArrayedFontPath: String? = null/*** 雷达图中心连接区域颜色*/
@ColorInt
private var mAreaColor: Int = 0/*** 雷达图中心连接区域边框颜色*/
@ColorInt
private var mAreaBorderColor: Int = 0/*** 雷达图中心文字名称*/
private var mTextCenteredName: String = default_textCenteredName/*** 雷达图中心文字颜色*/
@ColorInt
private var mTextCenteredColor: Int = 0/*** 雷达图中心文字字体路径*/
private var mTextCenteredFontPath: String? = null/*** 文字数组,且以该数组长度确定雷达图是几边形*/
private var mTextArray: Array<String> by Delegates.notNull()/*** 进度数组,与TextArray一一对应*/
private var mProgressArray: Array<Int> by Delegates.notNull()/*** 执行动画前的进度数组,与TextArray一一对应*/
private var mOldProgressArray: Array<Int> by Delegates.notNull()/*** 动画时间,为0代表没有动画* NOTE: 如果是速度一定模式下,代表从雷达中心执行动画到顶点的时间*/
private var mAnimateTime: Long = 0L/*** 动画模式,默认为时间一定模式*/
private var mAnimateMode: Int = default_animateMode

1.2 计算属性部分

所谓计算属性就是我们要通过某自定义属性为基础计算得来的属性。举个????:各属性描述文字的字体大小,此处我们使用雷达图半径✖UI稿的比例得来,其它属性也同理,代码如下:

搜索公众号程序员小乐回复关键字“offer”,获取算法面试题和答案。

//********************************
//* 计算属性部分
//********************************/*** 垂直文本距离雷达主图的宽度*/
private var mVerticalSpaceWidth: Float by Delegates.notNull()/*** 水平文本距离雷达主图的宽度*/
private var mHorizontalSpaceWidth: Float by Delegates.notNull()/*** 文字数组中的字体大小*/
private var mTextArrayedSize: Float by Delegates.notNull()/*** 文字数组设置字体大小后的文字宽度,取字数最多的*/
private var mTextArrayedWidth: Float by Delegates.notNull()/*** 文字数组设置字体大小后的文字高度*/
private var mTextArrayedHeight: Float by Delegates.notNull()/*** 该View的宽度*/
private var mWidth: Float by Delegates.notNull()/*** 该View的高度*/
private var mHeight: Float by Delegates.notNull()
/*** 初始化计算属性,基本的宽高、字体大小、间距等数据* NOTE:以UI稿比例为准,根据[mWebRadius]来计算*/
private fun initCalculateAttributes() {//根据比例计算相应属性(mWebRadius / 100).let {mVerticalSpaceWidth = it * 8mHorizontalSpaceWidth = it * 10mTextArrayedSize = it * 12}//设置字体大小后,计算文字所占宽高mPaint.textSize = mTextArrayedSizemTextArray.maxBy { it.length }?.apply {mTextArrayedWidth = mPaint.measureText(this)mTextArrayedHeight = mPaint.fontSpacing}mPaint.utilReset()//动态计算出view的实际宽高mWidth = (mTextArrayedWidth + mHorizontalSpaceWidth + mWebRadius) * 2.1fmHeight = (mTextArrayedHeight + mVerticalSpaceWidth + mWebRadius) * 2.1f
}

1. 3 绘制依赖的属性部分

绘制依赖的属性就是我们在实际绘制时需要使用的全局属性,我们会提前初始化他们,这样就可以复用,避免在draw()方法中每次都new对象开辟内存。

假如我们在draw()方法中new了Paint对象,AS也会提示警告我们,截图和翻译如下⚠️:

Avoid object allocations during draw/layout operations (preallocate and reuse instead) less... (⌘F1)
避免在绘制和布局期间创建对象,采用提前创建和能复用的方式代替Inspection info:You should avoid allocating objects during a drawing or layout operation.
These are called frequently, so a smooth UI can be interrupted by garbage collection pauses caused by the object allocations.
你应该避免在绘制和布局期间创建对象。他们会被频繁执行,因此,平滑的UI会被对象分配导致的垃圾收集暂停中断。The way this is generally handled is to allocate the needed objects up front and to reuse them for each drawing operation.
一般的处理方式就是提前初始化需要的对象并在每次绘制操作时复用它们。Some methods allocate memory on your behalf (such as Bitmap.create), and these should be handled in the same way.
有些方法替你分配了内存(比如Bitmap.create),这些方法应该采用相同的处理方式。Issue id: DrawAllocation

所以我们把画笔、Path、存放坐标的数组等全局声明并初始化,代码如下:

//********************************
//* 绘制使用的属性部分
//********************************/*** 全局画笔*/
private val mPaint = createPaint()
private val mHelperPaint = createPaint()/*** 全局路径*/
private val mPath = Path()/*** 雷达网虚线效果*/
private var mDashPathEffect: DashPathEffect by Delegates.notNull()/*** 雷达主图各顶点的坐标数组*/
private var mPointArray: Array<PointF> by Delegates.notNull()/*** 文字数组各文字的坐标数组*/
private var mTextArrayedPointArray: Array<PointF> by Delegates.notNull()/*** 文字数组各进度的坐标数组*/
private var mProgressPointArray: Array<PointF> by Delegates.notNull()/*** 作转换使用的临时变量*/
private var mTempPointF: PointF = PointF()/*** 雷达图文字数组字体*/
private var mTextArrayedTypeface: Typeface? = null/*** 雷达图中心文字字体*/
private var mTextCenteredTypeface: Typeface? = null/*** 动画处理器数组*/
private var mAnimatorArray: Array<ValueAnimator?> by Delegates.notNull()/*** 各雷达属性动画的时间数组*/
private var mAnimatorTimeArray: Array<Long> by Delegates.notNull()
/*** 初始化绘制相关的属性*/
private fun initDrawAttributes() {context.dpf2pxf(2f).run {mDashPathEffect = DashPathEffect(floatArrayOf(this, this), this)}mPointArray = Array(mTextArray.size) { PointF(0f, 0f) }mTextArrayedPointArray = Array(mTextArray.size) { PointF(0f, 0f) }mProgressPointArray = Array(mTextArray.size) { PointF(0f, 0f) }if (mTextArrayedFontPath != null) {mTextArrayedTypeface = Typeface.createFromAsset(context.assets, mTextArrayedFontPath)}if (mTextCenteredFontPath != null) {mTextCenteredTypeface = Typeface.createFromAsset(context.assets, mTextCenteredFontPath)}
}

1.4 定义设置属性的API

这里我们分别暴露设置文字数组、属性进度数组、执行动画前的进度数组三个API

//********************************
//* 设置数据属性部分
//********************************fun setTextArray(textList: List<String>) {this.mTextArray = textList.toTypedArray()this.mProgressArray = Array(mTextArray.size) { 0 }this.mOldProgressArray = Array(mTextArray.size) { 0 }initView()
}fun setProgressList(progressList: List<Int>) {this.mProgressArray = progressList.toTypedArray()initView()
}/*** 设置执行动画前的进度*/
fun setOldProgressList(oldProgressList: List<Int>) {this.mOldProgressArray = oldProgressList.toTypedArray()initView()
}

2. 绘制N角形雷达网

我们在绘制前整体将坐标系原点移动到View的中心处,这样很便于之后的绘制。如下:

override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)if (canvas == null) returncanvas.helpGreenCurtain(debug)canvas.save()canvas.translate(mWidth / 2, mHeight / 2)//此处做数据校验if (checkIllegalData(canvas)) {//绘制网状图形drawWeb(canvas)//绘制文字数组drawTextArray(canvas)//绘制连接区域drawConnectionArea(canvas)//绘制中心的文字drawCenterText(canvas)}canvas.restore()
}

然后就是绘制N角形网了,代码就是我们先前思路的具体体现。

/*** 绘制网状图形*/
private fun drawWeb(canvas: Canvas) {canvas.save()val rDeg = 360f / mTextArray.sizemTextArray.forEachIndexed { index, _ ->//绘制虚线,每次都将坐标系逆时针旋转(rDeg * index)度canvas.save()canvas.rotate(-rDeg * index)mPaint.pathEffect = mDashPathEffectmPaint.color = mWebLineColormPaint.strokeWidth = mWebLineWidthcanvas.drawLine(0f, 0f, 0f, -mWebRadius, mPaint)mPaint.utilReset()//用三角函数计算出最长的网的边val lineW = mWebRadius * (rDeg / 2).degreeSin() * 2for (i in 1..4) {//绘制网的边,每次将坐标系向上移动(mWebRadius / 4f)*i,//且顺时针旋转(rDeg / 2)度,然后绘制长度为(lineW / 4f * i)的实线canvas.save()canvas.translate(0f, -mWebRadius / 4f * i)canvas.rotate(rDeg / 2)mPaint.color = mWebLineColormPaint.strokeWidth = mWebLineWidthcanvas.drawLine(0f, 0f, lineW / 4f * i, 0f, mPaint)mPaint.utilReset()canvas.restore()}canvas.restore()}canvas.restore()
}

3. 绘制N个角的属性文字

这一步除了用代码实现我们先前的思路外,也有关于文字位置的整体处理与微调处理,这样一顿猛如虎的操作之后我们的还原度才能更上一层楼。

搜索公众号程序员小乐回复关键字“Java”,获取一份Java面试题和答案礼包。

/*** 绘制文字数组*/
private fun drawTextArray(canvas: Canvas) {canvas.save()val rDeg = 360f / mTextArray.size//先计算出雷达图各个顶点的坐标mPointArray.forEachIndexed { index, pointF ->if (index == 0) {pointF.x = 0fpointF.y = -mWebRadius} else {mPointArray[index - 1].degreePointF(pointF, rDeg)}//绘制辅助圆点if (debug) {mHelperPaint.color = Color.REDcanvas.drawCircle(pointF.x, pointF.y, 5f, mHelperPaint)mHelperPaint.utilReset()}}//基于各顶点坐标,计算出文字坐标并绘制文字mTextArrayedPointArray.mapIndexed { index, pointF ->pointF.x = mPointArray[index].xpointF.y = mPointArray[index].yreturn@mapIndexed pointF}.forEachIndexed { index, pointF ->mPaint.color = mTextArrayedColormPaint.textSize = mTextArrayedSizeif (mTextArrayedTypeface != null) {mPaint.typeface = mTextArrayedTypeface}when {index == 0 -> {//微调修正文字y坐标pointF.y += mPaint.getBottomedY()pointF.y = -(pointF.y.absoluteValue + mVerticalSpaceWidth)mPaint.textAlign = Paint.Align.CENTER}mTextArray.size / 2f == index.toFloat() -> {//微调修正文字y坐标pointF.y += mPaint.getToppedY()pointF.y = (pointF.y.absoluteValue + mVerticalSpaceWidth)mPaint.textAlign = Paint.Align.CENTER}index < mTextArray.size / 2f -> {//微调修正文字y坐标if (pointF.y < 0) {pointF.y += mPaint.getBottomedY()} else {pointF.y += mPaint.getToppedY()}pointF.x = (pointF.x.absoluteValue + mHorizontalSpaceWidth)mPaint.textAlign = Paint.Align.LEFT}index > mTextArray.size / 2f -> {//微调修正文字y坐标if (pointF.y < 0) {pointF.y += mPaint.getBottomedY()} else {pointF.y += mPaint.getToppedY()}pointF.x = -(pointF.x.absoluteValue + mHorizontalSpaceWidth)mPaint.textAlign = Paint.Align.RIGHT}}canvas.drawText(mTextArray[index], pointF.x, pointF.y, mPaint)mPaint.utilReset()}canvas.restore()
}

4. 绘制中心连接区域

这一步也算是驾轻就熟的操作了,根据各属性进度以及坐标公式,得出各点坐标,然后构建Path,绘制即可。

/*** 绘制雷达连接区域*/
private fun drawConnectionArea(canvas: Canvas) {canvas.save()val rDeg = 360f / mTextArray.size//根据雷达图第一个坐标最为基坐标进行相应计算,算出各个进度坐标val bPoint = mPointArray.first()mProgressPointArray.forEachIndexed { index, pointF ->val progress = mProgressArray[index] / mWebMaxProgress.toFloat()pointF.x = bPoint.x * progresspointF.y = bPoint.y * progresspointF.degreePointF(mTempPointF, rDeg * index)pointF.x = mTempPointF.xpointF.y = mTempPointF.y//绘制辅助圆点if (debug) {mHelperPaint.color = Color.BLACKcanvas.drawCircle(pointF.x, pointF.y, 5f, mHelperPaint)mHelperPaint.utilReset()}//使用路径连接各个点if (index == 0) {mPath.moveTo(pointF.x, pointF.y)} else {mPath.lineTo(pointF.x, pointF.y)}if (index == mProgressPointArray.lastIndex) {mPath.close()}}//绘制区域路径mPaint.color = mAreaColorcanvas.drawPath(mPath, mPaint)mPaint.utilReset()//绘制区域路径的边框mPaint.color = mAreaBorderColormPaint.style = Paint.Style.STROKEmPaint.strokeWidth = mWebLineWidthmPaint.strokeJoin = Paint.Join.ROUNDcanvas.drawPath(mPath, mPaint)mPath.reset()mPaint.utilReset()canvas.restore()
}

5. 绘制中心数字&名字

这一步也是常规操作,唯一需注意的也是对文字位置的微调,提高还原度

/*** 绘制中心文字*/
private fun drawCenterText(canvas: Canvas) {canvas.save()//绘制数字mPaint.color = mTextCenteredColormPaint.textSize = mTextArrayedSize / 12 * 20mPaint.textAlign = Paint.Align.CENTERif (mTextCenteredTypeface != null) {mPaint.typeface = mTextCenteredTypeface}//将坐标系向下微调移动canvas.translate(0f, mPaint.fontMetrics.bottom)var sum = mProgressArray.sum().toString()//添加辅助文本if (debug) {sum += "ajk你好"}canvas.drawText(sum, 0f, mPaint.getBottomedY(), mPaint)mPaint.utilReset()//绘制名字mPaint.color = mTextCenteredColormPaint.textSize = mTextArrayedSize / 12 * 10mPaint.textAlign = Paint.Align.CENTERif (mTextArrayedTypeface != null) {mPaint.typeface = mTextArrayedTypeface}canvas.drawText(mTextCenteredName, 0f, mPaint.getToppedY(), mPaint)mPaint.utilReset()//绘制辅助线if (debug) {mHelperPaint.color = Color.REDmHelperPaint.strokeWidth = context.dpf2pxf(1f)canvas.drawLine(-mWidth, 0f, mWidth, 0f, mHelperPaint)mHelperPaint.utilReset()}canvas.restore()
}

6. 添加动画效果

动画的本质就是不断调整各属性值的坐标,然后重绘View

在我们的代码中各属性的坐标又是根据属性进度得来的,所以不断调整各属性进度就能产生动画。

在初始化操作时要提前初始化动画处理器

/*** 初始化动画处理器*/
private fun initAnimator() {mAnimatorArray = Array(mTextArray.size) { null }mAnimatorTimeArray = Array(mTextArray.size) { 0L }mAnimatorArray.forEachIndexed { index, _ ->val sv = mOldProgressArray[index].toFloat()val ev = mProgressArray[index].toFloat()mAnimatorArray[index] = if (sv == ev) null else ValueAnimator.ofFloat(sv, ev)if (mAnimateMode == ANIMATE_MODE_TIME) {mAnimatorTimeArray[index] = mAnimateTime} else {//根据最大进度和动画时间算出恒定速度val v = mWebMaxProgress.toFloat() / mAnimateTimemAnimatorTimeArray[index] = if (sv == ev) 0L else ((ev - sv) / v).toLong()}}
}
/*** 各属性动画一起执行*/
fun doInvalidate() {mAnimatorArray.forEachIndexed { index, _ ->doInvalidate(index)}
}/*** 指定某属性开始动画*/
fun doInvalidate(index: Int, block: ((Int) -> Unit)? = null) {if (index >= 0 && index < mAnimatorArray.size) {val valueAnimator = mAnimatorArray[index]val at = mAnimatorTimeArray[index]if (valueAnimator != null && at > 0) {valueAnimator.duration = atvalueAnimator.removeAllUpdateListeners()valueAnimator.addUpdateListener {val av = (it.animatedValue as Float)mProgressArray[index] = av.toInt()invalidate()}//设置动画结束监听if (block != null) {valueAnimator.removeAllListeners()valueAnimator.addListener(object : Animator.AnimatorListener {override fun onAnimationRepeat(animation: Animator?) {}override fun onAnimationEnd(animation: Animator?) {block.invoke(index)}override fun onAnimationCancel(animation: Animator?) {}override fun onAnimationStart(animation: Animator?) {}})}valueAnimator.start()} else {block?.invoke(index)}}
}

开箱即用源码地址:

github.com/drawf/SourceSet/blob/master/app/src/main/java/me/erwa/sourceset/view/RadarView.kt

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

优秀的 Java 项目,代码都是如何分层的?

审阅“史上“最烂的代码

使用IntelliJ IDEA查看类图,内容极度舒适

关注订阅号「顶级架构师」,收看更多精彩架构内容

嘿,你在看吗

万字长文!View的进阶,自定义一款自带动画的雷达图相关推荐

  1. View的进阶,自定义一款自带动画的雷达图

    /   今日科技快讯   / 近日工信部发放四张5G商用牌照,中国联通.中国移动.中国电信和中国广播电视网络有限公司各得到一张牌照.中国的5G商用元年正式开启.5G牌照的发放,将推升产业链的成熟.加快 ...

  2. 微信小程序之自定义模态弹窗(带动画)实例

    代码地址如下: http://www.demodashi.com/demo/13991.html 一.前期准备工作 软件环境:微信开发者工具 官方下载地址:https://mp.weixin.qq.c ...

  3. 【JFreeChart】自定义蜘蛛网图生成带刻度三角雷达图 自定义文字风格 背景色

    工作中需要生成PDF 且包含图表.iText 或其他 可以访问网页地址转PDF.但是效果不是特别理想.故用iText代码方式实现生成PDF.奈何图表又是一个问题(还是个三角形的雷达图).Java端生成 ...

  4. 【2万字长文】深入浅出主流的几款小程序跨端框架原理

    开发者(KaiFaX) 面向全栈工程师的开发者 专注于前端.Java/Python/Go/PHP的技术社区 作者 | 雾豹 来源 | https://juejin.im/post/6881597846 ...

  5. 万字长文!对比分析了多款存储方案,KeeWiDB最终选择自己来

    大数据时代,无人不知Google的"三驾马车"."三驾马车"指的是Google发布的三篇论文,介绍了Google在大规模数据存储与计算方向的工程实践,奠定了业界 ...

  6. 李开复万字长文科普人工智能:AI是什么 将带我们去哪儿?

    人工智能正以前所未有的态势汹涌而来,一方面是风投和创业创新,都把人工智能当做了下一个尚未被开垦的宝地:另一方面是应用,比起概念盛行的阶段,现在的无人车.AlphaGo等已经把人工智能技术带到了&quo ...

  7. Qt 自定义悬浮窗(带动画,类似QQ拼音输入法)

    1.运行效果 实现功能: 1.可拖动. 2.可显示,可隐藏 . 3.悬浮在主界面上面. 4.带动画. 2.ui界面  3.源码 //FloatingWindow.h #pragma once#incl ...

  8. 20款前端特效动画及源码

    最近优化项目时看到一些实用的特效 感觉还不错 下面就分享给大家 代码量过长的我就不展示了 可以去在这里资源站源码部分预览下载 1.Loading加载动画 在canvas画布上,我们动态绘制许多多边形, ...

  9. 两万字长文总结,梳理 Java 入门进阶那些事

    两万字长文总结,梳理 Java 入门进阶那些事 先给大家看下完整的思维导图,也是这篇文章的主要脉络. Java从入门到进阶学习路线 主导三个项目,让我独当一面 能力提升你要怎么学 全篇总结 Java ...

最新文章

  1. 2021腾讯数字生态大会:腾讯安全聚焦安全共建,护航数字经济发展
  2. android ble5.0添加扫描过滤,bluetooth-lowenergy
  3. python 屏幕找图 点击_捕获屏幕并查找参考图像
  4. js中函数传递参数,究竟是值传递还是引用传递?
  5. python序列类型-python序列类型种类详解
  6. pxe安装系统 ip获取错误_【图说】消防系统安装典型错误举例
  7. JS中代表结束的三个关键字 break,continue,return
  8. 用python偷偷给班级群女同学的颜值进行排名,排最后的 说开学要打爆我
  9. 拿什么拯救你,程序新丁?
  10. RFC 2544阅读笔记
  11. c语言结构体memcmp,用memcmp()比较结构体
  12. 【Angular】@Input和@Output
  13. vue使用 moment.js 格式化时间(获取当前日期的周一和周日)
  14. 【CF226C】Anniversary
  15. oracle 安装ora 27102,Oracle数据库之ORA-27102: out of memory Linux-X86_64
  16. (二)操作系统的发展与分类
  17. bigemap如何导入矢量边界范围下载地图(KML/KMZ/SHP)
  18. python爬虫 2021中国大学排名定向爬虫
  19. android7 隐藏图标,华为nova7怎么隐藏桌面图标?华为nova7隐藏桌面图标教程
  20. 京东运维开发工程师一面经验总结2020

热门文章

  1. 物理卷,卷组,逻辑卷
  2. VMware中运行招行专业版问题
  3. 自动驾驶测试相关论文推荐
  4. 花 1 小时,开源设计 LoRa 烟感烟雾报警器
  5. Linux初识——基础指令及使用规则
  6. flash air移动应用中添加百度移动广告联盟sdk挣钱方法
  7. 将“闲聊么”改造成匿名聊天室
  8. 2023年中国科学院大学战略院管理科学与工程考研上岸前辈备考经验
  9. 2021GIAC全球互联网架构大会 | 聚焦前沿技术,传递实践经验
  10. 阿里云OSS、EsayExcel