04 RadioGroup

本文介绍了Android中RadioGroup和RadioButton的基本概念与使用方法。RadioGroup作为容器组件,用于容纳多个RadioButton实现单选功能,通过LinearLayout进行布局管理。文章详细阐述了如何在XML中定义这些组件,并展示了具体的代码示例。
 1. RadioGroup和RadioButton:

   在HTML中,<input type="radio">


java.lang.Object
    |-android.view.View
 	   |-android.view.ViewGroup
 	 	   |-android.widget.LinearLayout
 	 	 	   |-android.widget.RadioGroup

2. RadioGroup 单选钮在开发中提供了 一种多选一的操作模式。

  LinearLayout属于布局管理器,所以对于单选按钮而言肯定也要采用一定的布局管理。

3. 对于很多语言的单选钮直接定义即可,但是在android中,RadioGroup定义的只是一个
单选钮的容器,在这个容器中以后要加入多个单选项,而这个单选项就是RadioButton。


4. RadioButton:

java.lang.Object
   |-android.view.View
 	   |-android.widget.TextView
 	 	   |-android.widget.Button
 	 	 	   |-android.widget.CompoundButton
 	 	 	 	   |-android.widget.RadioButton




  <RadioGroup
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:checkedButton="@+id/male1">
        
        <RadioButton
          android:id="@+id/male1"
          android:text="@string/namemale"/>

      <RadioButton
          android:id="@+id/female1"
          android:text="@string/namefemale"/>
         
       </RadioGroup>

