运行有问题或需要源码请点赞关注收藏后评论区留言~~~
手机有自带的系统相机,也有自带的系统录音机,只要在调用startActivityForResult之前指定该动作,就会自动跳转到系统的录音机界面 效果如下
当然这里最好连接真机测试 模拟机好像没有录音机 总之调试有点麻烦

代码如下
package com.example.chapter13;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.chapter13.util.DateUtil;
import com.example.chapter13.util.FileUtil;
public class AudioRecordActivity extends AppCompatActivity implements View.OnClickListener {
private final static String TAG = "AudioRecordActivity";
private int RECORDER_CODE = 1; // 录制操作的请求码
private TextView tv_audio;
private ImageView iv_audio; // 该图标充当播放按钮
private Uri mAudioUri; // 音频文件的uri路径
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_record);
tv_audio = findViewById(R.id.tv_audio);
iv_audio = findViewById(R.id.iv_audio);
findViewById(R.id.btn_recorder).setOnClickListener(this);
findViewById(R.id.iv_audio).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_recorder) {
// 下面打开系统自带的录音机
Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
registerForActivityResult(intent, RECORDER_CODE); // 跳到录音机页面
} else if (v.getId() == R.id.iv_audio) {
// 下面打开系统自带的收音机
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(mAudioUri, "audio/*"); // 类型为音频
startActivity(intent); // 跳到收音机页面
}
}
private void registerForActivityResult(Intent intent, int recorder_code) {
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode==RESULT_OK && requestCode==RECORDER_CODE){
mAudioUri = intent.getData(); // 获得录制好的音频uri
String filePath = String.format("%s/%s.mp3",
getExternalFilesDir(Environment.DIRECTORY_MUSIC), "audio_"+ DateUtil.getNowDateTime());
FileUtil.saveFileFromUri(this, mAudioUri, filePath); // 保存为临时文件
tv_audio.setText("录制完成的音频地址为:"+mAudioUri.toString());
iv_audio.setVisibility(View.VISIBLE);
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_recorder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="打开录音机"
android:textColor="@color/black"
android:textSize="17sp" />
<TextView
android:id="@+id/tv_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:textColor="@color/black"
android:textSize="17sp" />
<ImageView
android:id="@+id/iv_audio"
android:layout_width="match_parent"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:src="@drawable/play_audio"
android:visibility="gone" />
</LinearLayout>
尽管让App跳到收音机界面就能播放音频,但是通常App都不希望用户离开自身页面,何况播音本来就是一个小功能,完全可以一边播放音频一边操作界面,若想在App内部自己播音,便用到了媒体播放器MediaPlayer,并且要找到音频文件的路径。
MediaPlayer常用方法如下
1:reset 重置播放器
2:prepare 准备播放
3:start 开始播放
4:pause 暂停播放
5:stop 停止播放
6:create 创建uri指定的播放器
7:setDataSource 设置播放器数据来源的文件路径
应用场景主要有以下两个1:播放指定音频文件
2:退出页面时准备释放媒体资源

代码如下
package com.example.chapter13;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import com.example.chapter13.adapter.AudioRecyclerAdapter;
import com.example.chapter13.bean.MediaInfo;
import com.example.chapter13.util.FileUtil;
import com.example.chapter13.widget.RecyclerExtras;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class AudioPlayActivity extends AppCompatActivity implements RecyclerExtras.OnItemClickListener {
private final static String TAG = "AudioPlayActivity";
private RecyclerView rv_audio; // 音频列表的循环视图
private List<MediaInfo> mAudioList = new ArrayList<MediaInfo>(); // 音频列表
private Uri mAudioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; // 音频库的Uri
private String[] mAudioColumn = new String[]{ // 媒体库的字段名称数组
MediaStore.Audio.Media._ID, // 编号
MediaStore.Audio.Media.TITLE, // 标题
MediaStore.Audio.Media.DURATION, // 播放时长
MediaStore.Audio.Media.SIZE, // 文件大小
MediaStore.Audio.Media.DATA}; // 文件路径
private AudioRecyclerAdapter mAdapter; // 音频列表的适配器
private MediaPlayer mMediaPlayer = new MediaPlayer(); // 媒体播放器
private Timer mTimer = new Timer(); // 计时器
private int mLastPosition = -1; // 上次播放的音频序号
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_play);
rv_audio = findViewById(R.id.rv_audio);
loadAudioList(); // 加载音频列表
showAudioList(); // 显示音频列表
}
// 加载音频列表
private void loadAudioList() {
mAudioList.clear(); // 清空音频列表
// 通过内容解析器查询音频库,并返回结果集的游标。记录结果按照修改时间降序返回
Cursor cursor = getContentResolver().query(mAudioUri, mAudioColumn,
null, null, "date_modified desc");
if (cursor != null) {
// 下面遍历结果集,并逐个添加到音频列表。简单起见只挑选前十个音频
for (int i=0; i<10 && cursor.moveToNext(); i++) {
MediaInfo audio = new MediaInfo(); // 创建一个音频信息对象
audio.setId(cursor.getLong(0)); // 设置音频编号
audio.setTitle(cursor.getString(1)); // 设置音频标题
audio.setDuration(cursor.getInt(2)); // 设置音频时长
audio.setSize(cursor.getLong(3)); // 设置音频大小
audio.setPath(cursor.getString(4)); // 设置音频路径
Log.d(TAG, audio.getTitle() + " " + audio.getDuration() + " " + audio.getSize() + " " + audio.getPath());
if (!FileUtil.checkFileUri(this, audio.getPath())) {
i--;
continue;
}
mAudioList.add(audio); // 添加至音频列表
}
cursor.close(); // 关闭数据库游标
}
}
// 显示音频列表
private void showAudioList() {
// 创建一个水平方向的线性布局管理器
LinearLayoutManager manager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
rv_audio.setLayoutManager(manager); // 设置循环视图的布局管理器
mAdapter = new AudioRecyclerAdapter(this, mAudioList); // 创建音频列表的线性适配器
mAdapter.setOnItemClickListener(this); // 设置线性列表的点击监听器
rv_audio.setAdapter(mAdapter); // 设置循环视图的列表适配器
}
@Override
protected void onDestroy() {
super.onDestroy();
mTimer.cancel(); // 取消计时器
if (mMediaPlayer.isPlaying()) { // 是否正在播放
mMediaPlayer.stop(); // 结束播放
}
mMediaPlayer.release(); // 释放媒体播放器
}
@Override
public void onItemClick(View view, final int position) {
if (mLastPosition!=-1 && mLastPosition!=position) {
MediaInfo last_audio = mAudioList.get(mLastPosition);
last_audio.setProgress(-1); // 当前进度设为-1表示没在播放
mAudioList.set(mLastPosition, last_audio);
mAdapter.notifyItemChanged(mLastPosition); // 刷新此处的列表项
}
mLastPosition = position;
final MediaInfo audio = mAudioList.get(position);
Log.d(TAG, "onItemClick position="+position+",audio.getPath()="+audio.getPath());
mTimer.cancel(); // 取消计时器
mMediaPlayer.reset(); // 重置媒体播放器
// mMediaPlayer.setVolume(0.5f, 0.5f); // 设置音量,可选
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置音频流的类型为音乐
try {
mMediaPlayer.setDataSource(audio.getPath()); // 设置媒体数据的文件路径
mMediaPlayer.prepare(); // 媒体播放器准备就绪
mMediaPlayer.start(); // 媒体播放器开始播放
} catch (Exception e) {
e.printStackTrace();
}
mTimer = new Timer(); // 创建一个计时器
mTimer.schedule(new TimerTask() {
@Override
public void run() {
audio.setProgress(mMediaPlayer.getCurrentPosition()); // 设置进度条的当前进度
mAudioList.set(position, audio);
// 界面刷新操作需要在主线程执行,故而向处理器发送消息,由处理器在主线程更新界面
mHandler.sendEmptyMessage(position);
Log.d(TAG, "CurrentPosition="+mMediaPlayer.getCurrentPosition()+",position="+position);
}
}, 0, 1000); // 计时器每隔一秒就更新进度条上的播放进度
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mAdapter.notifyItemChanged(msg.what); // 刷新此处的列表项
}
};
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="点击音频列表开始播放"
android:textColor="@color/black"
android:textSize="17sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="2dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="left|center"
android:text="音频名称"
android:textColor="@color/black"
android:textSize="15sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="right|center"
android:text="总时长"
android:textColor="@color/black"
android:textSize="15sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_audio"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
Android提供了媒体录制器MediaRecoder,它既能录制音频也能录制视频,它可以在当前页面直接录音,而不必跳到系统自带的录音机界面。常用方法与MediaPlayer基本一致
它的基本应用场景也只有两个1:开始录制媒体文件
2:停止录制媒体文件
其中录制音频的场景需要经历以下步骤
重置录制器-设置媒体文件的路径-准备录制-开始录制
效果如下 可以自行选择编码格式和录制时长

