Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。
效果图:

实现代码:
/** * Author : wangning * Email : maoning20080809@163.com * Date : 2022/5/23 16:00 * Description : 朋友圈 */ class DiscoverMomentsFragment : BaseDataBindingFragment(), OnRefreshListener, OnLoadMoreListener { override fun getLayoutRes() = R.layout.wc_discover_moments private val momentsViewModel : MomentsViewModel by viewModels() private val userViewModel : UserViewModel by viewModels() private var navController: NavController? = null private val REQ_PICTURE_CODE = 101 private val REQ_VIDEO_CODE = 102 private var momentsAdapter : DiscoverMomentsAdapter? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) if(!EventBus.getDefault().isRegistered(this)){ EventBus.getDefault().register(this) } swipeToLoadLayout.setOnRefreshListener(this) swipeToLoadLayout.setOnLoadMoreListener(this) var account = DataStoreUtils.getAccount() userViewModel.getUserLocal(account) navController = findNavController() navController?.currentBackStackEntry?.savedStateHandle?.getLiveData (CommonUtils.Moments.PUBLISH_SUCCESS)?.observe(viewLifecycleOwner){ TagUtils.d("朋友圈发布返回") if(it){ momentsViewModel.getMomentsListLocalEvent() } } TagUtils.d("朋友圈重新主页 ") var momentsList = mutableListOf () //第一行默认写入个人信息 momentsList.add(MomentsBean()) momentsAdapter = DiscoverMomentsAdapter(momentsList, PopupWindowClick()) userViewModel.userBeanLocal.observe(viewLifecycleOwner){ TagUtils.d("4朋友圈返回用户$it") if(it == null) return@observe momentsAdapter?.setUserBean(it) } //使用EventBus刷新数据 momentsViewModel.getMomentsListLocalEvent() var linearLayoutManager = LinearLayoutManager(requireActivity()) linearLayoutManager.orientation = LinearLayoutManager.VERTICAL swipe_target.layoutManager = linearLayoutManager swipe_target.adapter = momentsAdapter //先加载本地,然后刷新服务器的数据 momentsViewModel.getMomentsListServerEvent() } @Subscribe(threadMode = ThreadMode.MAIN) fun onMessageCallback(momentsEventBean: MomentsEventBean) { if(momentsEventBean == null){ return } if(momentsEventBean.list == null || momentsEventBean.list.size < 1){ return } var list: MutableList = momentsEventBean.list if(momentsEventBean.type == MomentsEventBean.TYPE_LOCAL_LIST){ var momentsList = mutableListOf () //第一行默认写入个人信息 momentsList.add(MomentsBean()) TagUtils.d("DiscoverFragment onMessageCallback 个数${list.size} ") momentsList.addAll(list) momentsAdapter?.refresh(momentsList) } else { CoroutineScope(Dispatchers.IO).launch { //从服务器获取到数据,插入本地数据库 MomentsRepository.insertMomentListLocal(list) //查询最新的刷新页面 momentsViewModel.getMomentsListLocalEvent() } } } override fun onDestroyView() { super.onDestroyView() TagUtils.d("DiscoverFragment onDestroyView") EventBus.getDefault().unregister(this) } inner class PopupWindowClick : WcOnItemClickInterface { override fun onItemClick(obj: Any) { showPopupWindow() } } private fun showPopupWindow(){ var popupView = layoutInflater.inflate(R.layout.wc_moments_pop_view , moment_root, false) var popupWindow = PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true) popupWindow.showAtLocation(moment_root, 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 (R.id.moments_pop_video).setOnClickListener { popupWindow.dismiss() var bundle = bundleOf(CameraFragment.TYPE_ENTER to CameraFragment.TYPE_MOMENT) navController?.navigate(R.id.action_svideo_camera, bundle) } //选择相册 popupView.findViewById (R.id.moments_pop_album).setOnClickListener { popupWindow.dismiss() openAblum() } //选择小视频 popupView.findViewById (R.id.moments_pop_svideo).setOnClickListener { popupWindow.dismiss() openVideo() } //取消 popupView.findViewById (R.id.moments_pop_cancel).setOnClickListener { popupWindow.dismiss() } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQ_PICTURE_CODE && data != null) { val images = data.getStringArrayListExtra(ImageSelector.SELECT_RESULT) val isCameraImage = data.getBooleanExtra(ImageSelector.IS_CAMERA_IMAGE, false) TagUtils.d("ImageSelector", "是否是拍照图片:" + isCameraImage) images?.map { TagUtils.d("返回图片:${it}") } if(images != null && images.size > 0) { var bundle = bundleOf(CommonUtils.Moments.TYPE_NAME to CommonUtils.Moments.TYPE_PICTURE, CommonUtils.Moments.TYPE_IMAGE_PATH to images) navController?.navigate(R.id.action_moments_publish, bundle) } else { TagUtils.d("请选择图片") } } else if (requestCode == REQ_VIDEO_CODE && resultCode == RESULT_OK && null != data) { var selectedVideo = data.getData()!! var filePathColumn = arrayOf(MediaStore.Video.Media.DATA) var cursor : Cursor? = requireActivity().contentResolver.query(selectedVideo, filePathColumn, null, null, null); cursor?.moveToFirst(); var columnIndex : Int? = cursor?.getColumnIndex(filePathColumn[0]); var videoPath = cursor?.getString(columnIndex!!); cursor?.close(); TagUtils.d("选择小视频路径:${videoPath}") var bundle = bundleOf(CommonUtils.Moments.TYPE_NAME to CommonUtils.Moments.TYPE_VIDEO, CommonUtils.Moments.TYPE_VIDEO_PATH to videoPath) navController?.navigate(R.id.action_moments_publish, bundle) } } fun openAblum(){ // 打开相册 ImageSelector.builder() .useCamera(false) // 设置是否使用拍照 .setSingle(false) //设置是否单选 .canPreview(true) //是否点击放大图片查看,,默认为true .start(this,REQ_PICTURE_CODE) } //选择小视频 fun openVideo(){ val i = Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) startActivityForResult(i, REQ_VIDEO_CODE) } override fun onRefresh() { CoroutineScope(Dispatchers.Main).launch { delay(200) swipeToLoadLayout.isRefreshing = false } } override fun onLoadMore() { CoroutineScope(Dispatchers.Main).launch { delay(200) swipeToLoadLayout.isLoadingMore = false } } }
/** * Author : wangning * Email : maoning20080809@163.com * Date : 2022/5/26 17:55 * Description : 朋友圈适配器 */ class DiscoverMomentsAdapter(var list : MutableList, var userClick : WcOnItemClickInterface) : RecyclerView.Adapter (){ private var userBean : UserBean? = null override fun getItemCount() = list.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiscoverMomentsHolder { var view = LayoutInflater.from(parent.context).inflate(R.layout.wc_discover_moments_item, parent, false) return DiscoverMomentsHolder(view) } override fun onBindViewHolder(holder: DiscoverMomentsHolder, position: Int) { //TagUtils.d("朋友圈position = ${position} , ${userBean}" ) if(position == 0){ //朋友圈个人头像信息 holder.personalView.visibility = View.VISIBLE holder.momentsView.visibility = View.GONE userBean?.let { holder.personalView.process(it, userClick) } } else { holder.personalView.visibility = View.GONE holder.momentsView.visibility = View.VISIBLE holder.momentsView.process(list.get(position)) } } fun setUserBean(userBean: UserBean) { this.userBean = userBean notifyDataSetChanged() } //下拉刷新 fun refresh(list : MutableList ){ this.list = list notifyDataSetChanged() } //加载更多 fun add(list : MutableList ){ this.list.addAll(list) notifyDataSetChanged() } class DiscoverMomentsHolder(itemView : View) : RecyclerView.ViewHolder(itemView) { var momentsView = itemView.findViewById (R.id.discover_moments_item_view) var personalView = itemView.findViewById (R.id.discover_moments_item_personal_view) } }
/**
* Author : wangning
* Email : maoning20080809@163.com
* Date : 2022/5/26 15:29
* Description : 朋友圈自定义view
*/
class DiscoverMomentsView : RelativeLayout{
constructor(context: Context) : this(context, null)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr : Int) : super(context, attributeSet, defStyleAttr)
init {
LayoutInflater.from(context).inflate(R.layout.wc_discover_moments_view, this)
}
fun process(momentsBean: MomentsBean){
momentsBean?.let {
discover_moments_content.text = it.content
discover_moments_time.text = CommonUtils.Date.getCurrentDate(it.addTime)
BaseUtils.showAvatar(momentsBean.account, discover_moments_icon, discover_moments_name)
if(it.type == CommonUtils.Moments.TYPE_PICTURE){
//图片
discover_moments_recyclerview.visibility = View.VISIBLE
discover_moments_video.visibility = View.GONE
discover_moments_video_icon.visibility = View.GONE
//TagUtils.d("朋友圈图片:" + it.images)
var imagesList = it.images.split(CommonUtils.Moments.FILE_PATH_MARK)
var adapter = MomentsPublishAdapter(2, imagesList, object : WcOnItemClickInterface{
override fun onItemClick(obj: Any) {
TagUtils.d("点击:${obj}")
var imagePath = obj as String
var bundle = bundleOf(CommonUtils.Moments.TYPE_IMAGE_PATH to imagePath,
CommonUtils.Moments.TYPE_NAME to CommonUtils.Moments.TYPE_PICTURE,
CommonUtils.Chat.IS_HIDE_CONFIRM to true)
Navigation.findNavController(discover_moments_recyclerview).navigate(R.id.action_svideo_play, bundle)
}
})
var linearLayoutManager = GridLayoutManager(context , 3)
//linearLayoutManager.orientation = LinearLayoutManager.HORIZONTAL
discover_moments_recyclerview.layoutManager = linearLayoutManager
discover_moments_recyclerview.adapter = adapter
} else if(it.type == CommonUtils.Moments.TYPE_VIDEO){
//小视频
discover_moments_recyclerview.visibility = View.GONE
if(File(momentsBean.videoLocal).exists()){
TagUtils.d("本地小视频存在:${momentsBean.videoLocal}")
//小视频文件存在, 直接显示播放
discover_moments_video_icon.visibility = View.GONE
discover_moments_video.visibility = View.VISIBLE
playVideo(momentsBean.videoLocal)
discover_moments_video.setOnClickListener {
showVideo(discover_moments_video, momentsBean.videoLocal)
}
} else {
//小视频文件不存在, 默认先显示缩略图, 然后下载
discover_moments_video_icon.visibility = View.VISIBLE
discover_moments_video.visibility = View.GONE
GlideUtils.load(discover_moments_video_icon, CommonUtils.Base.getReallyImage(momentsBean.images))
var videoUrl = CommonUtils.Moments.getReallyImageUrl(momentsBean.video)
TagUtils.d("小视频地址:${momentsBean.video} , ${videoUrl}")
TagUtils.d("小视频缩略图:${momentsBean.images} , ${CommonUtils.Base.getReallyImage(momentsBean.images)}")
var videoFile = FileUtils.getBaseFile(momentsBean.video)
VideoDownloadManager.download(videoUrl, videoFile, object : VideoDownloadInter{
override fun onDone(filePath: String) {
TagUtils.d("小视频下载完成:${filePath}")
CoroutineScope(Dispatchers.Main).launch {
discover_moments_video_icon.visibility = View.GONE
discover_moments_video.visibility = View.VISIBLE
momentsBean.videoLocal = filePath
playVideo(filePath)
CoroutineScope(Dispatchers.IO).launch {
//更新到本地数据库
MomentsRepository.updateMomentLocal(momentsBean)
}
discover_moments_video.setOnClickListener {
showVideo(discover_moments_video, momentsBean.videoLocal)
}
}
}
override fun onError() {
CoroutineScope(Dispatchers.Main).launch {
TagUtils.d("小视频下载失败:${momentsBean.video}")
}
}
override fun onProgress(process: Int) {
}
})
}
} else {
//文字
discover_moments_recyclerview.visibility = View.GONE
discover_moments_video.visibility = View.GONE
}
}
}
//点击查看小视频
private fun showVideo(view: View, videoPath : String){
var bundle = bundleOf(CommonUtils.Moments.TYPE_VIDEO_PATH to videoPath,
CommonUtils.Moments.TYPE_NAME to CommonUtils.Moments.TYPE_VIDEO,
CommonUtils.Chat.IS_HIDE_CONFIRM to true)
Navigation.findNavController(view).navigate(R.id.action_svideo_play, bundle)
}
/**
* 播放小视频
* @param filePath String
*/
private fun playVideo(filePath : String){
discover_moments_video.setVideoPath(filePath)
discover_moments_video.requestFocus()
discover_moments_video.start()
discover_moments_video.setOnCompletionListener {
discover_moments_video.start()
}
}
}