RecyclerView上滑显示【回到底部】按钮

RecyclerView上滑显示【回到底部】按钮

在 RecyclerView 中的 onScrolled 方法中实现回到底部按钮的显示逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

private var isUserScrolling = false
private var hasScrolledUp = false // 是否向上滚动
private var lastScrollOffset = 0 // 上次滚动位置
private var isScrollingDown = false // 是否向下滚动
private var totalScrollDownDistance = 0 // 累计向下滚动距离

recyclerView.apply {
this.layoutManager = layoutManager
this.adapter = adapter

itemAnimator = null

// 添加滚动监听,检测用户手动滚动
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
Log.d(TAG, "onScrollStateChanged: newState=$newState")
isUserScrolling = newState != RecyclerView.SCROLL_STATE_IDLE
}

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
handleScrollChanged(dy)
}
})
}

scrollToEndBtn.setOnClickListener {
val itemCount = adapter.itemCount
if (itemCount > 0) {
recyclerView.smoothScrollToPosition(itemCount - 1)
}
}

1. 累计下滑距离超过200像素,出现【回到底部】按钮

✅ 显示条件:

  • 用户向下滚动,通过 dy 参数检测滚动方向(正值=向下,负值=向上)
  • 累计下滑距离超过200像素
  • 最后2条Item不可见
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 处理滚动变化
*/
private fun handleScrollChanged(dy: Int) {
val currentScrollOffset = recyclerView.computeVerticalScrollOffset()

// 检测滚动方向
if (dy > 0) {
// 向下滚动 (正值表示向下)
isScrollingDown = true
totalScrollDownDistance += dy
} else if (dy < 0) {
// 向上滚动 (负值表示向上)
isScrollingDown = false
// 重置累计向下滚动距离
totalScrollDownDistance = 0
}

// 检查最后2条Item是否可见
val itemCount = adapter.itemCount
val lastVisiblePosition = layoutManager.findLastVisibleItemPosition()
val isNearBottom = itemCount > 2 && lastVisiblePosition >= itemCount - 2

// 当向下滚动累计超过200时显示按钮,但如果最后2条Item可见则隐藏
val shouldShow = isScrollingDown && totalScrollDownDistance > 200 && !isNearBottom

if (shouldShow != hasScrolledUp) {
hasScrolledUp = shouldShow
scrollToEndBtn.isVisible = shouldShow
}

lastScrollOffset = currentScrollOffset
}

2. 底部不可见高度大于600像素,出现【回到底部】按钮

✅ 显示条件:

  • 底部不可见高度大于600像素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 当页面底部不可见高度大于600像素,出现该btn
*/
private fun handleScrollChanged() {
// 检查底部不可见内容高度
val scrollRange = recyclerView.computeVerticalScrollRange() // 总内容高度
val scrollExtent = recyclerView.computeVerticalScrollExtent() // 可见区域高度
val scrollOffset = recyclerView.computeVerticalScrollOffset() // 当前滚动位置
val bottomInvisibleHeight = scrollRange - scrollExtent - scrollOffset // 底部不可见高度
val shouldShow = bottomInvisibleHeight >= 600 // 是否接近底部

if (shouldShow != hasScrolledUp) {
hasScrolledUp = shouldShow
scrollToEndBtn.isVisible = shouldShow
}
}

自定义ImageSpan之行居中,可设置左右间距

自定义 ImageSpan 之行居中,可设置左右间距

image-20241126200147

实现功能:

  1. Image 所在行居中显示
  2. 可以设置图片和前后文本的间距
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.text.style.ImageSpan

open class MarginImageSpan(
context: Context,
drawableRes: Int,
val l: Int = 0,
val r: Int = 0
) : ImageSpan(context, drawableRes) {

override fun getSize(
paint: Paint,
text: CharSequence?,
start: Int,
end: Int,
fm: Paint.FontMetricsInt?
): Int {
val d: Drawable = drawable
val rect = d.bounds

if (fm != null) {
val var7 = Paint.FontMetricsInt()
paint.getFontMetricsInt(var7)
fm.ascent = var7.ascent
fm.descent = var7.descent
fm.top = var7.top
fm.bottom = var7.bottom
}

return l + rect.right + r
}

override fun draw(
canvas: Canvas,
text: CharSequence,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int,
bottom: Int,
paint: Paint
) {
// image to draw
val b = drawable
// font metrics of text to be replaced
val fm = paint.fontMetricsInt
var transY = ((y + fm.descent + y + fm.ascent) / 2 - b.bounds.bottom / 2)
// to check the last line.(当 image 在单独一行显示时可能会存在这个问题)
if (transY > bottom - b.bounds.bottom) transY = bottom - b.bounds.bottom
//
val transX = x + l
canvas.save()
canvas.translate(transX, transY.toFloat())
b.draw(canvas)
canvas.restore()
}
}

使用场景

1
2
3
4
5
6
val spanStr = SpannableStringBuilder("#")
spanStr.append(data.seriesName)
val imageSpan = MarginImageSpan(context, R.drawable.home_ic, 0, 3.dp)
// 创建 ImageSpan
spanStr.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

HDC常用功能

SDK 版本:HarmonyOS NEXT Developer Beta2 SDK (5.0.0.31)
DevEco-Studio 版本:DevEco Studio NEXT Developer Beta2 (5.0.3.502)
工程机版本:ALN-AL00 NEXT.0.0.31

