android放微信@功能,Android仿微信语音消息的录制和播放功能

一、简述

效果:

e968db4c04aad639a97a28973d07bb5a.gif

实现功能:

长按Button时改变Button显示文字,弹出Dialog(动态更新音量),动态生成录音文件,开始录音;

监听手指动作,规定区域。录音状态下手指划出规定区域取消录音,删除生成的录音文件;

监听手指动作。当手指抬起时,判断是否开始录音,录音时长是否过短,符合条件则提示录音时长过短;正常结束时通过回调返回该次录音的文件路径和时长。

4.点击录音列表的item时,播放动画,播放对应的音频文件。

主要用到4个核心类:

自定义录音按钮(AudioRecordButton);

弹框管理类(DialogManager);

录音管理类(AudioManager)。

1.AudioRecordButton状态:

1.STATE_NORMAL:普通状态

2.STATE_RECORDING:录音中

3.STATE_CANCEL:取消录音

2.DialogManager状态:

1.RECORDING:录音中

2.WANT_TO_CANCEL:取消录音

3.TOO_SHORT:录音时间太短

3.AudioManager:

1.prepare():准备状态

2.cancel():取消录音

3.release():正常结束录音

4.getVoiceLevel():获取音量

核心逻辑:

自定义Button,重写onTouchEvent()方法。

伪代码:

class AudioRecorderButton{

onTouchEvent(){

DOWN:

changeButtonState(STATE_RECORDING);

| DialogManager.showDialog(RECORDING)

触发LongClick事件(AudioManager.prepare() --> end prepared --> | );

| getVoiceLevel();//开启一个线程,更新Dialog上的音量等级

MOVE:

if(wantCancel(x,y)){

DialogManager.showDialog(WANT_TO_CANCEL);更新Dialog

changeButtonState(STATE_WANT_TO_CANCEL);更新Button状态

}else{

DialogManager.showDialog(WANT_TO_CANCEL);

changeButtonState(STATE_RECORDING);

}

UP:

if(wantCancel == curState){//当前状态是想取消状态

AudioManager.cancel();

}

if(STATE_RECORDING = curState){

if(tooShort){//判断录制时长,如果录制时间过短

DialogManager.showDialog(TOO_SHORT);

}

AudioManager.release();

callbackActivity(url,time);//(当前录音文件路径,时长)

}

}

}

二、MediaManager封装

简述:使用MediaPlayer播放录制好的音频文件,要注意MediaPlayer资源的释放。

代码:

import android.media.*;

import java.io.IOException;

/**

* 播放管理类

*/

public class MediaManager {

private static MediaPlayer mMediaPlayer;

private static boolean isPause;

public static void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener) {

if (mMediaPlayer == null) {

mMediaPlayer = new MediaPlayer();

mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

@Override

public boolean onError(MediaPlayer mp, int what, int extra) {

mMediaPlayer.reset();

return false;

}

});

} else {

mMediaPlayer.reset();

}

try {

mMediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC);

mMediaPlayer.setOnCompletionListener(onCompletionListener);

mMediaPlayer.setDataSource(filePath);

mMediaPlayer.prepare();

mMediaPlayer.start();

} catch (IOException e) {

e.printStackTrace();

}

}

public static void pause(){

if(mMediaPlayer != null && mMediaPlayer.isPlaying()){

mMediaPlayer.pause();

isPause = true;

}

}

public static void resume(){

if(mMediaPlayer != null && isPause){

mMediaPlayer.start();

isPause = false;

}

}

public static void release(){

if(mMediaPlayer != null){

mMediaPlayer.release();

mMediaPlayer = null;

}

}

}

三、DialogManager封装

封装了6个方法:

1. showRecordingDialog():用来设置Diaog布局,拿到控件的引用,显示Dialog。

2. recording():更改Dialog状态为录音中状态。

3. wantToCancel():更改Dialog状态为想要取消状态。

4. tooShort():更改Dialog状态为录音时长过短状态。

5. dismissDialog():移除Dialog。

