RecyclerView加载sdcard/Music下的音乐列表

本文介绍了如何在Android应用中使用RecyclerView从sdcard/Music目录加载音乐列表。内容涉及RecyclerView.Adapter的使用,包括重写getItemCount、onCreateViewHolder和onBindViewHolder等关键方法,以及动态权限申请和适配器数据绑定。通过这个过程,实现了显示完整音乐列表的功能。

最近我在学习在学习RecyclerView,跟着老司机玩转App,《Android App 开发入门与项目实战》很友好,比较适合新手入门,通过基础知识和案例相结合,慢慢掌握其中要点。
书中的音乐案例在我的Android Studio模拟器中运行,从媒体库MediaStore.Audio.Media.EXTERNAL_CONTENT_URI加载音频文件列表,显示只有两首歌曲,无论我怎么重启模拟器,结果还是如此。于是我选择从sdcard/Music下遍历文件,通过适配器加载到RecyclerView中。

	[案例代码仓库](https://codechina.youkuaiyun.com/mirrors/aqi00/myapp) 推荐购买书籍来上手。

运行书中的案例,结果如下,显示模拟器中的两首歌曲,实际导入了9首。
在这里插入图片描述
一番操作之后,可以显示完整的音乐列表,除了最佳损友不能播放,或许是MediaPlayer不喜欢损友爱基友。
在这里插入图片描述

以下是具体的步骤:
1、AndroidManifest.xml加载存储卡读写权限:

    <!-- 存储卡读写 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

运行在Android 10及更高版本手机,配置暂时停用分区存储。
<application android:requestLegacyExternalStorage=“true”

2、申请动态权限:方式一、用代码来申请权限;方式二、可以在手机设置中手动打开如下的权限。
在这里插入图片描述

3、写出每个音乐条目布局,没有点击播放时隐藏进度条,点击之后显示它。因为是progressBar,所以无法拖动播放进度。
item_audio.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_audio"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="4"
            android:gravity="left|center"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <TextView
            android:id="@+id/tv_duration"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="right|center"
            android:textColor="@color/black"
            android:textSize="15sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_progress"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:orientation="horizontal"
        android:visibility="gone">

        <ProgressBar
            android:id="@+id/pb_audio"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="4" />

        <TextView
            android:id="@+id/tv_progress"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="right|center"
            android:textColor="@color/black"
            android:textSize="15sp" />
    </LinearLayout>

</LinearLayout>

4、主界面显示RecyclerView布局。
activity_audio_play.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation
auto.waitFor(); setScreenMetrics(1236, 2676); // ================ 配置常量 ================ var LIST_PAGE_ID = "recyclerview"; var DEPOSIT_ID = "min_deposit_tv"; var NAME_ID = "name_tv"; var WEAR_FILTER_ID = "tv_abrade_filter"; var SORT_LIST_ID = "rv_sort_list"; var SELLING_PRICE_ID = "tv_selling_price_value"; var CATEGORY_CONTAINER_ID = "item_category_ll"; var MAX_WAIT_MS = 20000; var TASKS_CSV_PATH = "/sdcard/AIM/tasks.csv"; var GOODS_CSV_PATH = "/sdcard/AIM/goods.csv"; var MUSIC_PATH = "/sdcard/Music/My love 请别让爱凋落.mp3"; // 可配置参数 var MIN_DEPOSIT_THRESHOLD = 300; var PRICE_CEILING = 100; var START_FROM_INDEX = 0; // 用户可配置起始任务索引 var EXCLUDE_KEYWORDS = []; // 留空,稍后从文件加载 var BLACKLIST_PATH = "/sdcard/AIM/黑名单.txt"; // 可自定义路径 // 动态差价阈值表 var PRICE_THRESHOLD_MAP = [ {min: 0, max: 10, diff: 5}, {min: 10, max: 20, diff: 8}, {min: 20, max: 30, diff: 10}, {min: 30, max: 40, diff: 13}, {min: 40, max: 50, diff: 18}, {min: 50, max: 60, diff: 25}, {min: 60, max: 70, diff: 30}, {min: 70, max: 80, diff: 40}, {min: 80, max: 90, diff: 50}, {min: 90, max: 100, diff: 60}, {min: 100, max: Infinity, diff: 70} ]; /* 根据定价返回所需的最小差价 * @param {number} price 定价 * @returns {number} 所需最小差价 */ function getMinRequiredDiff(price) { for (var i = 0; i < PRICE_THRESHOLD_MAP.length; i++) { var rule = PRICE_THRESHOLD_MAP[i]; if (price >= rule.min && price < rule.max) { return rule.diff; } } return 70; // 默认兜底 } var successCount = 0; var File = java.io.File; var parentDir = new File(CSV_PATH).getParentFile(); if (!parentDir.exists()) { parentDir.mkdirs(); } // ================ 工具函数 ================ /** * 从文件加载黑名单关键词(支持整行逗号分隔) */ function loadBlacklist() { try { var File = java.io.File; var FileReader = java.io.FileReader; var BufferedReader = java.io.BufferedReader; var file = new File(BLACKLIST_PATH); if (!file.exists()) { toast("⚠️ 黑名单文件不存在: " + BLACKLIST_PATH); EXCLUDE_KEYWORDS = []; // 默认为空 return; } var reader = new BufferedReader(new FileReader(file)); var line; var keywords = []; while ((line = reader.readLine()) !== null) { var trimmed = line.trim(); // 跳过空行和注释 if (!trimmed || trimmed.startsWith("#")) continue; // 👇 核心:按逗号分割,并去除前后空格 var items = trimmed.split(","); for (var i = 0; i < items.length; i++) { var word = items[i].trim(); // 去除每个词的空格 if (word) { keywords.push(word); } } } reader.close(); EXCLUDE_KEYWORDS = keywords; log("✅ 成功加载黑名单,共 " + keywords.length + " 个关键词"); } catch (e) { toast("❌ 加载黑名单失败: " + e.message); console.error(e); EXCLUDE_KEYWORDS = []; } } function main() { auto.waitFor(); // 等待无障碍服务启动 toast("🚀 开始执行任务"); // 创建输出目录 files.ensureDir("/sdcard/AIM"); // 确保 goods.csv 存在并有表头 ensureGoodsCsvHeader(); // 读取任务列表 var taskNames = readTaskNames(); if (taskNames.length === 0) { toast("❌ 无有效任务可执行"); return false; } // 检查起始索引是否合法 if (START_FROM_INDEX >= taskNames.length) { toast("⚠️ 起始索引超出任务数量!"); log("当前共有 " + taskNames.length + " 个任务,无法从第 " + (START_FROM_INDEX + 1) + " 个开始"); return false; } toast("✅ 开始执行任务,共 " + taskNames.length + " 个,从第 " + (START_FROM_INDEX + 1) + " 个开始"); var successCount = 0; // 遍历任务 for (var i = START_FROM_INDEX; i < taskNames.length; i++) { var taskName = taskNames[i]; log("🔄 正在处理第 " + (i + 1) + " 个任务: " + taskName); toast("📌 " + (i + 1) + "/" + taskNames.length + " | " + taskName); // 复制到剪贴板 setClip(taskName); sleep(800); // 点击搜索框(请根据你的APP实际控件调整) if (!clickSearchBox()) { log("❌ 未找到搜索框,跳过: " + taskName); continue; } sleep(1500); // 粘贴 if (!pasteFromClipboard()) { log("❌ 粘贴失败"); back(); sleep(800); continue; } sleep(1200); // 执行搜索 performSearch(); sleep(3000); // 搜索后操作:抓取数据并写入 var handled = doAfterSearch(taskName); if (handled) { successCount++; log("✅ 成功记录: " + taskName); } else { log("🟡 跳过或无数据: " + taskName); } // 返回上一页 back(); sleep(1200); } toast("🎉 所有任务执行完毕!共成功记录 " + successCount + " 条数据。"); return true; } 用ES5语法将以上auto脚本拼接到以下auto脚本,不要改变源代码逻辑
11-16
贴错了,是这个脚本 auto.waitFor(); // 请求无障碍服务 // 👇 新增:申请截图权限 if (!requestScreenCapture()) { toast("❌ 未获得截图权限,脚本无法运行!"); console.error("请手动开启录屏权限后再运行脚本"); exit(); // 终止脚本,防止后续崩溃 } console.show(); // 显示控制台日志 device.keepScreenDim(); setScreenMetrics(1236, 2676); // 请按实际设备修改 // ================ 配置常量 ================ var LIST_PAGE_ID = "recyclerview"; // 列表 RecyclerView ID var DEPOSIT_ID = "min_deposit_tv"; // 在售字段 ID var NAME_ID = "name_tv"; // 商品名称 ID var WEAR_FILTER_ID = "tv_abrade_filter"; // 磨损过滤器 ID var SORT_LIST_ID = "rv_sort_list"; // 排序面板列表 ID var SELLING_PRICE_ID = "tv_selling_price_value"; // 售卖价 ID var CATEGORY_CONTAINER_ID = "item_category_ll"; // 分类容器 ID var MAX_WAIT_MS = 5000; // 控件等待超时 var CSV_PATH = "/sdcard/AIM/Aa1.csv"; // CSV 输出路径 var successCount = 0; // 创建目录 var File = java.io.File; var parentDir = new File(CSV_PATH).getParentFile(); if (!parentDir.exists()) { parentDir.mkdirs(); } // ================ 工具函数(纯 ES5 写法) ================ function round(num, digits) { var d = digits || 0; var f = Math.pow(10, d); return Math.round(parseFloat(num) * f) / f; } function extractNumber(text) { if (text === null || text === undefined) return null; var str = String(text); var clean = str.replace(/[^\d.]/g, ''); var match = clean.match(/^\d*\.\d+$|^\d+$/); if (!match) return null; var n = parseFloat(match[0]); return isNaN(n) ? null : n; } function normalizeString(str) { if (typeof str !== 'string') { str = String(str); } return str.trim().toLowerCase() .replace(/\s+/g, ' ') .replace(/[^\w\u4e00-\u9fa5]/g, ''); } function formatDateTime() { var now = new Date(); var year = now.getFullYear(); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); function pad(n) { return n < 10 ? '0' + n : n; } return year + " " + pad(hour) + ":" + pad(minute) + ":" + pad(second); } function clickCenter(bounds) { if (!bounds) return false; click(bounds.centerX(), bounds.centerY()); sleep(800); return true; } function waitFor(query, timeout) { var start = new Date().getTime(); var t = timeout || MAX_WAIT_MS; while ((new Date().getTime() - start) < t) { try { var w = query.findOne(1000); if (w) return w; } catch (e) {} sleep(300); } return null; } // 防屏蔽:随机延迟 function randomSleep(min, max) { sleep(Math.random() * (max - min) + min); } function waitForVisible(idStr) { var start = new Date().getTime(); while ((new Date().getTime() - start) < MAX_WAIT_MS) { try { var w = id(idStr).findOne(1000); if (w && w.visibleToUser && w.visibleToUser()) { return w; } } catch (e) {} sleep(300); } return null; } function performBitmapClicks() { var cap = captureScreen(); // ←← 这一行触发了权限请求 var tem = images.read("/sdcard/脚本/小图/筛选.png"); if (cap && tem) { var p = images.findImage(cap, tem, { region: [957, 293, 242, 67], threshold: 0.9 }); if (!p) { device.setMusicVolume(7); media.playMusic("/sdcard/Music/My love 请别让爱凋落.mp3"); sleep(media.getMusicDuration() || 5000); engines.myEngine().forceStop(); } cap.recycle(); if (tem) tem.recycle(); } } // ================ 数据采集函数 ================ function getTaskName() { try { var w = id("tv_order_details_title").findOne(3000); var txt = w ? (w.text() || w.desc() || "未知") : "未知"; log("📋 名称: " + txt); return txt; } catch (e) { log("⚠️ 获取名称失败: " + e.message); return "异常"; } } function getSellingPrice() { var w = waitFor(id(SELLING_PRICE_ID), 3000); if (!w) { log("❌ 未找到售卖价控件"); return null; } var priceText = w.text(); var price = extractNumber(priceText); if (price === null) { log("❌ 解析售价失败: " + priceText); } else { log("✅ 我的售价: " + price); } return price; } function getCurrentCategoryPrice() { var list = id(CATEGORY_CONTAINER_ID).find(); for (var i = 0; i < list.size(); i++) { var c = list.get(i); try { if (c.selected && typeof c.selected === 'function' && c.selected()) { var pricing = extractPricingFrom(c); if (pricing !== null) return pricing; } } catch (e) {} } return null; } function extractPricingFrom(parent) { try { var children = parent.children(); for (var i = 0; i < children.size(); i++) { var ch = children.get(i); var text = (ch.text && ch.text()) || (ch.desc && ch.desc()) || ''; var num = extractNumber(text); if (num !== null && num > 1) return num; } } catch (e) {} return null; } function getWearLevel() { var w = waitFor(id(WEAR_FILTER_ID), 2000); return w ? (w.text() || w.desc() || "未获取") : "未获取"; } function getAbraidFilterRange() { var w = waitFor(id(WEAR_FILTER_ID).className("android.widget.TextView"), 2000); if (!w) return null; var txt = (w.text && w.text()) || (w.desc && w.desc()) || ''; txt = String(txt).trim(); var m = txt.match(/(\d+\.\d+)\s*[-~\u81F3]\s*(\d+\.\d+)/); if (!m) return null; var minW = parseFloat(m[1]), maxW = parseFloat(m[2]); if (isNaN(minW) || isNaN(maxW) || minW >= maxW) return null; return { min: minW, max: maxW }; } // ================ 滑动 & 图像识别辅助 ================ function swipeUp() { var h = device.height; var w = device.width; try { gesture(500, [w/2, h*0.7], [w/2, h*0.3]); sleep(1000); return true; } catch (e) { sleep(800); return false; } } // ================ 核心:磨损筛选逻辑 ================ // ======== 修改后的 processWearFilter ======== function processWearFilter() { log("🔧 开始处理磨损筛选..."); // Step 1: 点击【磨损区间】 if (text("磨损区间").exists()) { var widget = text("磨损区间").findOne(2000); if (widget && widget.bounds) { clickCenter(widget.bounds()); log("🖱️ 已点击【磨损区间】"); sleep(1000); } } // Step 2: 等待 rv_sort_list 加载,并点击索引1 var list = waitFor(id(SORT_LIST_ID), 5000); if (!list) { toast("❌ 未找到磨损列表"); return null; // 返回 null 表示失败 } var items = list.children(); if (items.size() <= 5) { toast("❌ 选项不足,无法点击索引1"); return null; } var target1 = items[1]; if (!clickCenter(target1.bounds())) { return null; } log("✅ 成功点击索引1"); sleep(1000); // Step 3: 循环 step=2 to 4 var initialMinWear = null; var finalMaxWear = null; var categoryPrice = null; var diff = null; for (var step = 2; step <= 4; step++) { var sellingPrice = getSellingPrice(); if (!sellingPrice) break; categoryPrice = getCurrentCategoryPrice(); if (!categoryPrice) break; diff = round(sellingPrice - categoryPrice, 2); log("📊 当前差价: " + diff.toFixed(2)); if (diff <= 5) { log("🔴 差价≤5,终止推进"); break; } if (initialMinWear === null) { var range = getAbraidFilterRange(); if (range) { initialMinWear = range.min; } } // 重新打开面板 var filter = waitFor(id(WEAR_FILTER_ID), 3000); if (!filter || !clickCenter(filter.bounds())) { break; } list = waitFor(id(SORT_LIST_ID), 4000); if (!list) break; items = list.children(); if (step >= items.size()) break; var target = items[step]; if (!clickCenter(target.bounds())) break; log("✅ 已点击第 " + step + " 档"); sleep(1000); var currentRange = getAbraidFilterRange(); if (currentRange) { finalMaxWear = currentRange.max; } } // 👇 返回完整结果对象 if (finalMaxWear !== null && diff > 5 && categoryPrice) { return { shouldRecord: true, minWear: initialMinWear || 0, maxWear: finalMaxWear, category: categoryPrice, selling: sellingPrice, diff: diff }; } else { return { shouldRecord: false, minWear: initialMinWear || 0, maxWear: finalMaxWear || 0, category: categoryPrice || 0, selling: sellingPrice || 0, diff: diff || 0 }; } } // ================ CSV 记录 ================ function writeToCSV(data) { try { var file = new File(CSV_PATH); var needsHeader = !file.exists() || file.length() === 0; var fw = new java.io.FileWriter(file, true); // 追加模式 var bw = new java.io.BufferedWriter(fw); if (needsHeader) { // 👇 更新表头:包含新字段 bw.write("任务名称,物品名称,最小磨损,最大磨损,采购价格,采购数量,定价,售价,差价,时间\n"); } // 动态计算采购价格 = 定价 + 1 var buyPrice = data.category + 1; // ←← 关键修改 var buyCount = 5; // 固定采购数量 var taskName = "5"; // 任务名称仍默认为 "5" var escapedName = data.name.replace(/"/g, '""'); // 转义引号 // 构造数据行 var lineParts = [ '"' + taskName + '"', // 任务名称 '"' + escapedName + '"', // 物品名称 data.minWear.toFixed(2), // 最小磨损 data.maxWear.toFixed(2), // 最大磨损 buyPrice.toFixed(2), // 采购价格 = 定价 + 1 buyCount, // 采购数量(固定为5) data.category.toFixed(2), // 定价 data.selling.toFixed(2), // 售价 data.diff.toFixed(2), // 差价 data.time // 时间 ]; var line = lineParts.join(",") + "\n"; bw.write(line); bw.close(); fw.close(); successCount++; toastLog("📌 已记录: " + data.name + " | 差价:" + data.diff + " | 采购价:" + buyPrice.toFixed(2)); } catch (e) { log("❌ CSV写入失败: " + e.message); } } // ================ 主循环 ================ toast("✅ 脚本启动,请进入列表页..."); var processedKeys = {}; while (true) { log("🔄 ========== 新一轮循环开始 =========="); // A: 等待列表页 if (!waitForVisible(LIST_PAGE_ID)) { log("🔙 返回中..."); back(); sleep(2000); continue; } // B: 扫描并点击符合条件的商品 var rv = id(LIST_PAGE_ID).findOne(2000); if (!rv) { log("❌ 未找到商品列表"); continue; } var found = false; var children = rv.children(); for (var i = 0; i < children.size(); i++) { var item = children.get(i); var depositWidget = item.findOne(id(DEPOSIT_ID)); if (!depositWidget) continue; var depositText = depositWidget.text(); var deposit = extractNumber(depositText); if (isNaN(deposit) || deposit <= 500) continue; var nameWidget = item.findOne(id(NAME_ID)); var rawName = (nameWidget && (nameWidget.text() || nameWidget.desc())) || "未命名-" + deposit; var key = normalizeString(rawName) + "@" + Math.round(deposit * 100); if (processedKeys[key]) { log("🔁 跳过已处理: " + rawName); continue; } log("🎯 发现目标: " + rawName + " | 在售¥" + deposit); if (item.click()) { processedKeys[key] = true; found = true; break; } else { log("❌ 点击失败"); } } if (!found) { log("📭 无新目标,上滑加载"); performBitmapClicks(); // 图像检测音乐提醒 if (swipeUp()) { sleep(1500); continue; } else { toastLog("🔚 已到底部,脚本结束"); break; } } // C: 进入详情页后执行磨损筛选 sleep(2000); var shouldRecord = processWearFilter(); if (shouldRecord) { var name = getTaskName(); var selling = getSellingPrice(); var category = getCurrentCategoryPrice(); var wear = getWearLevel(); var diff = round(selling - category, 2); if (selling && category && diff > 5) { writeToCSV({ name: name, wear: wear, category: category, selling: selling, diff: diff, time: formatDateTime() }); } } else { log("🟡 条件不满足,跳过记录"); } // 返回列表 back(); sleep(1500); }
11-11
// ======== 1. 初始化 ========== auto.waitFor(); device.keepScreenDim(); setScreenMetrics(1236, 2676); if (!requestScreenCapture()) { toast("❌ 请求截图失败"); exit(); } toast("✅ 脚本启动,请进入列表页..."); // ======== 2. 定义变量 ======== var processedKeys = {}; // 去重缓存 var LIST_PAGE_ID = "recyclerview"; // 列表 RecyclerView ID var TARGET_TEXT_ID = "min_deposit_tv"; // 押金字段 ID var MAX_WAIT_SECONDS = 20; // 等待超时时间 var CSV_FILE_PATH = "/sdcard/AIM/Aac_report.csv"; var successCount = 0; // 成功写入条数 // Java 文件系统支持 var File = java.io.File; var parentDir = new File(CSV_FILE_PATH).getParentFile(); if (!parentDir.exists()) { parentDir.mkdirs(); } console.info("📁 CSV 日志路径: " + CSV_FILE_PATH); // ======== 3. 工具函数定义(ES5 风格)======== // ✅ extractNumber —— 提取数字 function extractNumber(text) { if (typeof text === 'undefined' || text === null) return null; var clean = String(text).replace(/[^\d.]/g, ''); var match = clean.match(/^\d*\.?\d+$/); var num = match ? parseFloat(match[0]) : null; return isNaN(num) ? null : num; } // ✅ round —— 四舍五入 function round(num, digits) { if (typeof digits === 'undefined') digits = 2; var factor = Math.pow(10, digits); return Math.round(num * factor) / factor; } // ✅ normalizeString —— 字符串归一化(用于唯一键) function normalizeString(str) { if (typeof str !== 'string') str = String(str); return str.trim().toLowerCase() .replace(/\s+/g, ' ') .replace(/[^\w\u4e00-\u9fa5]/g, ''); // 移除非字母/汉字/数字字符 } // ✅ waitForListPage —— 等待列表页出现 function waitForListPage(timeout) { if (typeof timeout === 'undefined') timeout = MAX_WAIT_SECONDS * 1000; var start_time = new Date().getTime(); while ((new Date().getTime() - start_time) < timeout) { try { var widget = id(LIST_PAGE_ID).findOnce(); if (widget && widget.visibleToUser && widget.visibleToUser()) { log("✅ 成功返回列表页"); return true; } } catch (err) {} sleep(500); } log("❌ 等待列表页超时"); return false; } // ✅ getTaskName —— 获取物品名称(从详情页获取) function getTaskName() { try { var w = id("tv_order_details_title").findOne(3000); if (!w) { log("❌ 未找到标题控件"); return "未知物品"; } var txt = (w.text && w.text()) || (w.desc && w.desc()) || "未命名"; setClip(txt); // 复制到剪贴板备用 log("📋 名称: " + txt); return txt; } catch (e) { log("⚠️ 获取名称异常: " + e.message); return "异常"; } } // ✅ getSellingPrice —— 获取我的售价 function getSellingPrice() { try { var w = id("tv_selling_price_value").findOne(5000); if (!w) { log("❌ 售卖价控件不存在"); return null; } var rawText = w.text ? w.text() : ""; var price = extractNumber(rawText); if (price === null) { log("❌ 解析失败: " + rawText); } else { log("✅ 我的售价: " + price); } return price; } catch (e) { log("⚠️ 获取售价出错: " + e.message); return null; } } // ✅ getCurrentCategoryContainer —— 获取选中的分类容器 function getCurrentCategoryContainer() { var list = id("item_category_ll").className("android.view.ViewGroup").find(); for (var i = 0; i < list.size(); i++) { var c = list.get(i); try { if (c.clickable() && c.selected()) { return c; } } catch (e) {} } return null; } // ✅ extractPricingFrom —— 从分类项中提取价格 function extractPricingFrom(parent) { try { var children = parent.children(); for (var i = 0; i < children.size(); i++) { var ch = children.get(i); var t = (ch.text && ch.text()) || (ch.desc && ch.desc()) || ""; var num = extractNumber(t); if (num !== null) { return { value: num, rawText: t }; } } } catch (e) { log("⚠️ 提取定价失败: " + e.message); } return null; } // ✅ getWearLevel —— 获取磨损区间 function getWearLevel() { try { var w = id("tv_abrade_filter").findOne(2000); return w ? (w.text() || "未获取") : "未获取"; } catch (e) { return "获取失败"; } } // ✅ swipeUp —— 上滑加载更多 function swipeUp() { var h = device.height; var w = device.width; try { gesture(500, [w / 2, h * 0.7], [w / 2, h * 0.3]); return true; } catch (e) { return false; } } // ===================================== // 🔍 scanAndClickItem —— 扫描并点击符合条件的商品 // ===================================== function scanAndClickItem() { var rv = id(LIST_PAGE_ID).findOne(2000); if (!rv) { log("❌ 找不到 RecyclerView"); return false; } var items = rv.children(); var itemCount = items.size(); for (var i = 0; i < itemCount; i++) { var child = items.get(i); // 🔍 查找押金控件 var depositWidget = child.findOne(id(TARGET_TEXT_ID)); if (!depositWidget) continue; var rawText = depositWidget.text(); var deposit = extractNumber(rawText); if (isNaN(deposit) || deposit <= 500) continue; // 🔍 查找名称控件(列表页上的 name_tv) var nameWidget = child.findOne(id("name_tv")); var itemName = "未知物品"; if (nameWidget) { itemName = (nameWidget.text() || "").trim(); if (!itemName) itemName = (nameWidget.desc() || "").trim(); } if (!itemName || itemName.length === 0) itemName = "未命名-" + deposit; // ✅ 构造唯一键:归一化名称 + 押金(保留两位小数精度) var key = normalizeString(itemName) + "@" + Math.round(deposit * 100); // 检查是否已处理 if (processedKeys[key]) { log("🔁 跳过已处理项: " + itemName + " | ¥" + deposit); continue; } // ✅ 尝试点击 log("🎯 发现新目标: " + itemName + " | 押金¥" + deposit); if (child.click()) { log("✅ 成功点击 → 进入详情页"); processedKeys[key] = true; return true; // 成功点击一个就退出 } else { log("❌ 点击失败,可能被遮挡或不可见"); } } return false; // 未找到可点击项 } //时间格式化 function formatDateTime() { var now = new Date(); var year = now.getFullYear(); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); // 补零函数 function pad(n) { return n < 10 ? '0' + n : n; } // 拼接成 "2025 10:52:03" 格式(24小时制) return year + " " + pad(hour) + ":" + pad(minute) + ":" + pad(second); } // ===================================== // ✅ B阶段:图像识别点击(磨损区间 & 磨损度) // ===================================== function performBitmapClicks() { log("🖼️ B阶段:执行图像识别操作"); sleep(2000); // B1: 点击“磨损区间” if (text("磨损区间").exists()) { var cap = captureScreen(); var tem = images.read("/sdcard/脚本/小图/磨损区间.png"); if (!tem) { log("❌ 无法读取模板图片:磨损区间.png"); } else { var p = images.findImage(cap, tem, { region: [206, 987, 142, 177], threshold: 0.9 }); if (p) { click(p.x + tem.getWidth() / 2, p.y + tem.getHeight() / 2); log("🖱️ 已点击:磨损区间"); sleep(1000); } else { log("🔍 未匹配到:磨损区间"); } cap.recycle(); tem.recycle(); } } // B2: 点击“磨损度” sleep(1000); var cap1 = captureScreen(); var tem1 = images.read("/sdcard/脚本/小图/磨损度.png"); if (!tem1) { log("❌ 无法读取模板图片:磨损度.png"); } else { var p1 = images.findImage(cap1, tem1, { region: [27, 1089, 93, 184], threshold: 0.9 }); if (p1) { click(p1.x + tem1.getWidth() / 2 + 47, p1.y + tem1.getHeight() / 2 + 136); log("🖱️ 已点击:磨损度"); } else { log("🔍 未匹配到:磨损度"); } cap1.recycle(); tem1.recycle(); } } // ===================================== // ✅ C阶段:数据采集与 CSV 记录(追加写入) // ===================================== function collectAndRecordData() { log("📝 C阶段:采集数据并比较差价"); var taskName = getTaskName(); var sellingPrice = getSellingPrice(); if (!sellingPrice) { toast("⚠️ 售卖价获取失败,跳过"); return false; } var container = getCurrentCategoryContainer(); if (!container) { toast("🚫 未找到选中分类容器"); return false; } var pricing = extractPricingFrom(container); if (!pricing) { toast("⚠️ 分类中无有效定价"); return false; } var categoryPrice = pricing.value; var diff = round(Math.abs(sellingPrice - categoryPrice), 2); var wearLevel = getWearLevel(); // 当差价 > 5时才记录 if (diff > 5) { try { var file = new File(CSV_FILE_PATH); var needsHeader = !file.exists() || file.length() === 0; // ✅ 使用 FileWriter(true) 实现追加写入 var fw = new java.io.FileWriter(file, true); // true 表示 append 追加模式 var bw = new java.io.BufferedWriter(fw); // 写入表头(仅首次) if (needsHeader) { bw.write("物品名称,磨损程度,定价,售价,差价\n,时间"); log("📊 创建新 CSV 文件并写入表头"); } // 转义双引号 var escapedName = taskName.replace(/"/g, '""'); var line = [ '"' + escapedName + '"', '"' + wearLevel + '"', categoryPrice.toFixed(2), sellingPrice.toFixed(2), diff.toFixed(2), formatDateTime() ].join(",") + "\n"; bw.write(line); bw.close(); fw.close(); successCount++; toastLog( "📌 物品: " + taskName + "\n" + "🔺 差额: " + diff + "\n" + "📈 累计: " + successCount + " 条" ); } catch (e) { log("❌ 写入 CSV 失败: " + (e.message || e.toString())); } } else { log("📉 差价 ≤5,不记录。累计仍为: " + successCount + " 条"); } return true; } // ======== 4. 主循环开始 ========= while (true) { if (id("filter_iv").exists()) { let cap = captureScreen(), tem = images.read("/sdcard/脚本/小图/筛选.png"); if (!images.findImage(cap, tem, { region: [957, 293, 242, 67], threshold: 0.9 })) { device.setMusicVolume(7); media.playMusic("/sdcard/Music/My love 请别让爱凋落.mp3"); sleep(media.getMusicDuration() || 5000); engines.myEngine().forceStop(); } cap.recycle(); // 必须回收截图 if (tem) tem.recycle(); // 回收模板图 sleep(1000); } log("🔄 ========== 新一轮循环开始 =========="); // A: Wait for List Page if (!waitForListPage()) { back(); sleep(2000); continue; } // D: Scan and Click var clicked = scanAndClickItem(); if (!clicked) { log("📭 未发现新目标,执行 E 阶段:滑动加载"); if (swipeUp()) { sleep(1500); continue; } else { toastLog("🔚 已到底部,脚本结束"); break; } } // B: Bitmap-based Click performBitmapClicks(); // C: Collect & Compare collectAndRecordData(); // 返回列表 back(); log("🔙 返回列表页..."); if (!waitForListPage()) { toastLog("⏳ 返回超时,脚本终止"); break; } sleep(1000); } 这是我之前的脚本逻辑,我要把它加入到点击磨损度后,然后执行这个小循环,获取到最后的一个最大磨损度和最小磨损度,还有差价,并写入csv,要如何重新编写脚本
11-11
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值