0x01 鸿蒙样机升级,查询软件版本和序列号

1
2
3
4
5
6
## 获取 OTA 系统(鸿蒙)软件版本
$ hdc shell param get const.product.software.version

## 获取序列号(SN)
$ hdc list targets

麻烦升级 NEXT.0.0.68 版本
OTA 系统版本号
ALT-AL10 5.0.0.66(SP6C00E66R6P7log)
序列号
22M0224313000155

0x02 鸿蒙手机抓取全量日志保存到本地文件

1
2
3
4
hdc shell hilog -Q domainoff
hdc shell hilog -Q pidoff
hdc shell hilog -b D
hdc hilog >> d://txt.log

鸿蒙-使用系统文件预览不同类型的本地文件

SDK 版本:HarmonyOS NEXT Developer Beta2 SDK (5.0.0.31)
DevEco-Studio 版本:DevEco Studio NEXT Developer Beta2 (5.0.3.502)
工程机版本:ALN-AL00 NEXT.0.0.31

使用系统文件预览不同类型的本地文件

MediaUtils.ets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
* Copyright (c) 2024. Dench.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { fileUri } from "@kit.CoreFileKit";
import { BusinessError } from "@kit.BasicServicesKit";
import { filePreview } from "@kit.PreviewKit";
import { uniformTypeDescriptor } from "@kit.ArkData";

/**
* filePreview(文件预览)
*/
export function openSystemPreview(context: Context, path: string) {
// let uiContext = getContext(this);
// let displayInfo: filePreview.DisplayInfo = {
// x: 100,
// y: 100,
// width: 800,
// height: 800
// };
const uri = fileUri.getUriFromPath(path);
const mimeType = getFileMimeType(path);
console.info("uri:" + uri);
console.info("mimeType:" + mimeType);
let fileInfo: filePreview.PreviewInfo = {
uri: uri,
mimeType: mimeType,
};
filePreview
.openPreview(context, fileInfo)
.then(() => {
console.info("Succeeded in opening preview");
})
.catch((err: BusinessError) => {
console.error(
`Failed to open preview, err.code = ${err.code}, err.message = ${err.message}`
);
});
}

/**
* 根据文件后缀查询对应文件的 mimeType
*/
export function getFileMimeType(path: string): string {
if (path.indexOf(".") != -1) {
try {
const ext = "." + path.split(".").pop();
console.info("ext:" + ext);
// 2.可根据 “.mp3” 文件后缀查询对应UTD数据类型,并查询对应UTD数据类型的具体属性
let typeId1 =
uniformTypeDescriptor.getUniformDataTypeByFilenameExtension(ext);
console.info("typeId1:" + typeId1);
let typeObj1 = uniformTypeDescriptor.getTypeDescriptor(typeId1);
// console.info('typeId:' + typeObj1.typeId);
console.info("belongingToTypes:" + typeObj1.belongingToTypes);
// console.info('description:' + typeObj1.description);
// console.info('referenceURL:' + typeObj1.referenceURL);
// console.info('filenameExtensions:' + typeObj1.filenameExtensions);
console.info("mimeTypes:" + typeObj1.mimeTypes);
return typeObj1.mimeTypes[0];
} catch (e) {}
}
return "text/plain";
}

Reference

https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/preview-arkts-V5#section1081123302517

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/uniform-data-type-descriptors-V5

文件大小最大两位小数显示

SDK 版本:HarmonyOS NEXT Developer Beta2 SDK (5.0.0.31)
DevEco-Studio 版本:DevEco Studio NEXT Developer Beta2 (5.0.3.502)
工程机版本:ALN-AL00 NEXT.0.0.31

文件大小最大两位小数显示

FileUtil.ets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
* Copyright (c) 2024. Dench.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { intl } from "@kit.LocalizationKit";

export class FileUtil {
/**
* 文件大小最大两位小数显示
*/
getFileSizeStr(size?: number): string {
const numberFormat = new intl.NumberFormat("zh-CN", {
maximumFractionDigits: 2,
});
if (size === undefined) {
return "";
}
if (size >= this.GB) {
return numberFormat.format(size / this.GB) + "G";
}
if (size >= this.MB) {
return numberFormat.format(size / this.MB) + "M";
}

if (size >= this.KB) {
return numberFormat.format(size / this.KB) + "k";
}
return size + "b";
}

private readonly KB = 1000;
private readonly MB = 1000000;
private readonly GB = 1000000000;
}

Reference

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/i18n-numbers-weights-measures-V5#%E6%95%B0%E5%AD%97%E6%A0%BC%E5%BC%8F%E5%8C%96

鸿蒙-日期格式化输出(ArkTS)

DevEco Studio 版本:DevEco Studio NEXT Developer Preview2(4.1.3.700)
HarmonyOS API 版本:4.1.0(11)

日期格式化输出(ArkTS)

1
2
3
4
5
6
7
8
9
10
11
// 时间戳转换为显示时间输出
function timestampToDate(t: number): string {
let date = new Date(t);
const year = date.getFullYear();
const month = ("0" + (date.getMonth() + 1)).slice(-2);
const day = ("0" + date.getDate()).slice(-2);
const hours = ("0" + date.getHours()).slice(-2);
const minutes = ("0" + date.getMinutes()).slice(-2);
const seconds = ("0" + date.getSeconds()).slice(-2);
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×