代码如下
package com.example.chapter13;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.chapter13.util.MediaUtil;
import java.util.Timer;
import java.util.TimerTask;
public class MediaRecorderActivity extends AppCompatActivity implements View.OnClickListener, MediaRecorder.OnInfoListener {
private static final String TAG = "MediaRecorderActivity";
private Button btn_record;
private LinearLayout ll_progress;
private ProgressBar pb_record; // 声明一个进度条对象
private TextView tv_progress;
private ImageView iv_audio; // 该图标充当播放按钮
private MediaRecorder mMediaRecorder = new MediaRecorder(); // 媒体录制器
private boolean isRecording = false; // 是否正在录制
private int mAudioEncoder; // 音频编码
private int mOutputFormat; // 输出格式
private int mDuration; // 录制时长
private String mRecordFilePath; // 录制文件的保存路径
private Timer mTimer = new Timer(); // 计时器
private int mTimeCount; // 时间计数
private MediaPlayer mMediaPlayer = new MediaPlayer(); // 媒体播放器
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_recorder);
btn_record = findViewById(R.id.btn_record);
ll_progress = findViewById(R.id.ll_progress);
pb_record = findViewById(R.id.pb_record);
tv_progress = findViewById(R.id.tv_progress);
iv_audio = findViewById(R.id.iv_audio);
btn_record.setOnClickListener(this);
iv_audio.setOnClickListener(this);
initEncoderSpinner(); // 初始化音频编码的下拉框
initFormatSpinner(); // 初始化输出格式的下拉框
initDurationSpinner(); // 初始化录制时长的下拉框
}
// 初始化音频编码的下拉框
private void initEncoderSpinner() {
ArrayAdapter<String> encoderAdapter = new ArrayAdapter<String>(this,
R.layout.item_select, encoderDescArray);
Spinner sp_encoder = findViewById(R.id.sp_encoder);
sp_encoder.setPrompt("请选择音频编码"); // 设置下拉框的标题
sp_encoder.setAdapter(encoderAdapter); // 设置下拉框的数组适配器
// 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法
sp_encoder.setOnItemSelectedListener(new EncoderSelectedListener());
sp_encoder.setSelection(0); // 设置下拉框默认显示第一项
}
private String[] encoderDescArray = {
"默认编码",
"窄带编码",
"宽带编码",
"低复杂度的高级编码",
"高效率的高级编码",
"增强型低延时的高级编码"
};
private int[] encoderArray = {
MediaRecorder.AudioEncoder.DEFAULT,
MediaRecorder.AudioEncoder.AMR_NB,
MediaRecorder.AudioEncoder.AMR_WB,
MediaRecorder.AudioEncoder.AAC,
MediaRecorder.AudioEncoder.HE_AAC,
MediaRecorder.AudioEncoder.AAC_ELD
};
class EncoderSelectedListener implements AdapterView.OnItemSelectedListener {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
mAudioEncoder = encoderArray[arg2];
}
public void onNothingSelected(AdapterView<?> arg0) {}
}
// 初始化输出格式的下拉框
private void initFormatSpinner() {
ArrayAdapter<String> formatAdapter = new ArrayAdapter<String>(this,
R.layout.item_select, formatDescArray);
Spinner sp_format = findViewById(R.id.sp_format);
sp_format.setPrompt("请选择输出格式"); // 设置下拉框的标题
sp_format.setAdapter(formatAdapter); // 设置下拉框的数组适配器
sp_format.setSelection(0); // 设置下拉框默认显示第一项
// 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法
sp_format.setOnItemSelectedListener(new FormatSelectedListener());
}
private String[] formatDescArray = {
"默认格式",
"窄带格式",
"宽带格式",
"高级的音频传输流格式"
};
private int[] formatArray = {
MediaRecorder.OutputFormat.DEFAULT,
MediaRecorder.OutputFormat.AMR_NB,
MediaRecorder.OutputFormat.AMR_WB,
MediaRecorder.OutputFormat.AAC_ADTS
};
class FormatSelectedListener implements AdapterView.OnItemSelectedListener {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
mOutputFormat = formatArray[arg2];
}
public void onNothingSelected(AdapterView<?> arg0) {}
}
// 初始化录制时长的下拉框
private void initDurationSpinner() {
ArrayAdapter<String> durationAdapter = new ArrayAdapter<String>(this,
R.layout.item_select, durationDescArray);
Spinner sp_duration = findViewById(R.id.sp_duration);
sp_duration.setPrompt("请选择录制时长"); // 设置下拉框的标题
sp_duration.setAdapter(durationAdapter); // 设置下拉框的数组适配器
sp_duration.setSelection(0); // 设置下拉框默认显示第一项
// 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法
sp_duration.setOnItemSelectedListener(new DurationSelectedListener());
}
private String[] durationDescArray = {"5秒", "10秒", "20秒", "30秒", "60秒"};
private int[] durationArray = {5, 10, 20, 30, 60};
class DurationSelectedListener implements AdapterView.OnItemSelectedListener {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
mDuration = durationArray[arg2];
}
public void onNothingSelected(AdapterView<?> arg0) {}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_record) {
if (!isRecording) { // 未在录音
startRecord(); // 开始录音
} else { // 正在录音
stopRecord(); // 停止录音
}
} else if (v.getId() == R.id.iv_audio) {
mMediaPlayer.reset(); // 重置媒体播放器
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置音频流的类型为音乐
try {
mMediaPlayer.setDataSource(mRecordFilePath); // 设置媒体数据的文件路径
mMediaPlayer.prepare(); // 媒体播放器准备就绪
mMediaPlayer.start(); // 媒体播放器开始播放
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 开始录音
private void startRecord() {
Log.d(TAG, "startRecord mAudioEncoder="+mAudioEncoder+", mOutputFormat="+mOutputFormat+", mDuration="+mDuration);
ll_progress.setVisibility(View.VISIBLE);
isRecording = !isRecording;
btn_record.setText("停止录制");
pb_record.setMax(mDuration); // 设置进度条的最大值
mTimeCount = 0; // 时间计数清零
mTimer = new Timer(); // 创建一个计时器
mTimer.schedule(new TimerTask() {
@Override
public void run() {
pb_record.setProgress(mTimeCount); // 设置进度条的当前进度
tv_progress.setText(MediaUtil.formatDuration(mTimeCount*1000));
mTimeCount++;
}
}, 0, 1000); // 计时器每隔一秒就更新进度条上的录制进度
// 获取本次录制的媒体文件路径
mRecordFilePath = MediaUtil.getRecordFilePath(this, "RecordAudio", ".amr");
// 下面是媒体录制器的处理代码
mMediaRecorder.reset(); // 重置媒体录制器
mMediaRecorder.setOnInfoListener(this); // 设置媒体录制器的信息监听器
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频源为麦克风
mMediaRecorder.setOutputFormat(mOutputFormat); // 设置媒体的输出格式。该方法要先于setAudioEncoder调用
mMediaRecorder.setAudioEncoder(mAudioEncoder); // 设置媒体的音频编码器
// mMediaRecorder.setAudioSamplingRate(8); // 设置媒体的音频采样率。可选
// mMediaRecorder.setAudioChannels(2); // 设置媒体的音频声道数。可选
// mMediaRecorder.setAudioEncodingBitRate(1024); // 设置音频每秒录制的字节数。可选
mMediaRecorder.setMaxDuration(mDuration * 1000); // 设置媒体的最大录制时长
// mMediaRecorder.setMaxFileSize(1024*1024*10); // 设置媒体的最大文件大小
// setMaxFileSize与setMaxDuration设置其一即可
mMediaRecorder.setOutputFile(mRecordFilePath); // 设置媒体文件的保存路径
try {
mMediaRecorder.prepare(); // 媒体录制器准备就绪
mMediaRecorder.start(); // 媒体录制器开始录制
} catch (Exception e) {
e.printStackTrace();
}
}
// 停止录音
private void stopRecord() {
isRecording = !isRecording;
btn_record.setText("开始录制");
mTimer.cancel(); // 取消定时器
mMediaRecorder.stop(); // 媒体录制器停止录制
}
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
// 录制达到最大时长,或者达到文件大小限制,都停止录制
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
|| what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
stopRecord(); // 停止录音
iv_audio.setVisibility(View.VISIBLE);
Toast.makeText(this, "已结束录制,音频文件路径为"+mRecordFilePath, Toast.LENGTH_LONG).show();
}
}
@Override
protected void onStop() {
super.onStop();
if (!TextUtils.isEmpty(mRecordFilePath) && isRecording) {
stopRecord(); // 停止录音
}
if (mMediaPlayer.isPlaying()) { // 如果正在播放
mMediaPlayer.stop(); // 停止播放
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mMediaRecorder.release(); // 释放媒体录制器
mMediaPlayer.release(); // 释放媒体播放器
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingLeft="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="音频编码:"
android:textColor="@color/black"
android:textSize="17sp" />
<Spinner
android:id="@+id/sp_encoder"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="left|center"
android:spinnerMode="dialog" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingLeft="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="输出格式:"
android:textColor="@color/black"
android:textSize="17sp" />
<Spinner
android:id="@+id/sp_format"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="left|center"
android:spinnerMode="dialog" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingLeft="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="录制时长:"
android:textColor="@color/black"
android:textSize="17sp" />
<Spinner
android:id="@+id/sp_duration"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="left|center"
android:spinnerMode="dialog" />
</LinearLayout>
<Button
android:id="@+id/btn_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始录音"
android:textColor="@color/black"
android:textSize="17sp" />
<LinearLayout
android:id="@+id/ll_progress"
android:layout_width="match_parent"
android:layout_height="30dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:orientation="horizontal"
android:visibility="gone">
<ProgressBar
android:id="@+id/pb_record"
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>
<ImageView
android:id="@+id/iv_audio"
android:layout_width="match_parent"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:src="@drawable/play_audio"
android:visibility="gone" />
</LinearLayout>
创作不易 觉得有帮助请点赞关注收藏~~~
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po