软件编程
位置:首页>> 软件编程>> Android编程>> Android自定义UI实现微信语音

Android自定义UI实现微信语音

作者:Saflyer  发布时间:2022-04-18 10:47:41 

标签:Android,UI,微信,语音

本文实例为大家分享了java获取不同路径的方法,供大家参考,具体内容如下

思路:
自定义Button
获取DialogManager、AudioManager setOnLongClickListener长按事件--做好AudioManager的录音准备工作
AudioManager.setOnAudioStateListener(this)实现录音准备完毕的接口回调方法,方法中去发送MSG_AUDIO_PREPARE消息代表录音准备工作完毕
在mHandler中接收消息,开始开启线程录音,并且计时,计时的过程中,去发送MSG_VOICE_CHANGED消息,去updateVoiceLeve更新音量图标,另外在MotionEvent.ACTION_UP事件中,去定义audioFinishRecorderListener.onFinish接口方法,方法是录音完毕的回调方法,在MainActivity中,mAudioRecorderButton.setAudioFinishRecorderListener实现录音完毕回调方法去更新listViewUi界面

点击listViewItem条目播放动画, MediaManager.playSound并且播放音频

activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.imooc_recorder.MainActivity" >

<ListView
android:id="@+id/id_listview"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#ebebeb"
android:divider="@null"
android:dividerHeight="10dp" >
</ListView>

<FrameLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<View
 android:background="#ccc"
 android:layout_width="fill_parent"
 android:layout_height="1dp" />

<com.example.recorder_view.AudioRecorderButton
 android:id="@+id/id_recorder_button"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:layout_marginBottom="7dp"
 android:layout_marginLeft="50dp"
 android:layout_marginRight="50dp"
 android:layout_marginTop="6dp"
 android:background="@drawable/button_recorder_normal"
 android:gravity="center"
 android:minHeight="0dp"
 android:padding="5dp"
 android:text="@string/str_recorder_nomal"
 android:textColor="#727272" >
</com.example.recorder_view.AudioRecorderButton>

</FrameLayout>

</LinearLayout>

dialog_recorder.xml---dialog布局文件


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/dialog_loading_bg"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp" >

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<ImageView
 android:id="@+id/id_recorder_dialog_icon"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:src="@drawable/recorder"
 android:visibility="visible" />

<ImageView
 android:id="@+id/id_recorder_dialog_voice"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:src="@drawable/v1"
 android:visibility="visible" />
</LinearLayout>

<TextView
android:id="@+id/id_recorder_dialog_label"
android:layout_marginTop="5dp"
android:text="手指上滑,取消发送"
android:textColor="#ffffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</LinearLayout>

item_recorder.xml--listview中的每个item


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:background="#eeeeee"
android:layout_height="60dp"
android:layout_marginTop="5dp" >

<ImageView
android:id="@+id/id_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:src="@drawable/icon" />

<FrameLayout
android:id="@+id/id_recorder_length"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/id_icon"
android:background="@drawable/chatto_bg_focused" >

<View
 android:id="@+id/id_recorder_anim"
 android:layout_width="25dp"
 android:layout_height="25dp"
 android:layout_gravity="center_vertical|right"
 android:background="@drawable/adj" />
</FrameLayout>

<TextView
android:id="@+id/id_recorder_time"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/id_recorder_length"
android:layout_marginRight="3dp"
android:textColor="#ff777777"
android:text=""
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</RelativeLayout>

styles.xml


<style name="Theme_AudioDialog" parent="@android:Theme.Dialog">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
 <item name="android:windowIsTranslucent">true</item>
 <item name="android:backgroundDimEnabled">false</item>

</style>

button_recorder_normal.xml


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >

<solid android:color="#ffffff"/>
<stroke android:width="1px" android:color="#9b9b9b"/>
<corners android:radius="3dp"/>
</shape>

button_recorder_recording.xml


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >

<solid android:color="#ffffff"/>
<stroke android:width="1px" android:color="#eeeeee"/>
<corners android:radius="3dp"/>
</shape>

play_anim.xml


<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >

<item
android:drawable="@drawable/v_anim1"
android:duration="300">
</item>
<item
android:drawable="@drawable/v_anim2"
android:duration="300">
</item>

<item
android:drawable="@drawable/v_anim3"
android:duration="300">
</item>

</animation-list>

MainActivity


package com.example.imooc_recorder;

import java.util.ArrayList;
import java.util.List;

import com.example.recorder_view.AudioRecorderButton;
import com.example.recorder_view.AudioRecorderButton.AudioFinishRecorderListener;
import com.example.recorder_view.MediaManager;