6. updateVoiceLevel():用来更新音量图片。

代码:

import android.app.Dialog;

import android.content.Context;

import android.view.LayoutInflater;

import android.view.View;

import android.widget.ImageView;

import android.widget.TextView;

import com.tiddlerliu.wxrecorder.R;

/**

* Dialog管理类

*/

public class DialogManager {

private Dialog mDialog;

private ImageView mIcon;

private ImageView mVoice;

private TextView mLabel;

private Context mContext;

public DialogManager(Context context) {

mContext = context;

}

/**

* 显示Dialog

*/

public void showRecordingDialog(){

//将布局应用于Dialog

mDialog = new Dialog(mContext, R.style.Theme_AudioDialog);

LayoutInflater inflater = LayoutInflater.from(mContext);

View view = inflater.inflate(R.layout.dialog_recorder,null);

mDialog.setContentView(view);

//成员控件赋值

mIcon = (ImageView) mDialog.findViewById(R.id.recorder_dialog_icon);

mVoice = (ImageView) mDialog.findViewById(R.id.recorder_dialog_voice);

mLabel = (TextView) mDialog.findViewById(R.id.recorder_dialog_label);

mDialog.show();

}

public void recording(){

if(mDialog != null && mDialog.isShowing()){

mIcon.setVisibility(View.VISIBLE);

mVoice.setVisibility(View.VISIBLE);

mLabel.setVisibility(View.VISIBLE);

mIcon.setImageResource(R.mipmap.recorder);

mLabel.setText("手指上滑,取消发送");

}

}

public void wantToCancel(){

if(mDialog != null && mDialog.isShowing()){

mIcon.setVisibility(View.VISIBLE);

mVoice.setVisibility(View.GONE);

mLabel.setVisibility(View.VISIBLE);

mIcon.setImageResource(R.mipmap.cancel);

mLabel.setText("松开手指,取消发送");

}

}

public void tooShort(){

if(mDialog != null && mDialog.isShowing()){

mIcon.setVisibility(View.VISIBLE);

mVoice.setVisibility(View.GONE);

mLabel.setVisibility(View.VISIBLE);

mIcon.setImageResource(R.mipmap.voice_to_short);

mLabel.setText("录音时间过短");

}

}

public void dismissDialog(){

if(mDialog != null && mDialog.isShowing()){

mDialog.dismiss();

mDialog = null;

}

}

/**

* 通过level更新音量资源图片

* @param level

*/

public void updateVoiceLevel(int level){

if(mDialog != null && mDialog.isShowing()){

int resId = mContext.getResources().getIdentifier("v"+level,"mipmap",mContext.getPackageName());

mVoice.setImageResource(resId);

}

}

}

四、AudioManager封装

4.1 添加必要权限

4.2 代码

import android.media.MediaRecorder;

import java.io.File;

import java.io.IOException;

import java.util.UUID;

/**

* 录音管理类

*/

public class AudioManager {

private String mDir;//文件夹名称

private MediaRecorder mMediaRecorder;

private String mCurrentFilePath;//文件储存路径

private static AudioManager mInstance;

//表明MediaRecorder是否进入prepare状态(状态为true才能调用stop和release方法)

private boolean isPrepared;

public AudioManager(String dir) {

mDir = dir;

}

public String getCurrentFilePath() {

return mCurrentFilePath;

}

/**

* 准备完毕接口

*/

public interface AudioStateListener{

void wellPrepared();

}

public AudioStateListener mListener;

public void setOnAudioStateListener(AudioStateListener listener){

mListener = listener;

}

/**

* 单例

* @return AudioManager

*/

public static AudioManager getInstance(String dir){

if (mInstance == null){

synchronized (AudioManager.class){

if(mInstance == null){

mInstance = new AudioManager(dir);

}

}

}

return mInstance;

}

/**

* 准备

*/

public void prepareAudio() {

try {

isPrepared = false;

File dir = new File(mDir);//创建文件夹

if (!dir.exists()) {

dir.mkdirs();

}

String fileName = generateFileName();//随机生成文件名

File file = new File(dir, fileName);//创建文件

mCurrentFilePath = file.getAbsolutePath();

mMediaRecorder = new MediaRecorder();

mMediaRecorder.setOutputFile(file.getAbsolutePath());//设置输出文件

mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置麦克风为音频源

mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);//设置音频格式

mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);//设置音频编码

mMediaRecorder.prepare();

mMediaRecorder.start();

//准备结束

isPrepared = true;

if (mListener != null){

mListener.wellPrepared();

}

} catch (IOException e) {

e.printStackTrace();

}

}

