前语
都说你不理财,财不理你,现在我理财了,财却直接离我而去了,我成为了绿莹莹的大韭菜。
望着毫无起色的基金,我陷入了沉思,基金到底能够带给我什么呢?我又能从中学习到什么呢?
嗯,尽管我是一颗大韭菜,但同时我也是一名程序员,站在代码的角度,至少我能学习一下它的成绩走势图时怎么完成的呀,从而练习一下我的自定义View技术。
所以,今日咱就先不考虑是加仓死扛仍是换回割肉了。今日!就让咱们来测验复刻一下这个基金的成绩走势图吧。
就复刻这个基金吧
作用展示
直接先展示一下复刻的作用吧,下载apk体验更佳,下载地址传送门点这里。
获取数据
想要制作出相同的作用,条件得有数据。这边咱们能够抓取一下接口数据,获取到近1月、近3月、近1年等JSON数据。
内容格局如下:
{
"data": [
{
"date": "2023-03-01",
"yield": "-0.23",
"indexYield": "1.41",
"fundTypeYield": "0.86",
"benchQuote": "0.01"
},
{
"date": "2023-03-02",
"yield": "-1.17",
"indexYield": "1.19",
"fundTypeYield": "0.17",
"benchQuote": "-0.64"
},
... 省略 ...
],
"total": {
"totalYield": "-5.10",
"totalIndexYield": "-0.46",
"totalFundTypeYield": "-1.46",
"totalBenchQuote": "-2.31"
},
"success": true,
"totalCount": 24,
"name": "保密!无奖竞猜~"
}
主要分为两块内容。
-
一组包含每天收益率的列表。
-
date
:表明日期。 -
yield
:表明本基金当日的收益率。 -
indexYield
:表明沪深300当日的收益率。 -
fundTypeYield
:表明同类均匀当日的收益率。
-
-
以及这段时间总的收益率数据。
-
totalYield
:表明本基金总收益率。 -
totalIndexYield
:表明沪深300总收益率。 -
totalFundTypeYield
:表明同类均匀总收益率。
-
接着将JSON
转为对应的Bean
文件。
data class FundReturnRateBean(
@SerializedName("data")
var dayRateList: List<DayRateDetail>,
@SerializedName("total")
var totalReturnRate: TotalReturnRate,
var name: String,
var success: Boolean,
var totalCount: Int
)
data class DayRateDetail(
var benchQuote: String,
var fundTypeYield: String,//同类均匀收益率
var indexYield: String,//沪深300收益率
var pdate: String,//日期
var yield: String//本基金收益率
)
data class TotalReturnRate(
var totalYield: String,//本基金总收益率
var totalIndexYield: String,//沪深300总收益率
var totalFundTypeYield: String,//同类均匀总收益率
var totalBenchQuote: String
)
有了数据,接下来就能够动手完成该功用啦。
详细完成
需求分析
分析一下图表中存在的元素,有:
- 横坐标:日期。
- 纵坐标:收益率。
- 走势线:别离代表着本基金、同类均匀、沪深300的收益率走势线。
- 标签:蚂蚁基金。
这里咱们能够运用自定义Drawable
来将整个图表进行拆分,咱们能够分为3层:
-
FundGridDrawable
:用于制作横坐标与纵坐标。 -
RateLineDrawable
:用于制作本基金、同类均匀、沪深300的收益率走势线。 -
FundLabelDrawable
:用于制作标签。
然后按照制作顺序进行逐层制作。
- 制作
fundGridDrawable
。 - 制作
rateLineDrawable
。 - 制作
fundLabelDrawable
。
接着咱们逐层进行完成。
区域划分
因为咱们是分层制作,所以咱们需要按需求进行区域划分。
咱们能够先承认好中心区域,也便是制作走势线的区域,即rateLineDrawable.bounds
。(这个中心区域,下文会用 lineChartRect
表明。)。承认好了中心区域后,咱们就能够使用它来承认fundGridDrawable.bounds
、fundLabelDrawable.bounds
。
有了区域分布图今后,咱们就能够经过代码来完成区域分配了。
private var chartRect = Rect()
private val defaultPadding = 5f.px.toInt()
private val paddingTop = 15f.px.toInt()
private val paddingBottom = 25f.px.toInt()
private val paddingStart = 60f.px.toInt()
private val paddingEnd = 20f.px.toInt()
private val labelWidth = 35f.px.toInt()
private val labelHeight = 100f.px.toInt()
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
chartRect = Rect(0, 0, w, h)
rateLineDrawable.bounds = Rect(
paddingStart,
chartRect.top + paddingTop,
chartRect.right - paddingEnd,
chartRect.bottom - paddingBottom
)
fundGridDrawable.bounds = Rect(
chartRect.left + defaultPadding,
rateLineDrawable.bounds.top - defaultPadding,
rateLineDrawable.bounds.right,
chartRect.bottom - defaultPadding
)
fundGridDrawable.lineChartRect = rateLineDrawable.bounds
fundLabelDrawable.bounds = Rect(
rateLineDrawable.bounds.left,
rateLineDrawable.bounds.bottom - labelWidth,
rateLineDrawable.bounds.left + labelHeight,
rateLineDrawable.bounds.bottom
)
}
区域分配好之后,咱们就要来聊聊详细的制作工作了。
制作坐标轴
坐标轴这边分为横轴与竖轴,别离表明日期与收益率。
咱们先来看横轴。
横轴
横轴表明日期,由三部分构成,别离是:
- 三个
”yyyy-MM-dd”
格局的日期:别离代表着该段周期基金的初始日期、结尾日期以及居中日期。坐落lineChartRect
最左面、中心以及最右边位置。 - 三根短小的竖线:以
lineChartRect.bottom
为起点,向下延伸5dp
。相同坐落lineChartRect
最左面、中心以及最右边位置。 - 一根灰色的实线:跨度刚好是
lineChartRect
的宽度。
FundGridDrawable.kt
private fun drawDateTimeText(canvas: Canvas) {
textPaint.getTextBounds(minDateTime, 0, minDateTime.length, textBoundsRect)
dateTimeTextPxY = lineChartRect.bottom + textBoundsRect.height() + paddingBottom
textPaint.textAlign = Paint.Align.LEFT
//制作初始日期
canvas.drawText(
minDateTime,
lineChartRect.left.toFloat(),
dateTimeTextPxY,
textPaint
)
//制作最左面竖线
canvas.drawLine(
lineChartRect.left.toFloat(),
lineChartRect.bottom.toFloat(),
lineChartRect.left.toFloat(),
lineChartRect.bottom.toFloat() + paddingBottom / 2,
bottomLinePaint
)
...省略代码...
//制作横线
canvas.drawLine(
lineChartRect.left.toFloat(),
lineChartRect.bottom.toFloat(),
lineChartRect.right.toFloat(),
lineChartRect.bottom.toFloat(),
bottomLinePaint
)
}
竖轴
竖轴表明收益率,由两部分构成,别离是:
- 收益率百分比文字:坐落
lineChartRect
左面。 - 收益率虚线:跨度刚好是
lineChartRect
的宽度。
仔细观察了蚂蚁基金,我发现其将竖轴上的收益率等分红4份,也便是画5条线,且除了最上方的最高收益率线与最下方的最低收益率线,中心还要有一条0收益率线。还发现,尽管收益率百分比保留了两位小数,但都是0,也便是说等分的间距其实取整了。
所以在拿到接口回来的List<DayRateDetail>
后,咱们还应进行一番处理,从而得出制作所需的真实收益率数据。
theRateRangeInterval = (maxRate - minRate).div(3).roundToInt()
private fun calcRateAbscissa(): MutableList<Int> {
val rateAbscissaLines = mutableListOf<Int>()
if (theRateRangeInterval == 0) {
return rateAbscissaLines
}
rateAbscissaLines.clear()
rateAbscissaLines.add(0)
for (i in 1..5) {
rateAbscissaLines.add(theRateRangeInterval * i)
if (theRateRangeInterval * i > maxRate) {
break
}
}
for (i in 1..5) {
rateAbscissaLines.add(-theRateRangeInterval * i)
if (-theRateRangeInterval * i < minRate) {
break
}
}
rateAbscissaLines.sort()
Log.e(TAG, "calcRateAbscissa: after sort rateAbscissaLines = $rateAbscissaLines")
return rateAbscissaLines
}
maxRate
与 minRate
别离表明真实接口回来的最大收益率与最小收益率,使用其差额来计算出theRateRangeInterval
。接着以0位起点,遍历循环,向上向下增加收益率,以maxRate
与minRate
作为鸿沟终止条件。
计算出真实的rateAbscissaLines
后,咱们就能够进行制作了。
private var yPx = 0f
private fun drawRateTextAndLines(canvas: Canvas) {
rateAbscissaLines.forEach {
yPx = lineChartRect.top + (maxRate - it).div(yPxSpec).toFloat()
//制作收益率虚线
canvas.drawLine(
lineChartRect.left.toFloat(),
yPx,
lineChartRect.right.toFloat(),
yPx,
rateLinePaint
)
//制作收益率百分比文字
textPaint.textAlign = Paint.Align.RIGHT
canvas.drawText(
"${it.toStringAsFixed(2)}%",
lineChartRect.left.toFloat() - 10f.px,
yPx + 5f.px,
textPaint
)
}
}
制作走势线
走势线一共有三条,别离是本基金走势线、同类均匀走势线以及沪深300走势线。
线其实是由很多的点组成的,接口回来的List<DayRateDetail>
列表中,每个元素其实便是线上的一个点。而点的定位,正是中心之处了。
假如你阅览该文章是从上往下一步一步看下来的,那此刻你就知道,咱们现在已经拿到了lineChartRect
与收益率差额
这两个数据了。经过这两个数据,咱们就能够计算出竖轴的像素标准了。
yPxSpec = (maxRate - minRate).div(lineChartRect.bounds.height())
而横轴的像素标准就更加简略了。
xPxSpec = lineChartRect.bounds.width().div(dayRateDetailList.size.toDouble())
经过 xPxSpec
与 yPxSpec
就能够很方便的完成点的定位啦。再经过Path
将点连成线,就构成了走势线。
以本基金走势线为例。
private var x = 0f
private var yYield = 0f
private fun calcData() {
yieldLinePath.reset()
dayRateDetailList.forEachIndexed { index, dayRateDetail ->
x = bounds.left + index.times(xPxSpec).toFloat()
yYield = bounds.top + (maxRate - dayRateDetail.yield.toDouble()).div(yPxSpec).toFloat()
if (index == 0) {
yieldLinePath.moveTo(x, yYield)
} else {
yieldLinePath.lineTo(x, yYield)
}
}
}
制作标签
标签的制作就很简略了,这里咱们替代“蚂蚁基金”
,改为“JC基金复刻”
。
private fun drawLabelTag(canvas: Canvas) {
canvas.drawText(
"JC基金复刻",
bounds.left.toFloat(),
bounds.bottom - paddingBottom,
labelTextPaint
)
}
总结
其实本文最主要的目的是练习自定义View,完成基金的成绩走势线不算杂乱,但你想完成相同的作用,其实也不简略。我也仅仅起了个开头,假如你有兴趣,后期能够增加上动画,增加下单符号点,还可进一步进行自定义。
文章主要共享了完成原理,假如你想检查文本的一切代码,请检查我的GitHub 项目AntFundChart。创作共享不易,假如本文有协助到你,期望能够给我点个Star,非常感谢。
引荐阅览
-
为何引荐在自定义View中抽Drawable
假如你不是很熟悉本文涉及的自定义Drawable相关知识点,引荐阅览该文。
-
本文apk下载地址
apk大小为4M左右,欢迎下载安装体验。
-
本文项目一切源码地址
一切源码都已开源,托管在GitHub上,创作共享不易,假如有协助到你,期望能够给我点个Star,非常感谢。
到此文章就完毕啦~
其实共享文章的最大目的正是等待着有人指出我的过错,假如你发现哪里有过错,请毫无保留的指出即可,虚心请教。
另外,假如你觉得文章不错,对你有所协助,请给我点个赞,就当鼓舞,谢谢~Peace~✌️!