import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends Activity {

private ListView mListView;
private ArrayAdapter<Recorder> mAdapter;
private List<Recorder> mDatas = new ArrayList<MainActivity.Recorder>();
private AudioRecorderButton mAudioRecorderButton;
private View animView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.id_listview);
mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.id_recorder_button);
//录音完毕,回调
mAudioRecorderButton.setAudioFinishRecorderListener(new AudioFinishRecorderListener() {

@Override
 public void onFinish(float seconds, String filePath) {
 // TODO Auto-generated method stub
 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 OnItemClickListener() {
 @Override
 public void onItemClick(AdapterView<?> parent, View view,
  int position, long id) {
 if (animView !=null) {
  animView.setBackgroundResource(R.drawable.adj);
  animView = null;

}
 //播放动画
 animView = view.findViewById(R.id.id_recorder_anim);
 animView.setBackgroundResource(R.drawable.play_anim);
 AnimationDrawable anim = (AnimationDrawable) animView.getBackground();
 anim.start();
 //播放音频
 MediaManager.playSound(mDatas.get(position).filePath,new MediaPlayer.OnCompletionListener() {

@Override
  public void onCompletion(MediaPlayer mp) {
  // TODO Auto-generated method stub
  animView.setBackgroundResource(R.drawable.adj);
  }
 });
 }
});
}

@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
MediaManager.pause();
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
MediaManager.resume();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
MediaManager.release();
}
/**内部类*/
class Recorder{
float time;
String filePath;
public Recorder(float time, String filePath) {
 super();
 this.time = time;
 this.filePath = filePath;
}
public float getTime() {
 return time;
}
public void setTime(float time) {
 this.time = time;
}
public String getFilePath() {
 return filePath;
}
public void setFilePath(String filePath) {
 this.filePath = filePath;
}

}
}

RecorderAdapter


package com.example.imooc_recorder;

import java.util.List;

import com.example.imooc_recorder.MainActivity.Recorder;

import android.content.Context;
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.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;

public class RecorderAdapter extends ArrayAdapter<Recorder> {

// private List<Recorder> mDatas;
// private Context mContext;
private int mMinItemWidth;
private int mMaxItemWidth;
private LayoutInflater mInflater;

public RecorderAdapter(Context context, List<Recorder> datas) {
super(context, -1, datas);
// mContext = context;
// mDatas = datas;
WindowManager wm = (WindowManager) context
 .getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);

mMaxItemWidth = (int) (outMetrics.widthPixels * 0.7f);
mMinItemWidth = (int) (outMetrics.widthPixels * 0.15f);
mInflater = LayoutInflater.from(context);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
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.id_recorder_time);
 holder.length = convertView.findViewById(R.id.id_recorder_length);

convertView.setTag(holder);
}else {
 holder = (ViewHolder) convertView.getTag();
}
holder.seconds.setText(Math.round(getItem(position).time)+"\"");
ViewGroup.LayoutParams lp = holder.length.getLayoutParams();
lp.width = (int) (mMinItemWidth +(mMaxItemWidth/60f*getItem(position).time));
return convertView;
}

private class ViewHolder {
private TextView seconds;
private View length;
}
}

AudioRecorderButton


package com.example.recorder_view;

import com.example.imooc_recorder.R;
import com.example.recorder_view.AudioManager.AudioStateListener;

import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.telephony.SignalStrength;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