/**

* 随机生成文件的名称

* @return

*/

private String generateFileName() {

return UUID.randomUUID().toString()+".amr";

}

/**

* 获取音量等级

*/

public int getVoiceLevel(int maxLevel) {

if (isPrepared) {

try {

//mMediaRecorder.getMaxAmplitude() 范围:1-32767

return maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;//最大值 * [0,1)+ 1

} catch (Exception e) {

}

}

return 1;

}

/**

* 重置

*/

public void release(){

if(mMediaRecorder != null){

mMediaRecorder.stop();

mMediaRecorder.release();

mMediaRecorder = null;

}

}

/**

* 取消

*/

public void cancel(){

release();

//删除产生的文件

if(mCurrentFilePath != null){

File file = new File(mCurrentFilePath);

file.delete();

mCurrentFilePath = null;

}

}

}

五、AudioRecordButton封装

import android.annotation.SuppressLint;

import android.content.Context;

import android.os.Environment;

import android.os.Handler;

import android.os.Message;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.View;

import android.widget.Button;

import com.tiddlerliu.wxrecorder.R;

/**

* 自定义Button

*/

@SuppressLint("AppCompatCustomView")

public class AudioRecordButton extends Button implements AudioManager.AudioStateListener{

private static final int STATE_NORMAL = 1;//默认状态

private static final int STATE_RECORDING = 2;//录音状态

private static final int STATE_WANT_CANCEL = 3;//想取消状态

private static final int DISTANCE_Y_CANCEL = 50;//定义上滑取消距离

private int mCurState = STATE_NORMAL;//记录当前状态

private boolean isRecording = false;//是否在录音状态

private DialogManager mDialogManager;

private AudioManager mAudioManager;

private float mTime;//记录录音时长

private boolean mReady;//是否触发OnLongClick事件

private boolean isComplete = true;//是否已经完成

public AudioRecordButton(Context context) {

this(context,null);

}

public AudioRecordButton(Context context, AttributeSet attrs) {

super(context, attrs);

mDialogManager = new DialogManager(getContext());

String dir = Environment.getExternalStorageDirectory()+"/TiddlerLiu/recorder/audios";//最好判断SD卡是否存在可读

mAudioManager = AudioManager.getInstance(dir);

mAudioManager.setOnAudioStateListener(this);

setOnLongClickListener(new OnLongClickListener() {

@Override

public boolean onLongClick(View v) {

mReady = true;

mAudioManager.prepareAudio();

return false;

}

});

}

/**

* 录音完成后的回调

*/

public interface AudioFinishRecorderListener {

void onFinish(float seconds,String filePath);

}

private AudioFinishRecorderListener mAudioFinishRecorderListener;

public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener){

mAudioFinishRecorderListener = listener;

}

private static final int MSG_AUDIO_PREPARED = 0x110;

private static final int MSG_VOICE_CHANGED = 0x111;

private static final int MSG_DIALOG_DISMISS = 0x112;

private static final int MSG_AUDIO_COMPLETE = 0x113;//达到最大时长,自动完成

/**

* 获取音量大小

*/

