从零尝试IM聊天界面

移动端最没尝试的就属IM了,这次想拆出自己尝试的聊天界面记录下
还是基于kotlin开发

从零尝试IM聊天界面_第1张图片
WechatIMG2.jpeg

我觉得聊天有很多种,当然今天只说一对一的

从零尝试IM聊天界面_第2张图片
屏幕快照 2017-06-11 11.32.34.png

对于消息数据的存储跟检索需要定义一些枚举来方便自己
比如ItemType作为消息类型决定消息的是否发送或接收或时间line

enum class ItemType(var value:Int){
        Time(0),
        SendText(1),
        SendImg(2),
        ReceiveText(3),
        ReceiveImg(4)
    }

比如TimeType与ContentType
现在只区分昨天以前、昨天与今天
内容也只简单的区分文本与图片
当然等服务端IM正式联用会拓展功能的

enum class TimeType(var value: Int){
        Faraway(0),Yesterday(1),Today(2)
    }
    enum class ContentType(var value: Int){
        Text(0),Img(1)
    }

俩个所需的数据元类
ChatItem直接作为realm本地数据存储与recycleview显示的模型类(聊天这块的realm还未加入)

/**
 * Created by tanweiping on 16/12/29.
 */
data class ChatItem(var itemtype:ItemType, var sender:Contact?=null,
                        var itemcontent:String?=null, var timecontent:String?=null,
                        var timetype:TimeType?=TimeType.Today, var contentType:ContentType?=ContentType.Text)

    data class FucViewItem(var label:String,var iconcode:String,var iconcolor:String? = "#8399a6")

FucViewItem作为自定义改装的keyboard的功能view如下图

从零尝试IM聊天界面_第3张图片
WechatIMG3.jpeg

静态加入

//拍照 相册  语音输入 文件 位置
    val funcviewdata:List? by lazy {
        listOf(
                FucViewItem("拍照","\ue640","#00d1a0"),
                FucViewItem("相册","\ue64c","#368bfc"),
                FucViewItem("文件","\ue648","#00b7fa"),
                FucViewItem("位置","\ue806","#ffa243")
        )
    }
val datasource: ArrayList? by lazy {
        arrayListOf(
                ChatItem(ItemType.Time,timecontent = "2016-12-22"
                        ,timetype = TimeType.Faraway),
                ChatItem(ItemType.ReceiveText,itemcontent = "hello",timecontent = "2016-12-22"
                        ,timetype = TimeType.Faraway),
                ChatItem(ItemType.ReceiveText,itemcontent = "你好啊",timecontent = "2016-12-22"
                        ,timetype = TimeType.Faraway),
                ChatItem(ItemType.Time,timecontent = "2016-12-30"
                        ,timetype = TimeType.Today),
                ChatItem(ItemType.SendText,itemcontent = "我好的啊",timecontent = "2016-12-30"
                        ,timetype = TimeType.Today),
                ChatItem(ItemType.SendText,itemcontent = "hehe",timecontent = "2016-12-30"
                        ,timetype = TimeType.Today)
....
        )
    }

现在了解下childview的设置吧
titletv是自定义的头部view的标题view,接收上一个界面传来的需要chat的人或组织,righttv是使用iconfont的右部的功能button的Textview

titletv?.text = arguments.getString("title","")
righttv?.text = "\ue601"

下面加入keyboard的自定义的functionview部分
了解anko与kotlin的童鞋应该很容易就明白了吧

主要使用了anko的dsl与kotlin 函数式
funcviewdata数据源直接参与UI的构建

//拍照 相册  语音输入 文件 位置
        ek_bar?.addFuncView(context.verticalLayout {
            backgroundColor = Color.parseColor("#ecebf0")
            linearLayout {
                orientation = LinearLayout.HORIZONTAL
                weightSum = 4f
                padding = dip(10)
                    funcviewdata?.forEach {
                        verticalLayout {
                            isClickable = true
                            gravity = Gravity.CENTER_HORIZONTAL
                            textView {
                                gravity = Gravity.CENTER
                                text = it.iconcode
                                setPadding(dip(15),dip(10),dip(15),dip(5))
                                typeface = App.instance?.iconfont
                                textSize = 35f
                                textColor = Color.parseColor(it.iconcolor)
                                background = resources.getDrawable(R.drawable.func_border_radius)
                            }.lparams { width = wrapContent;height = wrapContent }
                            textView {
                                padding = dip(5)
                                gravity = Gravity.CENTER
                                text = it.label
                                textSize = 12f
                                textColor = resources.getColor(R.color.gray)
                            }
                        }.lparams { height = matchParent;width= matchParent;weight = 1f; }
                    }
            }.lparams { width = matchParent;height = wrapContent;weight = 1f }
            linearLayout {
                orientation = LinearLayout.HORIZONTAL
                weightSum = 4f
                padding = dip(10)
            }.lparams { width = matchParent;height = wrapContent;weight = 1f }
        })