public class AudioRecorderButton extends Button implements AudioStateListener {
private static final int DISTANCE_Y_CANCEL = 50;

/** 提示框管理工具类 */
private DialogManager mDialogManager;
/** 正常、松开手指取消发送、手指上滑取消发送 */
private static final int STATE_NORMAL = 1;
private static final int STATE_RECORDING = 2;
private static final int STATE_WANT_TO_CANCEL = 3;
/** 初始状态:正常 */
private int mCurState = STATE_NORMAL;
/** 当前是否正在录音 */
private boolean isRecording = false;

/** 录音工具类 */
private AudioManager mAudioManager;

/** 录音总时间 */
private float mTime;

/** 是否触发button */
private boolean mReady;

/** *****************自定义Button************************************* */
public AudioRecorderButton(Context context) {
super(context);
}

public AudioRecorderButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public AudioRecorderButton(Context context, AttributeSet attrs) {
super(context, attrs);
mDialogManager = new DialogManager(getContext());
String dir = Environment.getExternalStorageDirectory()
 + "/imooc_recorder_audios";
mAudioManager = AudioManager.getInstance(dir);
mAudioManager.setOnAudioStateListener(this);
/** 监听回调 */
setOnLongClickListener(new OnLongClickListener() {

@Override
 public boolean onLongClick(View v) {
 mReady = true;
 // 录音准备--准备好后,有实现接口方法
 mAudioManager.prepareAndio();
 return false;
 }
});
}

/******************************************************
* 完成录音准备的回调函数
*/
@Override
public void wellPrepared() {
// 准备完毕,发送消息
mHandler.sendEmptyMessage(MSG_AUDIO_PREPARE);
}

private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
 switch (msg.what) {
 case MSG_AUDIO_PREPARE:
 mDialogManager.showRecordingDailog();
 isRecording = true;

new Thread(new Runnable() {

@Override
  public void run() {

// TODO Auto-generated method stub
  while (isRecording) {
   try {
   Thread.sleep(100);
   // 录音计时
   mTime += 0.1f;
   mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);
   } catch (InterruptedException e) {
   e.printStackTrace();
   }

}

}
 }).start();
 break;
 case MSG_VOICE_CHANGED:

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

break;
 case MSG_DIALOG_DIMISS:

mDialogManager.dimissDailog();
 break;

default:
 break;
 }
 super.handleMessage(msg);
};
};

private static final int MSG_AUDIO_PREPARE = 0x110;
private static final int MSG_VOICE_CHANGED = 0x111;
private static final int MSG_DIALOG_DIMISS = 0x112;

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
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) {
 // 根据xy坐标。判断是否想要取消
 if (wantToCancel(x, y)) {
  changeState(STATE_WANT_TO_CANCEL);
 } else {
  changeState(STATE_RECORDING);
 }

}
 break;
case MotionEvent.ACTION_UP:
 if (!mReady) {
 // 没有触发onclick
 reset();
 return super.onTouchEvent(event);

}
 if (!isRecording || mTime < 0.6f) {
 // prapred没有完成已经up了
 mDialogManager.tooShort();
 mAudioManager.cancel();
 mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);

} else if (mCurState == STATE_RECORDING) {

mDialogManager.dimissDailog();
 mAudioManager.release();
 if (audioFinishRecorderListener != null) {
  audioFinishRecorderListener.onFinish(mTime,
   mAudioManager.getCurrentFilePath());
 }
 } else if (mCurState == STATE_WANT_TO_CANCEL) {
 mDialogManager.dimissDailog();
 mAudioManager.cancel();
 }
 // 恢复标志位
 reset();
 break;

default:
 break;
}
return super.onTouchEvent(event);
}

private void reset() {
// TODO Auto-generated method stub
mReady = false;
mTime = 0;
isRecording = false;
changeState(STATE_NORMAL);
}

private boolean wantToCancel(int x, int y) {
// x<0代表来到按钮的范围以外
if (x < 0 || x > getWidth()) {
 return true;

}
if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
 return true;

}
return false;
}

private void changeState(int state) {
// TODO Auto-generated method stub
if (mCurState != state) {
 mCurState = state;

switch (state) {
 case STATE_NORMAL:
 setBackgroundResource(R.drawable.button_recorder_normal);
 setText(R.string.str_recorder_nomal);
 break;

case STATE_RECORDING:
 setBackgroundResource(R.drawable.button_recorder_recording);
 setText(R.string.str_recorder_recording);
 if (isRecording) {
  // to do
  mDialogManager.recording();
 }
 break;

case STATE_WANT_TO_CANCEL:
 setBackgroundResource(R.drawable.button_recorder_recording);
 setText(R.string.str_recorder_want_cancel);
 mDialogManager.wantToCancle();
 break;
 default:
 break;
 }
}
}

/** 完成录音回调 */
public interface AudioFinishRecorderListener {
void onFinish(float seconds, String filePath);
}

private AudioFinishRecorderListener audioFinishRecorderListener;

public void setAudioFinishRecorderListener(
 AudioFinishRecorderListener audioFinishRecorderListener) {
this.audioFinishRecorderListener = audioFinishRecorderListener;
}
}

DialogManager


package com.example.recorder_view;

import com.example.imooc_recorder.R;

import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

public class DialogManager {
private Dialog mDialog;
private ImageView mIcon;
private ImageView mVoice;
private TextView mLabel;
private Context mContext;

public DialogManager(Context context) {
this.mContext = context;
}

public void showRecordingDailog() {
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.id_recorder_dialog_icon);
mVoice = (ImageView) mDialog
 .findViewById(R.id.id_recorder_dialog_voice);
mLabel = (TextView) mDialog.findViewById(R.id.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.drawable.recorder);
 mLabel.setText("手指上滑,取消发送");

}
}