private Runnable mGetVoiceLevelRunnable = new Runnable() {

@Override

public void run() {

while (isRecording){

try {

Thread.sleep(100);

mTime += 0.1f;

if(mTime >= 60f){//60s自动触发完成录制

mHandler.sendEmptyMessage(MSG_AUDIO_COMPLETE);

}

mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

};

private Handler mHandler = new Handler(){

@Override

public void handleMessage(Message msg) {

switch (msg.what){

case MSG_AUDIO_PREPARED:

//显示应该在audio end prepared以后

mDialogManager.showRecordingDialog();

isRecording = true;

isComplete = false;

new Thread(mGetVoiceLevelRunnable).start();

break;

case MSG_VOICE_CHANGED:

mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7));

break;

case MSG_DIALOG_DISMISS:

mDialogManager.dismissDialog();

break;

case MSG_AUDIO_COMPLETE:

complete();

reset();

break;

default:

break;

}

}

};

@Override

public void wellPrepared() {

mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

int action = event.getAction();

int x = (int) event.getX();

int y = (int) event.getY();

switch (action){

case MotionEvent.ACTION_DOWN:

changeState(STATE_RECORDING);

break;

case MotionEvent.ACTION_MOVE:

if(isRecording){

//根据(x,y)坐标,判断是否想要取消

if (wantToCancel(x,y)){

changeState(STATE_WANT_CANCEL);

}else{

changeState(STATE_RECORDING);

}

}

break;

case MotionEvent.ACTION_UP:

if(!isComplete){//没有执行超时自动完成逻辑

if (!mReady) {//还未触发OnLongClick事件

reset();

return super.onTouchEvent(event);

}

if (!isRecording || mTime < 0.6f) {//还未开始录音 或者 录制时长过短

mDialogManager.tooShort();

mAudioManager.cancel();

mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1300);//1.3秒后关闭对话框

} else if (mCurState == STATE_RECORDING) {//正常录制结束

complete();

} else if (mCurState == STATE_WANT_CANCEL) {//想要取消状态

mDialogManager.dismissDialog();

mAudioManager.cancel();

}

reset();

}

break;

}

return super.onTouchEvent(event);

}

/**

* 正常录制结束

*/

private void complete() {

mDialogManager.dismissDialog();

mAudioManager.release();

if(mAudioFinishRecorderListener != null && !isComplete){

mAudioFinishRecorderListener.onFinish(mTime,mAudioManager.getCurrentFilePath());

}

}

/**

* 恢复状态和标志位

*/

private void reset() {

isRecording = false;

mReady = false;

mTime = 0;

isComplete = true;

changeState(STATE_NORMAL);

}

/**

* 根据(x,y)坐标,判断是否想要取消

* @param x

* @param y

* @return

*/

private boolean wantToCancel(int x, int y) {

if(x < 0 || x > getWidth()){//手指移出button范围

return true;

}

if(y < - DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL){//手指移出Y轴设定范围

return true;

}

return false;

}

/**

* 改变状态

* @param state

*/

private void changeState(int state) {

if(mCurState != state){

mCurState = state;

switch (state){

case STATE_NORMAL:

setBackgroundResource(R.drawable.btn_recorder_normal);

setText(R.string.str_recorder_normal);

break;

case STATE_RECORDING:

setBackgroundResource(R.drawable.btn_recorder_recording);

setText(R.string.str_recorder_recording);

if(isRecording){

mDialogManager.recording();

}

break;

case STATE_WANT_CANCEL:

setBackgroundResource(R.drawable.btn_recorder_recording);

setText(R.string.str_recorder_want_cancel);

mDialogManager.wantToCancel();

break;

default:

break;

}

}

}

}

六、 主界面实现

6.1 adapter

import android.content.Context;

import android.support.annotation.NonNull;

import android.support.annotation.Nullable;

import android.util.DisplayMetrics;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.view.WindowManager;

import android.widget.ArrayAdapter;

import android.widget.TextView;

import com.tiddlerliu.wxrecorder.R;

import com.tiddlerliu.wxrecorder.model.Recorder;

import java.util.List;

