Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。
效果图:
实现代码:
/** * 视频通话、语音通话 */ private fun showVideoPopupWindow(){ var popupView = layoutInflater.inflate(R.layout.wc_chat_video_pop_view , moment_root, false) var popupWindow = PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true) var popupRoot = popupView.findViewById<LinearLayout>(R.id.chat_video_pop_root) popupWindow.showAtLocation(popupRoot, Gravity.BOTTOM, 0, 0) var window = requireActivity().window //popupWindow在弹窗的时候背景半透明 val params = window.attributes params.alpha = 0.5f window.attributes = params popupWindow.setOnDismissListener { params.alpha = 1.0f window.attributes = params } //视频通话 popupView.findViewById<AppCompatTextView>(R.id.chat_pop_video_call).setOnClickListener { popupWindow.dismiss() CallSingleActivity.openActivity( requireActivity(),toUserId, true, toUserName, false, false) } //语音通话 popupView.findViewById<AppCompatTextView>(R.id.chat_pop_voice_call).setOnClickListener { popupWindow.dismiss() CallSingleActivity.openActivity( requireActivity(),toUserId, true, toUserName, true, false) } //取消 popupView.findViewById<AppCompatTextView>(R.id.chat_pop_cancel).setOnClickListener { popupWindow.dismiss() } }
/** * Author : wangning * Email : maoning20080809@163.com * Date : 2022/5/15 13:49 * Description : */ class CallSingleActivity : AppCompatActivity(), CallSession.CallSessionCallback { companion object { val EXTRA_TARGET = "targetId" val EXTRA_MO = "isOutGoing" val EXTRA_AUDIO_ONLY = "audioOnly" val EXTRA_USER_NAME = "userName" val EXTRA_FROM_FLOATING_VIEW = "fromFloatingView" fun openActivity( context: Context, targetId: String, isOutgoing: Boolean, inviteUserName: String, isAudioOnly: Boolean, isClearTop: Boolean ) { val intent = getCallIntent(context, targetId, isOutgoing, inviteUserName, isAudioOnly, isClearTop) if (context is Activity) { context.startActivity(intent) } else { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } } fun getCallIntent( context: Context, targetId: String, isOutgoing: Boolean, inviteUserName: String, isAudioOnly: Boolean, isClearTop: Boolean ): Intent { val voip = Intent(context, CallSingleActivity::class.java) voip.putExtra(EXTRA_MO, isOutgoing) voip.putExtra(EXTRA_TARGET, targetId) voip.putExtra(EXTRA_USER_NAME, inviteUserName) voip.putExtra(EXTRA_AUDIO_ONLY, isAudioOnly) voip.putExtra(EXTRA_FROM_FLOATING_VIEW, false) if (isClearTop) { voip.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } return voip } } private val TAG = "CallSingleActivity" private val handler = Handler(Looper.getMainLooper()) private var isOutgoing = false private var targetId: String = "" private var inviteUserName: String = "" var isAudioOnly = false private var isFromFloatingView = false private var gEngineKit: SkyEngineKit? = null private var currentFragment: SingleCallFragment? = null private var room: String = "" private var activity:CallSingleActivity? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStatusBarOrScreenStatus(this) setContentView(R.layout.activity_single_call) activity = this try { gEngineKit = SkyEngineKit.Instance() } catch (e: NotInitializedException) { SkyEngineKit.init(VoipEvent()) //重新初始化 try { gEngineKit = SkyEngineKit.Instance() } catch (ex: NotInitializedException) { finish() } } val intent = intent targetId = intent.getStringExtra(EXTRA_TARGET)!! inviteUserName = intent.getStringExtra(EXTRA_USER_NAME)!! isFromFloatingView = intent.getBooleanExtra(EXTRA_FROM_FLOATING_VIEW, false) isOutgoing = intent.getBooleanExtra(EXTRA_MO, false) isAudioOnly = intent.getBooleanExtra(EXTRA_AUDIO_ONLY, false) if (isFromFloatingView) { val serviceIntent = Intent(this, FloatingVoipService::class.java) stopService(serviceIntent) init(targetId, false, isAudioOnly, false) } else { // 权限检测 val per: Array<String> per = if (isAudioOnly) { arrayOf(Manifest.permission.RECORD_AUDIO) } else { arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA) } Permissions.request(this, per, object : Consumer<Int> { override fun accept(t: Int) { if (t === 0) { // 权限同意 init(targetId, isOutgoing, isAudioOnly, false) } else { Toast.makeText(activity, "权限被拒绝", Toast.LENGTH_SHORT).show() // 权限拒绝 finish() } } }) } } override fun onBackPressed() { } private fun init(targetId: String, outgoing: Boolean, audioOnly: Boolean, isReplace: Boolean) { val fragment: SingleCallFragment if (audioOnly) { fragment = FragmentAudio() } else { fragment = FragmentVideo() } val fragmentManager: FragmentManager = getSupportFragmentManager() currentFragment = fragment if (isReplace) { fragmentManager.beginTransaction() .replace(android.R.id.content, fragment) .commit() } else { fragmentManager.beginTransaction() .add(android.R.id.content, fragment) .commit() } if (outgoing && !isReplace) { // 创建会话 room = UUID.randomUUID().toString() + System.currentTimeMillis() val b: Boolean = gEngineKit?.startOutCall(applicationContext, room, targetId, audioOnly)!! TagUtils.d("创建房间返回:${room} , ${targetId} , ${b}") if (!b) { finish() return } WcApp.getInstance().roomId = room WcApp.getInstance().otherUserId = targetId val session: CallSession? = gEngineKit?.getCurrentSession() if (session == null) { finish() } else { session.setSessionCallback(this) } } else { val session: CallSession? = gEngineKit?.getCurrentSession() if (session == null) { finish() } else { if (session.isAudioOnly() && !audioOnly) { //这种情况是,对方切换成音频的时候,activity还没启动,这里启动后需要切换一下 isAudioOnly = session.isAudioOnly() fragment.didChangeMode(true) } session.setSessionCallback(this) } } } fun getEngineKit(): SkyEngineKit? { return gEngineKit } fun isOutgoing(): Boolean { return isOutgoing } fun getInviteUserName(): String { return inviteUserName } fun getToUserId(): String { return targetId } fun isFromFloatingView(): Boolean { return isFromFloatingView } // 显示小窗 fun showFloatingView() { if (!checkOverlayPermission()) { return } val intent = Intent(this, FloatingVoipService::class.java) intent.putExtra(EXTRA_TARGET, targetId) intent.putExtra(EXTRA_USER_NAME, inviteUserName) intent.putExtra(EXTRA_AUDIO_ONLY, isAudioOnly) intent.putExtra(EXTRA_MO, isOutgoing) startService(intent) finish() } // 切换到语音通话 fun switchAudio() { init(targetId, isOutgoing, true, true) } fun getRoomId(): String { return room } // ======================================界面回调================================ override fun didCallEndWithReason(reason: EnumType.CallEndReason) { WcApp.getInstance().otherUserId = "0" //交给fragment去finish // finish(); handler.post { currentFragment?.didCallEndWithReason(reason) } } override fun didChangeState(callState: EnumType.CallState) { if (callState === EnumType.CallState.Connected) { isOutgoing = false } handler.post { currentFragment?.didChangeState(callState) } } override fun didChangeMode(var1: Boolean) { handler.post { currentFragment?.didChangeMode(var1) } } override fun didCreateLocalVideoTrack() { handler.post { currentFragment?.didCreateLocalVideoTrack() } } override fun didReceiveRemoteVideoTrack(userId: String) { handler.post { currentFragment?.didReceiveRemoteVideoTrack(userId) } } override fun didUserLeave(userId: String) { handler.post { currentFragment?.didUserLeave(userId) } } override fun didError(var1: String) { handler.post { currentFragment?.didError(var1) } // finish(); } override fun didDisconnected(userId: String) { handler.post { currentFragment?.didDisconnected(userId) } } // ======================================================================================== // ======================================================================================== private fun checkOverlayPermission(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { SettingsCompat.setDrawOverlays(this, true) if (!SettingsCompat.canDrawOverlays(this)) { Toast.makeText(this, "需要悬浮窗权限", Toast.LENGTH_LONG).show() SettingsCompat.manageDrawOverlays(this) return false } } return true } @TargetApi(19) private fun getSystemUiVisibility(): Int { var flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { flags = flags or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY } return flags } /** * 设置状态栏透明 */ @TargetApi(19) fun setStatusBarOrScreenStatus(activity: Activity) { val window = activity.window //全屏+锁屏+常亮显示 window.addFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON ) window.decorView.systemUiVisibility = getSystemUiVisibility() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val layoutParams = getWindow().attributes layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES window.attributes = layoutParams } // 5.0以上系统状态栏透明 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //清除透明状态栏 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) //设置状态栏颜色必须添加 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) window.statusBarColor = Color.TRANSPARENT //设置透明 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //19 window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) } } override fun onDestroy() { super.onDestroy() } }
/** * Author : wangning * Email : maoning20080809@163.com * Date : 2022/5/15 16:20 * Description : 语音通话控制界面 */ class FragmentAudio : SingleCallFragment(), View.OnClickListener { private val TAG = "FragmentAudio" private var muteImageView: ImageView? = null private var speakerImageView: ImageView? = null private var micEnabled = false // 静音 private var isSpeakerOn = false // 扬声器 override fun getLayout(): Int { return R.layout.fragment_audio } override fun initView(view: View) { super.initView(view) muteImageView = view.findViewById(R.id.muteImageView) speakerImageView = view.findViewById(R.id.speakerImageView) minimizeImageView!!.visibility = View.GONE outgoingHangupImageView!!.setOnClickListener(this) incomingHangupImageView!!.setOnClickListener(this) minimizeImageView!!.setOnClickListener(this) muteImageView?.setOnClickListener(this) acceptImageView?.setOnClickListener(this) speakerImageView?.setOnClickListener(this) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M || OSUtils.isMiui() || OSUtils.isFlyme()) { lytParent!!.post { val params = minimizeImageView!!.layoutParams as RelativeLayout.LayoutParams params.topMargin = BarUtils.getStatusBarHeight() minimizeImageView!!.layoutParams = params } } } override fun init() { super.init() val currentSession = gEngineKit!!.getCurrentSession() currentState = currentSession!!.getState() // 如果已经接通 if (currentSession != null && currentState === EnumType.CallState.Connected) { descTextView!!.visibility = View.GONE // 提示语 outgoingActionContainer!!.visibility = View.VISIBLE durationTextView!!.visibility = View.VISIBLE minimizeImageView!!.visibility = View.VISIBLE startRefreshTime() } else { // 如果未接通 if (isOutgoing) { descTextView?.setText(R.string.av_waiting) outgoingActionContainer!!.visibility = View.VISIBLE incomingActionContainer!!.visibility = View.GONE } else { descTextView?.setText(R.string.av_audio_invite) outgoingActionContainer!!.visibility = View.GONE incomingActionContainer!!.visibility = View.VISIBLE } } } override fun didChangeState(state: EnumType.CallState) { currentState = state runOnUiThread { if (state === EnumType.CallState.Connected) { handler!!.removeMessages(WHAT_DELAY_END_CALL) incomingActionContainer!!.visibility = View.GONE outgoingActionContainer!!.visibility = View.VISIBLE minimizeImageView!!.visibility = View.VISIBLE descTextView!!.visibility = View.GONE startRefreshTime() } else { // do nothing now } } } override fun onClick(v: View) { val id = v.id // 接听 if (id == R.id.acceptImageView) { val session = gEngineKit!!.getCurrentSession() if (session != null) Log.d( TAG, "session = " + session + "; session.getState() = " + session.getState() ) if (session != null && session.getState() === EnumType.CallState.Incoming) { session.joinHome(session.getRoomId()!!) } else session?.sendRefuse() } // 挂断电话 if (id == R.id.incomingHangupImageView || id == R.id.outgoingHangupImageView) { //App.getInstance().setOtherUserId("0") WcApp.getInstance().otherUserId = "0" val session = gEngineKit!!.getCurrentSession() if (session != null) { TagUtils.d("FragmentAudio 挂电话:endCall ") SkyEngineKit.Instance()?.endCall() } // activity.finish(); //再onEvent中结束,防止ChatActivity结束了,消息发送不了 } // 静音 if (id == R.id.muteImageView) { val session = gEngineKit!!.getCurrentSession() if (session != null && session.getState() !== EnumType.CallState.Idle) { if (session.toggleMuteAudio(!micEnabled)) { micEnabled = !micEnabled } muteImageView!!.isSelected = micEnabled } } // 扬声器 if (id == R.id.speakerImageView) { val session = gEngineKit!!.getCurrentSession() if (session != null && session.getState() !== EnumType.CallState.Idle) { if (session.toggleSpeaker(!isSpeakerOn)) { isSpeakerOn = !isSpeakerOn } speakerImageView!!.isSelected = isSpeakerOn } } // 小窗 if (id == R.id.minimizeImageView) { if (callSingleActivity != null) { callSingleActivity!!.showFloatingView() } } } }
/** * Author : wangning * Email : maoning20080809@163.com * Date : 2022/5/15 16:14 * Description : */ abstract class SingleCallFragment : Fragment() { private val TAG = "SingleCallFragment" var minimizeImageView: ImageView? = null // 用户头像 var portraitImageView : ImageView? = null // 用户昵称 var nameTextView : TextView? = null // 状态提示用语 var descTextView : TextView? = null // 通话时长 var durationTextView : Chronometer? = null var outgoingHangupImageView: ImageView? = null var incomingHangupImageView: ImageView? = null var acceptImageView: ImageView? = null var tvStatus: TextView? = null var outgoingActionContainer: View? = null var incomingActionContainer: View? = null var connectedActionContainer: View? = null var lytParent: View? = null var isOutgoing = false var inviteUserName = "" var toUserId = "" var gEngineKit: SkyEngineKit? = null var handler: CallHandler? = null companion object { var callSingleActivity: CallSingleActivity? = null val WHAT_DELAY_END_CALL = 0x01 val WHAT_NO_NET_WORK_END_CALL = 0x02 var currentState: EnumType.CallState? = null var headsetPlugReceiver: HeadsetPlugReceiver? = null var endWithNoAnswerFlag = false var isConnectionClosed = false } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) retainInstance = true if (!EventBus.getDefault().isRegistered(this)) { EventBus.getDefault().register(this) } handler = CallHandler() } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(getLayout(), container, false) initView(view) init() return view } override fun onDestroyView() { if (durationTextView != null) durationTextView!!.stop() refreshMessage(true) super.onDestroyView() } override fun onDestroy() { if (EventBus.getDefault().isRegistered(this)) { EventBus.getDefault().unregister(this) } super.onDestroy() } abstract fun getLayout(): Int @Subscribe(threadMode = ThreadMode.MAIN) fun onEvent(messageEvent: MsgEvent<Any?>) { val code: Int = messageEvent.getCode() Log.d(TAG, "onEvent code = \$code; endWithNoAnswerFlag = \$endWithNoAnswerFlag") if (code == MsgEvent.CODE_ON_CALL_ENDED) { if (endWithNoAnswerFlag) { didCallEndWithReason(EnumType.CallEndReason.Timeout) } else if (isConnectionClosed) { didCallEndWithReason(EnumType.CallEndReason.SignalError) } else { if (callSingleActivity != null) { callSingleActivity!!.finish() } } } else if (code == MsgEvent.CODE_ON_REMOTE_RING) { descTextView!!.text = "对方已响铃" } } override fun onAttach(context: Context) { super.onAttach(context) callSingleActivity = activity as CallSingleActivity? TagUtils.d("SingleCallFragment callSingleActivity 的对象: ${callSingleActivity}") if (callSingleActivity != null) { callSingleActivity?.let { isOutgoing = it.isOutgoing() gEngineKit = it.getEngineKit() inviteUserName = it.getInviteUserName() toUserId = it.getToUserId() } headsetPlugReceiver = HeadsetPlugReceiver() val filter = IntentFilter() filter.addAction(Intent.ACTION_HEADSET_PLUG) callSingleActivity!!.registerReceiver(headsetPlugReceiver, filter) } } override fun onDetach() { super.onDetach() callSingleActivity!!.unregisterReceiver(headsetPlugReceiver) //注销监听 callSingleActivity = null } open fun initView(view: View) { lytParent = view.findViewById(R.id.lytParent) minimizeImageView = view.findViewById(R.id.minimizeImageView) portraitImageView = view.findViewById(R.id.portraitImageView) nameTextView = view.findViewById(R.id.nameTextView) descTextView = view.findViewById(R.id.descTextView) durationTextView = view.findViewById(R.id.durationTextView) outgoingHangupImageView = view.findViewById(R.id.outgoingHangupImageView) incomingHangupImageView = view.findViewById(R.id.incomingHangupImageView) acceptImageView = view.findViewById(R.id.acceptImageView) tvStatus = view.findViewById(R.id.tvStatus) outgoingActionContainer = view.findViewById(R.id.outgoingActionContainer) incomingActionContainer = view.findViewById(R.id.incomingActionContainer) connectedActionContainer = view.findViewById(R.id.connectedActionContainer) durationTextView?.setVisibility(View.GONE) nameTextView?.setText(inviteUserName) //portraitImageView.setImageResource(R.mipmap.icon_default_header); BaseUtils.showAvatar(toUserId, portraitImageView!!) TagUtils.d("邀请名称:" + inviteUserName +" , " + toUserId) if (isOutgoing) { handler!!.sendEmptyMessageDelayed( WHAT_DELAY_END_CALL, (60 * 1000).toLong() ) //1分钟之后未接通,则挂断电话 } } open fun init() {} // ======================================界面回调================================ fun didCallEndWithReason(callEndReason: EnumType.CallEndReason) { when (callEndReason) { EnumType.CallEndReason.Busy -> { tvStatus!!.text = "对方忙线中" } EnumType.CallEndReason.SignalError -> { tvStatus!!.text = "连接断开" } EnumType.CallEndReason.RemoteSignalError -> { tvStatus!!.text = "对方网络断开" } EnumType.CallEndReason.Hangup -> { tvStatus!!.text = "挂断" } EnumType.CallEndReason.MediaError -> { tvStatus!!.text = "媒体错误" } EnumType.CallEndReason.RemoteHangup -> { tvStatus!!.text = "对方挂断" } EnumType.CallEndReason.OpenCameraFailure -> { tvStatus!!.text = "打开摄像头错误" } EnumType.CallEndReason.Timeout -> { tvStatus!!.text = "对方未接听" } EnumType.CallEndReason.AcceptByOtherClient -> { tvStatus!!.text = "在其它设备接听" } } incomingActionContainer!!.visibility = View.GONE outgoingActionContainer!!.visibility = View.GONE if (connectedActionContainer != null) connectedActionContainer!!.visibility = View.GONE refreshMessage(false) Handler(Looper.getMainLooper()).postDelayed({ if (callSingleActivity != null) { callSingleActivity!!.finish() } }, 1500) } open fun didChangeState(state: EnumType.CallState) {} open fun didChangeMode(isAudio: Boolean?) {} open fun didCreateLocalVideoTrack() {} open fun didReceiveRemoteVideoTrack(userId: String?) {} open fun didUserLeave(userId: String?) {} open fun didError(error: String?) {} fun didDisconnected(error: String?) { handler!!.sendEmptyMessage(WHAT_NO_NET_WORK_END_CALL) } private fun refreshMessage(isForCallTime: Boolean) { if (callSingleActivity == null) { return } // 刷新消息; demo中没有消息,不用处理这儿快逻辑 } fun startRefreshTime() { val session = SkyEngineKit.Instance()!!.getCurrentSession() ?: return if (durationTextView != null) { durationTextView!!.visibility = View.VISIBLE durationTextView!!.base = SystemClock.elapsedRealtime() - (System.currentTimeMillis() - session.getStartTime()) durationTextView!!.start() } } fun runOnUiThread(runnable: Runnable?) { if (callSingleActivity != null) { callSingleActivity!!.runOnUiThread(runnable) } } class CallHandler : Handler() { override fun handleMessage(msg: Message) { if (msg.what == WHAT_DELAY_END_CALL) { if (currentState !== EnumType.CallState.Connected) { endWithNoAnswerFlag = true if (callSingleActivity != null) { TagUtils.d("SingleCallFragment 挂电话:endCall 555 ") SkyEngineKit.Instance()?.endCall() } } } else if (msg.what == WHAT_NO_NET_WORK_END_CALL) { isConnectionClosed = true if (callSingleActivity != null) { TagUtils.d("SingleCallFragment 挂电话:endCall 666 ") SkyEngineKit.Instance()?.endCall() } } } } class HeadsetPlugReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.hasExtra("state")) { val session = SkyEngineKit.Instance()!!.getCurrentSession() ?: return if (intent.getIntExtra("state", 0) == 0) { //拔出耳机 session.toggleHeadset(false) } else if (intent.getIntExtra("state", 0) == 1) { //插入耳机 session.toggleHeadset(true) } } } } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/background_dark"> <ImageView android:id="@+id/minimizeImageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:src="@drawable/av_minimize" /> <LinearLayout android:id="@+id/lytParent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:gravity="center_horizontal" android:orientation="vertical"> <ImageView android:id="@+id/portraitImageView" android:layout_width="120dp" android:layout_height="120dp" android:layout_marginTop="80dp" android:src="@drawable/av_default_header" /> <TextView android:id="@+id/nameTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:text="" android:textColor="@android:color/white" /> <TextView android:id="@+id/descTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:text="邀请你进行语音通话" android:textColor="@android:color/white" /> </LinearLayout> <!--拨出按钮显示--> <include android:id="@+id/outgoingActionContainer" layout="@layout/av_p2p_audio_outgoing" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:visibility="visible" /> <!--接听按钮显示--> <include android:id="@+id/incomingActionContainer" layout="@layout/av_p2p_audio_incoming" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:visibility="gone" /> <TextView android:id="@+id/tvStatus" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="130dp" android:textColor="#FFFFFF" android:textSize="16sp" /> </RelativeLayout>