设置recycleview了
垂直显示
stackFromEnd是数据从栈底开始显示
ChatPageAdapter稍等讲
还有个是发送信息的事件

val lp = LinearLayoutManager(context)
            lp.orientation = LinearLayoutManager.VERTICAL
            lp.stackFromEnd = true
            //lp.reverseLayout = true
            //lp.scrollToPosition(0)
        chat_content?.layoutManager = lp
        chat_content?.adapter = ChatPageAdapter(context,datasource!!)
        //chat_content?.adapter?.setHasStableIds(true)

        ek_bar?.btnSend?.onClick {
            datasource?.add(ChatItem(
                    ItemType.SendText,itemcontent = ek_bar?.etChat?.text.toString(),timecontent = "2016-12-30"
                    ,timetype = TimeType.Today)
            )
            chat_content?.adapter?.notifyItemInserted(datasource?.size!! - 1)
            chat_content?.smoothScrollToPosition(datasource?.size!!)
            ek_bar?.etChat?.setText("")
        }

俩个事件代理
head_frame是包裹着recycleview的容器,负责用户对于聊天记录的下拉获取往日的记录
doAsync是异步包裹

chat_content的touch事件是为了点击空白,将keyboard收回

head_frame?.setPtrHandler(object : PtrDefaultHandler() {
            override fun onRefreshBegin(frame: PtrFrameLayout) {
                doAsync {
                    sleep(1000)
                    uiThread {
                        frame.refreshComplete()
                    }
                }
            }
        })

        chat_content?.onTouch { _, motionEvent ->
            if (motionEvent.action == MotionEvent.ACTION_DOWN){
                ek_bar?.reset()
            }
            false
        }

下面介绍今天比较重要的adapter吧
相信大家在此之前已经很了解recycleview的adapter了,
所以简单来说主要 实现从Holder来说
暂时没考虑消息撤回
我定义了MsgViewHolder局部父类holder
TimeViewHolder时间label
ReceiveTextViewHolder,ReceiveImgViewHolder 接收msg的holder
SendTextViewHolder,SendImgViewHolder发出去的holder

open inner class MsgViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {}

    inner class TimeViewHolder(itemView: View?) : MsgViewHolder(itemView) {
        var timetv:TextView? = itemView?.find(pageIds.ItemTextId)
    }

    inner class ReceiveTextViewHolder(itemView: View?) : MsgViewHolder(itemView) {
        var userimg:ImageView? = itemView?.find(pageIds.ItemUserImgId)
        var receivetext:TextView? = itemView?.find(pageIds.ItemTextId)
    }
    inner class ReceiveImgViewHolder(itemView: View?) : MsgViewHolder(itemView) {}
    inner class SendTextViewHolder(itemView: View?) : MsgViewHolder(itemView) {
        var userimg:ImageView? = itemView?.find(pageIds.ItemUserImgId)
        var sendtext:TextView? = itemView?.find(pageIds.ItemTextId)
    }
    inner class SendImgViewHolder(itemView: View?) : MsgViewHolder(itemView) {}

覆写onBindViewHolder
我需要枚举item的Type来给予相应的数据绑定及事件
比如

val obj = datasource[position]
        when(getItemViewType(position)){
            OneToOneChatFragment.ItemType.Time.value->{
                with(holder as TimeViewHolder){
                    timetv?.text = obj.timecontent
                }
            }
            OneToOneChatFragment.ItemType.ReceiveText.value->{
                with(holder as ReceiveTextViewHolder){
                    Glide.with(context).load(R.mipmap.sf).into(userimg)
                    receivetext?.text = obj.itemcontent
                }
            }
            OneToOneChatFragment.ItemType.SendText.value->{
                with(holder as SendTextViewHolder){
                    Glide.with(context).load(R.mipmap.sf).into(userimg)
                    sendtext?.text = obj.itemcontent
                }
            }
        }

而onCreateViewHolder则是给予关于datasource的item的视图
比如