public class RecorderAdapter extends ArrayAdapter{

private int mMinItemWidth;

private int mMaxItemWidth;

private LayoutInflater mInflater;

public RecorderAdapter(@NonNull Context context, List datas) {

super(context, -1 ,datas);

mInflater = LayoutInflater.from(context);

//获取屏幕参数

WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

DisplayMetrics outMetrics = new DisplayMetrics();

wm.getDefaultDisplay().getMetrics(outMetrics);

//设置最小宽度和最大宽度

mMinItemWidth = (int) (outMetrics.widthPixels * 0.16f);

mMaxItemWidth = (int) (outMetrics.widthPixels * 0.64f);

}

@NonNull

@Override

public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {

ViewHolder holder = null;

if(convertView == null){

convertView = mInflater.inflate(R.layout.item_recorder,parent,false);

holder = new ViewHolder();

holder.seconds = (TextView) convertView.findViewById(R.id.item_recorder_time);

holder.length = convertView.findViewById(R.id.item_recorder_length);

convertView.setTag(holder);

}else {

holder = (ViewHolder) convertView.getTag();

}

//设置时长

holder.seconds.setText(Math.round(getItem(position).getTime())+ "\"");

//根据时长按比例设置时长

ViewGroup.LayoutParams lp = holder.length.getLayoutParams();

lp.width = (int) (mMinItemWidth + (mMaxItemWidth/60f * getItem(position).getTime()));

return convertView;

}

private class ViewHolder{

TextView seconds;

View length;

}

}

6.2 activity

import android.graphics.drawable.AnimationDrawable;

import android.media.MediaPlayer;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.view.View;

import android.widget.AdapterView;

import android.widget.ArrayAdapter;

import android.widget.ListView;

import com.tiddlerliu.wxrecorder.CustomView.AudioRecordButton;

import com.tiddlerliu.wxrecorder.CustomView.MediaManager;

import com.tiddlerliu.wxrecorder.adapter.RecorderAdapter;

import com.tiddlerliu.wxrecorder.model.Recorder;

import java.util.ArrayList;

import java.util.List;

public class MainActivity extends AppCompatActivity {

private ListView mListView;

private AudioRecordButton mAudioRecordButton;

private ArrayAdapter mAdapter ;

private List mDatas = new ArrayList<>();

private View mAnimView;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mListView = (ListView) findViewById(R.id.recorder_list);

mAudioRecordButton = (AudioRecordButton) findViewById(R.id.recorder_button);

mAudioRecordButton.setAudioFinishRecorderListener(new AudioRecordButton.AudioFinishRecorderListener() {

@Override

public void onFinish(float seconds, String filePath) {

Recorder recorder = new Recorder(seconds,filePath);

mDatas.add(recorder);

mAdapter.notifyDataSetChanged();

mListView.setSelection(mDatas.size()-1);

}

});

mAdapter = new RecorderAdapter(this,mDatas);

mListView.setAdapter(mAdapter);

mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

@Override

public void onItemClick(AdapterView> parent, View view, int position, long id) {

if(mAnimView != null){

mAnimView.setBackgroundResource(R.mipmap.adj);

mAnimView = null;

}

//播放动画

mAnimView = view.findViewById(R.id.item_recorder_anim);

mAnimView.setBackgroundResource(R.drawable.play_ainm);

AnimationDrawable anim = (AnimationDrawable) mAnimView.getBackground();

anim.start();

//播放音频

MediaManager.playSound(mDatas.get(position).getFilePath(), new MediaPlayer.OnCompletionListener() {

@Override

public void onCompletion(MediaPlayer mp) {

mAnimView.setBackgroundResource(R.mipmap.adj);

}

});

}

});

}

@Override

protected void onPause() {

super.onPause();

MediaManager.pause();

}

@Override

protected void onResume() {

super.onResume();

MediaManager.resume();

}

@Override

protected void onDestroy() {

super.onDestroy();

MediaManager.release();

}

}

总结

以上所述是小编给大家介绍的Android仿微信语音消息的录制和播放功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/543018.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sap中泰国有预扣税设置吗_泰国的绘图标志| Python中的图像处理