public void wantToCancle() {
if (mDialog != null && mDialog.isShowing()) {
 mIcon.setVisibility(View.VISIBLE);
 mVoice.setVisibility(View.GONE);
 mLabel.setVisibility(View.VISIBLE);

mIcon.setImageResource(R.drawable.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.drawable.voice_to_short);
 mLabel.setText("录音时间过短");

}

}

public void dimissDailog() {
if (mDialog != null && mDialog.isShowing()) {
 mDialog.dismiss();
 mDialog = null;
}
}

public void updateVoiceLeve(int level) {
if (mDialog != null && mDialog.isShowing()) {
//  mIcon.setVisibility(View.VISIBLE);
//  mVoice.setVisibility(View.VISIBLE);
//  mLabel.setVisibility(View.VISIBLE);

// 通过方法名字,找到资源
 int resId = mContext.getResources().getIdentifier("v" + level,
  "drawable", mContext.getPackageName());
 mVoice.setImageResource(resId);
}
}

}

AudioManager


package com.example.recorder_view;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

import javax.security.auth.PrivateCredentialPermission;

import android.R.integer;
import android.media.MediaRecorder;

public class AudioManager {

private MediaRecorder mMediaRecorder;
private String mDir;
private String mCurrentFilepath;
private static AudioManager mInstance;

private boolean isPrepare;

/** 接口回调 */
public interface AudioStateListener {
void wellPrepared();
}

public AudioStateListener mAudioStateListener;

public void setOnAudioStateListener(AudioStateListener audioStateListener) {
mAudioStateListener = audioStateListener;

}

private AudioManager(String dir) {
this.mDir = dir;
}

public static AudioManager getInstance(String dir) {
if (mInstance == null) {
 synchronized (AudioManager.class) {
 if (mInstance == null) {
  mInstance = new AudioManager(dir);

}

}
}
return mInstance;
}

public void prepareAndio() {
isPrepare = false;
File dir = new File(mDir);
if (!dir.exists()) {
 dir.mkdirs();
}
String fileName = geneFileName();

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.RAW_AMR);
// 设置音频编码
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
 mMediaRecorder.prepare();
 mMediaRecorder.start();
 isPrepare = true;
 if (mAudioStateListener != null) {
 mAudioStateListener.wellPrepared();

}
} catch (Exception e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}
}

private String geneFileName() {
return UUID.randomUUID().toString() + ".amr";
}

public int getVoiceLevel(int maxLevel) {
if (isPrepare) {
 try {
 // mMediaRecorder.getMaxAmplitude() 1~32767
 // return maxlevel*mMediaRecorder.getMaxAmplitude()/32768+1;
 return maxLevel * new MediaRecorder().getMaxAmplitude() / 32768
  + 1;
 } catch (Exception e) {
 }

}
return 1;
}

public void release() {
mMediaRecorder.release();
mMediaRecorder = null;
}

public void cancel() {
release();
if (mCurrentFilepath != null) {
 File file = new File(mCurrentFilepath);
 file.delete();
 mCurrentFilepath = null;

}
}

public String getCurrentFilePath() {
// TODO Auto-generated method stub
return mCurrentFilepath;
}

}

MediaManager


package com.example.recorder_view;

import java.io.IOException;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;

public class MediaManager {

private static MediaPlayer mediaPlayer;
private static boolean isPause;
public static void playSound(String filePath,
 OnCompletionListener onCompletionListener) {
if (mediaPlayer == null) {
 mediaPlayer = new MediaPlayer();
 mediaPlayer.setOnErrorListener(new OnErrorListener() {

@Override
 public boolean onError(MediaPlayer mp, int what, int extra) {
  mediaPlayer.reset();
  return false;
 }
 });
}else {
 mediaPlayer.reset();
}
try {
 mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
 mediaPlayer.setOnCompletionListener(onCompletionListener);
 mediaPlayer.setDataSource(filePath);
 mediaPlayer.prepare();
} catch (Exception e) {
 e.printStackTrace();
}
mediaPlayer.start();
}

public static void pause(){
if (mediaPlayer!=null && mediaPlayer.isPlaying()) {
 mediaPlayer.pause();
 isPause = true;
}
}
public static void resume(){
if (mediaPlayer!=null && isPause) {
 mediaPlayer.start();
 isPause = false;
}
}
public static void release(){
if (mediaPlayer!=null) {
 mediaPlayer.release();
 mediaPlayer = null;

}
}
}

本文已被整理到了《Android微信开发教程汇总》,欢迎大家学习阅读。

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com