// src/components/UnifiedCsvMerger.jsx import React, { useState, useRef } from "react"; import Papa from "papaparse"; // MUI Imports import { Box, Typography, Button, TextField, Paper, Table, TableHead, TableRow, TableCell, TableBody, TableContainer, Radio, RadioGroup, FormControlLabel, FormControl, FormLabel, List, ListItem, ListItemText, IconButton, Divider, Alert, CircularProgress, Grid } from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; // 工具函数(保持不变) import { processDataList, generateTotalStats, filterPlayIdWithSuccessPriority } from "./utils"; const UnifiedCsvMerger = () => { const fileInputRef = useRef(null); const [selectedFiles, setSelectedFiles] = useState([]); const [mergedPreview, setMergedPreview] = useState([]); // 缓存处理后的数据 const [processedData, setProcessedData] = useState([]); const [statsSummary, setStatsSummary] = useState([]); const [statsSummaryUserImpact, setStatsSummaryUserImpact] = useState([]); const [totalRows, setTotalRows] = useState(null); const [isParsing, setIsParsing] = useState(false); const [error, setError] = useState(null); // 表单状态 const [formValues, setFormValues] = useState({ startTime: "", endTime: "", excludeErrorCodes: "-71114\n-71101\n-52208\n-1007\n-81607\n-80301\n-52405\n-1033\n-1029\n2\n-52407", targetEid: "Liveview.Video.StartPlay", }); const targetEidOptions = [ { value: "Liveview.Video.StartPlay", label: "直播" }, { value: "Playback.Video.StartPlay", label: "回放" }, ]; // 单选选项 const options = [ { value: "res", label: "明细数据 (res)" }, { value: "err_summary", label: "错误统计汇总 (err_summary)" }, { value: "err_summary_user_impact", label: "错误统计汇总 (用户感知维度)" }, ]; const [selectedOption, setSelectedOption] = useState("res"); // 添加文件 const handleAddFiles = () => { const files = fileInputRef.current?.files; if (!files || files.length === 0) return; const newFiles = Array.from(files).filter((file) => /\.(csv)$/i.test(file.name)); if (newFiles.length === 0) { alert("请选择有效的 CSV 文件"); return; } setSelectedFiles((prev) => [...prev, ...newFiles]); if (fileInputRef.current) fileInputRef.current.value = ""; }; // 删除文件 const handleRemoveFile = (index) => { setSelectedFiles((prev) => prev.filter((_, i) => i !== index)); }; // 处理表单输入 const handleInputChange = (e) => { const { name, value } = e.target; setFormValues((prev) => ({ ...prev, [name]: value, })); }; // 解析所有文件 const handleParseAll = () => { if (selectedFiles.length === 0) { setError("请先添加至少一个 CSV 文件"); return; } setIsParsing(true); setError(null); setMergedPreview([]); setTotalRows(0); const promises = selectedFiles.map( (file) => new Promise((resolve) => { const rows = []; Papa.parse(file, { worker: false, header: true, skipEmptyLines: true, transform: (value) => value.trim(), step: (result) => { rows.push(result.data); }, complete: () => resolve(rows), error: () => resolve([]), }); }) ); Promise.all(promises) .then((allRows) => { const mergedData = allRows.flat(); const config = { start_time: formValues.startTime.trim() || undefined, end_time: formValues.endTime.trim() || undefined, exclude_error_codes: formValues.excludeErrorCodes .split(/[\n,;,\s]+/) .map((code) => code.trim()) .filter((code) => code !== ""), target_eid: formValues.targetEid.trim() || "Liveview.Video.StartPlay", }; const res = processDataList(mergedData, config); const err_summary = generateTotalStats(res); const err_summary_user_impact = generateTotalStats(filterPlayIdWithSuccessPriority(res)); setProcessedData(res); setStatsSummary(err_summary); setStatsSummaryUserImpact(err_summary_user_impact); // 默认显示前100条明细 setMergedPreview(res.slice(0, 100)); setTotalRows(mergedData.length); }) .catch((err) => { const message = err instanceof Error ? err.message : "未知错误"; setError(`解析过程中发生错误: ${message}`); }) .finally(() => { setIsParsing(false); }); }; // 切换预览内容 const handleRadioChange = (e) => { const value = e.target.value; setSelectedOption(value); if (value === "res") { setMergedPreview(processedData.slice(0, 100)); } else if (value === "err_summary") { setMergedPreview(statsSummary); } else if (value === "err_summary_user_impact") { setMergedPreview(statsSummaryUserImpact); } }; return ( <Box sx={{ padding: 3 }}> <Paper elevation={3} sx={{ padding: 3, borderRadius: 2 }}> <Typography variant="h5" gutterBottom> 📁 解析多个 CSV 文件(统一格式) </Typography> {/* 添加文件 */} <Box mt={2}> <input ref={fileInputRef} type="file" accept=".csv" multiple onChange={handleAddFiles} style={{ display: "none" }} id="upload-csv" /> <label htmlFor="upload-csv"> <Button component="span" variant="contained" color="primary" disabled={isParsing} startIcon={<span>📁</span>}> 添加文件 </Button> </label> </Box> {/* 已选文件列表 */} {selectedFiles.length > 0 && ( <Box mt={3}> <Typography variant="h6">📎 已选文件 ({selectedFiles.length}):</Typography> <List dense> {selectedFiles.map((file, index) => ( <React.Fragment key={index}> <ListItem secondaryAction={ <IconButton edge="end" onClick={() => handleRemoveFile(index)} disabled={isParsing}> <DeleteIcon /> </IconButton> } > <ListItemText primary={file.name} secondary={`${(file.size / 1024).toFixed(1)} KB`} /> </ListItem> <Divider /> </React.Fragment> ))} </List> </Box> )} {/* 过滤条件表单 */} <Box mt={3}> <Typography variant="h6" gutterBottom> ⚙️ 数据处理配置 </Typography> <Grid container spacing={2}> <Grid item xs={12} sm={6}> <TextField label="开始时间(可选)" type="datetime-local" name="startTime" value={formValues.startTime} onChange={handleInputChange} fullWidth InputLabelProps={{ shrink: true }} /> </Grid> <Grid item xs={12} sm={6}> <TextField label="结束时间(可选)" type="datetime-local" name="endTime" value={formValues.endTime} onChange={handleInputChange} fullWidth InputLabelProps={{ shrink: true }} /> </Grid> <Grid item xs={12}> <TextField label="排除的错误码" name="excludeErrorCodes" value={formValues.excludeErrorCodes} onChange={handleInputChange} multiline rows={6} placeholder="每行或用逗号/空格分隔,例如:-71114 -1007" fullWidth /> </Grid> <Grid item xs={12}> <FormControl component="fieldset"> <FormLabel component="legend">目标事件类型 (target_eid)</FormLabel> <RadioGroup row name="targetEid" value={formValues.targetEid} onChange={handleInputChange}> {targetEidOptions.map((option) => ( <FormControlLabel key={option.value} value={option.value} control={<Radio size="small" />} label={option.label} /> ))} </RadioGroup> </FormControl> </Grid> </Grid> </Box> {/* 解析按钮 */} <Box mt={3}> <Button variant="contained" color="secondary" onClick={handleParseAll} disabled={isParsing || selectedFiles.length === 0} startIcon={isParsing ? <CircularProgress size={20} /> : "🚀"} size="large"> {isParsing ? "解析中..." : "解析并合并所有文件"} </Button> </Box> {/* 错误提示 */} {error && ( <Box mt={2}> <Alert severity="error">{error}</Alert> </Box> )} {/* 合并结果展示 */} {!isParsing && totalRows !== null && ( <Box mt={4}> <Typography variant="h6">请选择预览内容:</Typography> <FormControl component="fieldset" sx={{ mb: 2 }}> <RadioGroup value={selectedOption} onChange={handleRadioChange}> {options.map((option) => ( <FormControlLabel key={option.value} value={option.value} control={<Radio />} label={option.label} /> ))} </RadioGroup> </FormControl> <Typography variant="body1" color="textSecondary" mb={1}> 当前显示: <strong>{options.find((o) => o.value === selectedOption)?.label}</strong> </Typography> <Typography variant="body2" color="textSecondary" mb={2}> <strong>原始共 {totalRows.toLocaleString()} 行数据</strong>,{selectedOption === "res" ? " 展示过滤后明细数据前100条" : " 展示错误统计汇总"} </Typography> <TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 500 }}> <Table stickyHeader size="small"> <TableHead> <TableRow> {Object.keys(mergedPreview[0] || {}).map((header) => ( <TableCell key={header} sx={{ fontWeight: "bold" }}> {header} </TableCell> ))} </TableRow> </TableHead> <TableBody> {mergedPreview.length > 0 ? ( mergedPreview.map((row, idx) => ( <TableRow key={idx}> {Object.values(row).map((cell, j) => ( <TableCell key={j}>{String(cell)}</TableCell> ))} </TableRow> )) ) : ( <TableRow> <TableCell colSpan={Object.keys(mergedPreview[0] || {}).length || 1} align="center"> 暂无数据 </TableCell> </TableRow> )} </TableBody> </Table> </TableContainer> </Box> )} </Paper> </Box> ); }; export default UnifiedCsvMerger; 新增四个表格pullStreamStats err_daily_weekly_monthly err_device_cov err_device_cov_daily_weekly_monthly,还是使用之前的单选切换显示,内容都是generateTotalStats(res);
最新发布
10-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值