sap中泰国有预扣税设置吗A colored image can be represented as a 3 order matrix. The first order is for the rows, the second order is for the columns and the third order is for specifying the color of the corresponding pixel. Here we use the BGR color format…

Attach Volume 操作(Part II) - 每天5分钟玩转 OpenStack(54)

上一节我们讨论了 attach volume 操作中 cinder-api 的工作&#xff0c;本节讨论 cinder-volume 和 nova-compute 如何将 volume attach 到 Instance。 cinder-volume 初始化 volume 的连接 cinder-volume 接收到 initialize_connection 消息后&#xff0c;会通过 tgt 创建 ta…

FMDB的介绍

2019独角兽企业重金招聘Python工程师标准>>> FMDB方法的介绍 1.首先我们需要创建一个FMDatabase实例&#xff1a; (FMDatabase*)DataBaseSigonInstance { //数据库初始化 NSString *homeDir NSHomeDirectory(); //NSLog("%",homeDir); NSString *dbPath …

网络克隆软件_网文生成器,克隆的是骗钱“病毒”

文章克隆器页面。图据北京晚报如今不论男女老少&#xff0c;多半喜欢用手机收集信息、浏览自己关注的话题。有的时候&#xff0c;人们会发现&#xff0c;不少亲朋发来的链接或者公众号推送的文章&#xff0c;长得特别像&#xff0c;但多少有那么些微不同。其实&#xff0c;不是…

使用python 对图片进行水印,保护自己写的文章

1&#xff0c;关于文章被爬 说起来挺桑心的&#xff0c;好不容易写的文章&#xff0c;被爬走。 用个搜索引擎搜索都不是在第一位&#xff0c;写的文章全给这些网站提供流量了。 这种网站还居多广告。 还是抱怨少点吧。csdn对于这些事情也是无所作为啊。 最起码的防盗链也不…

android layout_width 属性,android:layout_weight属性详解

在android开发中LinearLayout很常用&#xff0c;LinearLayout的内控件的android:layout_weight在某些场景显得非常重要&#xff0c;比如我们需要按比例显示。android并没用提供table这样的控件&#xff0c;虽然有TableLayout&#xff0c;但是它并非是我们想象中的像html里面的t…

angular的$http发送post,get请求无法传送参数的问题

2019独角兽企业重金招聘Python工程师标准>>> 用$http进行异步请求的时候发现了一个奇怪的事情&#xff0c;用$http.post(url,data)的方法进行请求&#xff0c;后台死活接收不到data的参数&#xff0c;真是百思不得姐啊..... 折腾了老半天才在stackoverflow上找到答案…

怎样解决Word文档图标无法正常显示的问题?

此类问题是由于 Word 程序相关组件损坏导致&#xff0c;可以通过下面的方案来解决&#xff1a;步骤/方法按键盘上的 Windows 徽标健 R 键&#xff0c;输入 regedit&#xff0c;按回车键。&#xff08;若弹出用户账户控制窗口&#xff0c;请允许以继续&#xff09;对于 Word 200…

MathType与Origin是怎么兼容的

MathType作为一款常用的公式编辑器&#xff0c;可以与很多的软件兼容使用。Origin虽然是一款专业绘图与数据分析软件&#xff0c;但是在使用过程中也是可以用到MathType。它可以帮助Origin给图表加上标签&#xff0c;或者在表格中增加公式标签。但是一些用户朋友对这个不能不是…

AutoBookmark Adobe Acrobat快速自动批量添加书签/目录

前言 解决问题&#xff1a;Adobe Acrobat快速自动批量添加书签/目录, 彻底告别手动添加书签的烦恼 AutoBookmark 前言1 功能简介2 实现步骤2.1 下载插件2.2 将插件复制到Acrobat文件夹下2.3 自动生成书签 1 功能简介 我们在查看PDF版本的论文或者其他文件的时候, 虽然相比较于…

Python调用微博API获取微博内容

