Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。
Kotlin高仿微信-项目实践58篇,点击查看详情
效果图:
实现代码:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/login_root_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/wc_base_bg"> <include layout="@layout/wc_base_top_title"/> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/login_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="120dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toBottomOf="@+id/login_root_layout" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:src="@drawable/wc_logo3"/> <TextView android:id="@+id/login_account_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="20dp" android:layout_marginTop="60dp" android:text="@string/user_account" android:textSize="16sp" android:textColor="@color/color_171717" android:layout_marginBottom="@dimen/distance_40" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/login_icon"/> <EditText android:id="@+id/login_account" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginEnd="20dp" android:layout_marginStart="70dp" android:paddingLeft="10dp" android:paddingVertical="8dp" android:inputType="textEmailAddress" app:layout_constraintEnd_toStartOf="@+id/login_account_tv" app:layout_constraintTop_toTopOf="@+id/login_account_tv" app:layout_constraintBottom_toBottomOf="@+id/login_account_tv" android:background="@drawable/base_edittext_selector" android:hint="@string/login_user_tip" android:textColorHint="@color/gray" android:textSize="18sp" android:text="" android:textColor="@color/gray_text" /> <TextView android:id="@+id/login_password_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="20dp" android:layout_marginTop="50dp" android:text="@string/login_password" android:textSize="16sp" android:textColor="@color/black" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/login_account_tv" /> <EditText android:id="@+id/login_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginEnd="20dp" android:layout_marginStart="70dp" android:paddingLeft="10dp" android:paddingVertical="10dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/login_password_tv" app:layout_constraintTop_toTopOf="@+id/login_password_tv" app:layout_constraintBottom_toBottomOf="@+id/login_password_tv" android:hint="@string/login_password_tip" android:textColorHint="@color/gray" android:textSize="16sp" android:inputType="textPassword" android:background="@drawable/base_edittext_selector" android:text="" android:textColor="@color/gray_text" /> <androidx.appcompat.widget.AppCompatButton android:id="@+id/btn_register" android:layout_width="140dp" android:layout_height="wrap_content" android:background="@drawable/wc_base_green_selector" android:layout_marginTop="80dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/btn_login" app:layout_constraintTop_toBottomOf="@+id/login_password_tv" android:text="@string/register" android:textColor="@color/white" android:textSize="18sp" android:textStyle="bold"/> <androidx.appcompat.widget.AppCompatButton android:id="@+id/btn_login" android:layout_width="140dp" android:layout_height="wrap_content" android:background="@drawable/wc_base_green_selector" app:layout_constraintTop_toTopOf="@+id/btn_register" app:layout_constraintBottom_toBottomOf="@+id/btn_register" app:layout_constraintStart_toEndOf="@+id/btn_register" app:layout_constraintEnd_toEndOf="parent" android:textColor="@color/white" android:text="@string/login" android:textSize="18sp" android:textStyle="bold"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
/** * Author : wangning * Email : maoning20080809@163.com * Date : 2022/4/19 14:41 * Description : 登录页面 */ class LoginFragment : BaseDataBindingFragment<WcMainLoginBinding>() , ServiceConnection { override fun getLayoutRes() = R.layout.wc_main_login private val userViewModel : UserViewModel by viewModels() private var account: String = "" private var password: String = "" private var xmppService: XmppService? = null private var navController : NavController? = null //是否在注册成功页面,注册成功返回 private var isRegisterBack:Boolean = false override fun onAttach(context: Context) { super.onAttach(context) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) } override fun onStart() { super.onStart() } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) } override fun onHiddenChanged(hidden: Boolean) { super.onHiddenChanged(hidden) if(!hidden){ getFocus(); } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViews() } private fun initViews() { super.builder().setTitleContent(R.string.wc_base_top_login) base_top_back.setOnClickListener { System.exit(1) } navController = Navigation.findNavController(btn_register) navController?.previousBackStackEntry?.savedStateHandle?.getLiveData<Boolean>(CommonUtils.User.IS_REGISTER_BACK)?.observe(viewLifecycleOwner){ isRegisterBack = it } var intent = Intent(requireActivity(), XmppService::class.java) requireActivity().bindService(intent, this , BIND_AUTO_CREATE) btn_register.setOnClickListener { Navigation.findNavController(it).navigate(R.id.action_register) } btn_login.setOnClickListener { login() } var accountTemp = DataStoreUtils.get(DataStoreParams.User.DS_ACCOUNT, "") as String var passwordTemp = DataStoreUtils.get(DataStoreParams.User.DS_PASSWORD, "") as String CoroutineScope(Dispatchers.Main).launch { delay(200) login_account.setText(accountTemp) login_password.setText(passwordTemp) } login_root_layout.setOnClickListener { TagUtils.d("点击根节点 Click ") SoftInputUtils.hideSoftInput(login_account) } userViewModel.insertUserData.observe(viewLifecycleOwner){ //如果同步成功,则重新登录 if(it){ DataStoreUtils.put(DataStoreParams.User.DS_OPENFILE_REGISTER, true) login() } else { ToastUtils.makeText(requireActivity(), BaseUtils.getString(R.string.wc_login_failure)) dismissLoadingDialog() } } } private var loadingUtils : BaseDialogUtils? = null //显示加载对话框 private fun showLoadingDialog(){ loadingUtils = BaseDialogUtils(requireActivity()) loadingUtils!!.builder() .hideCancel() .hideConfirm() .setCancelable(true) .setOnLoadingClick(object : BaseDialogUtils.OnLoadingClick{ override fun onClickCancel() { ToastUtils.makeText(requireActivity(), "对话框取消按钮") } override fun onClickConfirm() { ToastUtils.makeText(requireActivity(), "对话框确定按钮") } }) loadingUtils?.show() } //隐藏加载对话框 private fun dismissLoadingDialog(){ loadingUtils?.dismiss() } //登录 private fun login() { var isRegisterSuccess = DataStoreUtils.get(DataStoreParams.User.DS_OPENFILE_REGISTER, false) as Boolean TagUtils.d("login 开始 ${isRegisterSuccess}") account = login_account.text.toString() password = login_password.getText().toString() if (account?.length == 0 || password?.length == 0) { ToastUtils.makeText(requireActivity(), "帐号或密码不能为空") return } else if (!isRegisterSuccess && isRegisterBack){ var name = DataStoreUtils.get(DataStoreParams.User.DS_NICKNAME, "") as String if(TextUtils.isEmpty(name)){ name = account } //如果在openfire注册成功,没有同步到web服务器,点击登录按钮,则重新同步 var userBean = UserBean(account = account, name = name, nickName = name, address = "", email = "",phone = "", avatar = "", birthday = "", note = "") userViewModel.insertUser(userBean) } else { SoftInputUtils.hideSoftInput(login_account) showLoadingDialog() try { xmppService?.login(account, password) } catch (e : Exception) { e.printStackTrace() //ToastUtils.makeText(R.string.wc_login_failure) dismissLoadingDialog() } } } /*private fun initCall() { //在前台了,发送广播 调起权限判断弹窗 val viop = Intent() val intent: Intent = requireActivity().intent viop.putExtra("room", intent.getStringExtra("room")) viop.putExtra("audioOnly", intent.getBooleanExtra("audioOnly", false)) viop.putExtra("inviteId", intent.getStringExtra("inviteId")) viop.putExtra("inviteUserName", intent.getStringExtra("inviteUserName")) // viop.putExtra("msgId", intent.getLongExtra("msgId", 0)); viop.putExtra("userList", intent.getStringExtra("userList")) viop.action = Utils.ACTION_VOIP_RECEIVER viop.component = ComponentName(WcApp.getInstance().getPackageName(), VoipReceiver::class.java.getName()) WcApp.getInstance().sendBroadcast(viop) }*/ private fun processLoginResult(type : Int) { CoroutineScope(Dispatchers.Main).launch { dismissLoadingDialog() if(type == 1){ ToastUtils.makeText(R.string.wc_login_success) /*var isFromCall = requireActivity().intent.getBooleanExtra("isFromCall", false) if (isFromCall) { //无权限,来电申请权限会走这里 initCall() }*/ DataStoreUtils.get(DataStoreParams.Install.DS_FIRST_INSTALL, true) loginSusscess() } else { ToastUtils.makeText(R.string.wc_login_failure) } } } override fun onServiceConnected(p0: ComponentName?, binder: IBinder?) { TagUtils.d("LoginService 连接成功") var xmppBinder = binder as XmppService.XmppBinder xmppService = xmppBinder.service xmppService?.setCallback(object : XmppService.LoginCallback{ override fun onFailure() { //TagUtils.d("LoginFragment登录失败") processLoginResult(2) } override fun onSuccess(account: String, password: String) { TagUtils.d("LoginFragment登录成功") processLoginSuccess() } }) } override fun onServiceDisconnected(p0: ComponentName?) { TagUtils.d("LoginService onServiceDisconnected 断开连接") } private fun processLoginSuccess(){ CoroutineScope(Dispatchers.IO).launch { DataStoreUtils.put(DataStoreParams.User.DS_ACCOUNT, account!!) DataStoreUtils.put(DataStoreParams.User.DS_PASSWORD, password!!) DataStoreUtils.put(DataStoreParams.User.DS_USER_EXIST, false) //登录成功, 保存本地信息 var userBean = UserRepository.getUserByAccount(account!!) TagUtils.d("登录成功:${account} , ${password} , userBean = ${userBean}") // 设置用户名 WcApp.getInstance().username = account!! // 添加登录回调 SocketManager.getInstance().addUserStateCallback(object : IUserState{ override fun userLogin() { TagUtils.d("登录视频服务器成功login") } override fun userLogout() { TagUtils.d("登录视频服务器失败logout") } }) // 连接socket:登录 SocketManager.getInstance().connect(BaseUtils.WS, account!!, 0) processLoginResult(1) } } //登录成功 private fun loginSusscess() { Navigation.findNavController(btn_login).popBackStack(R.id.main_start_dest, true) EventBus.getDefault().post(EventMsgBean(EventMsgBean.TYPE_LOGIN_SUCCESS)) } override fun onResume() { super.onResume() getFocus() } private fun getFocus(){ view?.isFocusableInTouchMode = true view?.requestFocus() view?.setOnKeyListener { view, keyCode, event -> if(event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){ System.exit(1) } true } } }
fun login(account : String, password : String){ CoroutineScope(Dispatchers.IO).launch { try { if(!xmppConnection.isAuthenticated){ xmppConnection.connect() // 连接服务器 xmppConnection.login(account, password) // 连接服务器成功,更改在线状态 val presence = Presence(Presence.Type.available) xmppConnection.sendPacket(presence) TagUtils.d("用户登录状态:${xmppConnection.isAuthenticated} , ${xmppConnection.isConnected}") } if(xmppConnection.isAuthenticated){ WcApp.setXmppConnection(xmppConnection) ChatManagerUtils.getInstance().addChatListener() AddFileListener.addFileListerer() AddFriendsListener.addSubscriptionListener() checkConnectionListener = CheckConnectionListener() xmppConnection.addConnectionListener(checkConnectionListener) TagUtils.d("xmpp 管理连接成功 ") var result = UserService.getApi().getUser(account) TagUtils.d("syncUser 用户返回数据:${result}") if(result.isSuccess){ //插入本地数据库 UserRepository.insertUser(GsonUtils.getUserBean(result.data!!)) //处理同步信息 } else { //如果在openfire注册成功,没有同步到web服务器,或者web服务器已经不存在该账户,只能重新创建 var userBean = UserBean(account = account, name = account, nickName = account, address = "", email = "",phone = "", avatar = "", birthday = "", note = "") var userStr = Gson().toJson(userBean) UserRepository.insertUser(userBean) UserRepository.insertUserServer(userStr) } loginCallback?.onSuccess(account, password) } else { loginCallback?.onFailure() } } catch (e: Exception){ //ToastUtils.makeText(R.string.wc_login_failure) TagUtils.d("用户登录状态:${e.message}") e.printStackTrace() loginCallback?.onFailure() } } }
//登录成功,同步数据 private fun showLoadingDialog(){ var isFirstLogin = DataStoreUtils.get(DataStoreParams.User.DS_IS_FIRST_LOGIN, true) as Boolean //首次安装 if(!isFirstLogin){ return } var account = DataStoreUtils.getAccount() if(TextUtils.isEmpty(account)){ return } var view = LayoutInflater.from(this).inflate(R.layout.wc_sync_first_data, null) loadingUtils = BaseDialogUtils(this) loadingUtils!!.builder() .showView(view) .hideCancel() .hideConfirm() .setCancelable(false) loadingUtils?.show() SyncRepository.syncFirst(account, packageName, object : ICommonCallback{ override fun onFailure(obj: Any) { dismissLoadingDialog() } override fun onSuccess(obj: Any) { dismissLoadingDialog() } }) }