var layout:View? = null
        when(viewType){
            OneToOneChatFragment.ItemType.Time.value->{
                layout = context.relativeLayout {
                    gravity = Gravity.CENTER
                    padding = dip(5)
                    textView {
                        id = pageIds.ItemTextId
                        background = resources.getDrawable(R.drawable.chat_item_border_radius)
                        textColor = resources.getColor(R.color.bgColor_overlay)
                        textSize = 14f
                        padding = dip(5)
                    }.lparams { width = wrapContent;height = wrapContent }
                }
                layout.layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
                //parent?.addView(layout)
                return TimeViewHolder(layout)
            }
            OneToOneChatFragment.ItemType.ReceiveText.value->{
                layout = context.linearLayout {
                    orientation = LinearLayout.HORIZONTAL
                    padding = dip(15)
                    gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
                    imageView {
                        scaleType = ImageView.ScaleType.CENTER_CROP
                        id = pageIds.ItemUserImgId
                    }.lparams { width = dip(30);height = dip(30)}
                    include(R.layout.chat_receive_msg_text).lparams { padding = dip(5) }
                }
                layout.layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
                //parent?.addView(layout)
                return ReceiveTextViewHolder(layout)
            }

            OneToOneChatFragment.ItemType.SendText.value->{
                layout = context.linearLayout {
                    orientation = LinearLayout.HORIZONTAL
                    padding = dip(15)
                    gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
                    include(R.layout.chat_send_msg_text).lparams { padding = dip(5) }
                    imageView {
                        scaleType = ImageView.ScaleType.CENTER_CROP
                        id = pageIds.ItemUserImgId
                    }.lparams{ width = dip(30);height = dip(30)}
                }
                layout.layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
                //parent?.addView(layout)
                return SendTextViewHolder(layout)
            }
        }

       return MsgViewHolder(layout)
    }

下面是adapter的完整代码

/**
 * Created by tanweiping on 16/12/30.
 */
class ChatPageAdapter(var context:Context,var datasource: ArrayList) : RecyclerView.Adapter() {
    data class PageID(var ItemTextId:Int,var ItemImgId:Int,var ItemUserImgId:Int)
    val pageIds:PageID by lazy { PageID(R.id.msgcontent,R.id.msgcontent,10103) }
    override fun getItemViewType(position: Int): Int  = datasource[position].itemtype.value

    override fun getItemCount(): Int  = datasource.size

    override fun onBindViewHolder(holder: MsgViewHolder?, position: Int) {
        val obj = datasource[position]
        when(getItemViewType(position)){
            OneToOneChatFragment.ItemType.Time.value->{
                with(holder as TimeViewHolder){
                    timetv?.text = obj.timecontent
                }
            }
            OneToOneChatFragment.ItemType.ReceiveText.value->{
                with(holder as ReceiveTextViewHolder){
                    Glide.with(context).load(R.mipmap.sf).into(userimg)
                    receivetext?.text = obj.itemcontent
                }
            }
            OneToOneChatFragment.ItemType.SendText.value->{
                with(holder as SendTextViewHolder){
                    Glide.with(context).load(R.mipmap.sf).into(userimg)
                    sendtext?.text = obj.itemcontent
                }
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MsgViewHolder {
        var layout:View? = null
        when(viewType){
            OneToOneChatFragment.ItemType.Time.value->{
                layout = context.relativeLayout {
                    gravity = Gravity.CENTER
                    padding = dip(5)
                    textView {
                        id = pageIds.ItemTextId
                        background = resources.getDrawable(R.drawable.chat_item_border_radius)
                        textColor = resources.getColor(R.color.bgColor_overlay)
                        textSize = 14f
                        padding = dip(5)
                    }.lparams { width = wrapContent;height = wrapContent }
                }
                layout.layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
                //parent?.addView(layout)
                return TimeViewHolder(layout)
            }
            OneToOneChatFragment.ItemType.ReceiveText.value->{
                layout = context.linearLayout {
                    orientation = LinearLayout.HORIZONTAL
                    padding = dip(15)
                    gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
                    imageView {
                        scaleType = ImageView.ScaleType.CENTER_CROP
                        id = pageIds.ItemUserImgId
                    }.lparams { width = dip(30);height = dip(30)}
                    include(R.layout.chat_receive_msg_text).lparams { padding = dip(5) }
                }
                layout.layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
                //parent?.addView(layout)
                return ReceiveTextViewHolder(layout)
            }

            OneToOneChatFragment.ItemType.SendText.value->{
                layout = context.linearLayout {
                    orientation = LinearLayout.HORIZONTAL
                    padding = dip(15)
                    gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
                    include(R.layout.chat_send_msg_text).lparams { padding = dip(5) }
                    imageView {
                        scaleType = ImageView.ScaleType.CENTER_CROP
                        id = pageIds.ItemUserImgId
                    }.lparams{ width = dip(30);height = dip(30)}
                }
                layout.layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
                //parent?.addView(layout)
                return SendTextViewHolder(layout)
            }
        }

       return MsgViewHolder(layout)
    }
}

你可能感兴趣的:(从零尝试IM聊天界面)