一&#xff1a;获取app-key 和 app-secret 使用自己的微博账号登录微博开放平台(http://open.weibo.com/)&#xff0c;在微博开放中心下“创建应用”创建一个应用&#xff0c;应用信息那些随便填&#xff0c;填写完毕后&#xff0c;不需要提交审核&#xff0c;需要的只是那个ap…

鸿蒙系统hdc,HDC2020有看头:要揭开鸿蒙系统和EMUI11神秘面纱?

IFA2020算是HDC2020的预热吧&#xff0c;一个是9月2日在德国柏林举办的消费电子展&#xff0c;一个是在松山湖举办的华为开发者大会&#xff0c;二者的目的都一样&#xff0c;但也有一丝不同&#xff0c;IFA是为了让老外了解HMS、了解华为的智慧生态&#xff0c;而HDC2020就是要…

Java String 学习笔记 (一)

2019独角兽企业重金招聘Python工程师标准>>> ###String 简介 String 并非java的8大基本数据类型之一。 java中基本数据类型存储在栈内存中。而String不是&#xff0c;新new的String 对象存储在堆内存中。而字符串存储在常量池中。String对象的引用存储中栈内存中。 …

note2 android4.3,玩家们动手吧 Note2安卓4.3固件已泄漏

【PConline 资讯】最近各个牌子的安卓机迎来了升级安卓4.3的大潮&#xff0c;现在三星Galaxy Note2的安卓4.3固件已经泄漏出来了。实际上&#xff0c;此前三星官方已经确认&#xff0c;Galaxy Note3可以获得官方的安卓4.3固件升级&#xff0c;但具体日期没有确定&#xff0c;只…

【JUnit 报错】 method initializationerror not found:JUnit4单元测试报错问题

今天是用JUnit测试一段代码&#xff0c;报错method initializationerror not found:&#xff1a;出现如下问题&#xff1a; 双击这个就显示出现如下的错误&#xff1a; 查询网上&#xff0c;说是junit版本的问题&#xff1a; 那我就不使用JUnit这个Libernary了&#xff0c;下载…

将byte数组以html形式输出到页面,java 数组显示到html

java 数组显示到html[2021-02-05 01:08:54] 简介:php去除nbsp的方法&#xff1a;首先创建一个PHP代码示例文件&#xff1b;然后通过“preg_replace("/(\s|\&nbsp\;| |\xc2\xa0)/", " ", strip_tags($val));”方法去除所有nbsp即可。推荐&#xff1a;…

windows 下 git 禁用 CRLF 转换 LF

2019独角兽企业重金招聘Python工程师标准>>> windows中的换行符为 CRLF&#xff0c; 而在linux下的换行符为LF&#xff0c;所以在执行add . 时出现提示&#xff0c;解决办法&#xff1a; 删除根目录 .git 文件夹禁用自动转换 > git config --global core.autocrl…

使用gulp构建前端(三)

为什么80%的码农都做不了架构师&#xff1f;>>> 使用gulp构建前端&#xff08;三&#xff09; 紧接着上述文章内容&#xff0c;开始新的插件的使用 插件三 gulp-clean-css&#xff0c;作用减小文件大小&#xff0c;并给引用url添加版本号避免缓存&#xff0c;一个需…

网站html标签如何优化,网站HTML标签优化教程

真正需要SEOer掌握并经常使用的HTML标签也就那么几个&#xff0c;本小节丈哥seo带来的是页面HTML标签优化。title标签 – 标题标签1)title标签用法网站SEOHTML标签优化教程 – 丈哥seo2)标题标签的作用关于网站标题写法与操作技巧有许多&#xff0c;需要怎么优化标题的朋友可以…

html 轮播图的鼠标事件,这是图片轮播的代码 html+css,怎么加上鼠标悬停移出继续功能?...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼.one {position: absolute;width: 500px;height: 400px;overflow: hidden;}.one_cantent img {width: 500px;height: 300px;float: left;}.one_cantent {width: 2500px;height: 400px;position: absolute;left: 0px;animation-name…