Android开发基础——UI实践

本文介绍如何使用Android平台构建一个基本的聊天应用界面,包括使用9-Patch图片优化背景图、设置RecyclerView显示聊天记录、实现消息发送功能等。

这里编写一个聊天界面。

制作9-Patch图片

9-Patch图片是一种被特殊处理过的png图片,能够指定哪些区域可以被拉伸,哪些区域不可以。

 比如上面的图片,如果直接设置为背景图:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@drawable/message_left_original">

</LinearLayout>

程序运行结果为: 

 可以看到,由于图片宽度不足以填满整个屏幕的宽度,因此整张图片被拉伸了,这种显示效果肯定是不行的,此时需要用到9-Patch图片。

在该图片右击,选择Create 9-Patch file,然后保存,保存后双击图片会出现如下编辑界面:

 此时可以在图片的4个边框绘制一个个的小黑点,在上边框和左边框绘制的部分表示当图片需要拉伸时就拉伸黑点标记的区域,在下边框和右边框绘制的部分表示内容允许被放置的区域,使用鼠标在图片的边缘拖动就可以进行绘制了,按住shift键可进行擦除。绘制完成后为:

 重新运行程序的结果为:

 编写聊天界面

首先在app/build.gradle中添加依赖:

dependencies {

    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.recyclerview:recyclerview:1.2.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

然后修改activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#d8e0e8">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/inputText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Type something here"
            android:maxLines="2"
            tools:ignore="Suspicious0dp" />

        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send"/>

    </LinearLayout>
</LinearLayout>

上面的代码中,使用RecyclerView用于显示聊天内容,使用EditText输入消息,Button用于发送消息。

然后定义消息实体类,新建Msg:

class Msg(val content:String, val type:Int) {
    companion object {
        const val TYPE_RECEIVED = 0
        const val TYPE_SENT = 1
    }
}

上面Msg类中只有两个字段,content表示消息内容,type表示消息类型,其中消息类型有两个值:TYPE_RECEIVED表示收到的消息,TYPE_SENT表示发送的消息。

然后编写RecyclerView子项布局,新建msg_left_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">
    
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:background="@drawable/message_left">
    
        <TextView
            android:id="@+id/leftMsg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#fff"/>
        
    </LinearLayout>
</FrameLayout>

收到的消息是左对齐的。类似的,还要编写一个发送消息的子项布局,新建msg_right_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/message_right">

        <TextView
            android:id="@+id/rightMsg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#000"/>

    </LinearLayout>
</FrameLayout>

发送的消息是右对齐的。然后创建RecyclerView的适配器类,新建类MsgAdapter:

class MsgAdapter(val msgList: List<Msg>):
    RecyclerView.Adapter<RecyclerView.ViewHolder>(){

    inner class LeftViewHolder(view:View):RecyclerView.ViewHolder(view) {
        val leftMsg: TextView = view.findViewById(R.id.leftMsg)
    }

    inner class RightViewHolder(view:View):RecyclerView.ViewHolder(view) {
        val rightMsg: TextView = view.findViewById(R.id.rightMsg)
    }

    override fun getItemViewType(position: Int): Int {
        val msg = msgList[position]
        return msg.type
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (viewType == Msg.TYPE_RECEIVED) {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
        LeftViewHolder(view)
    } else {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
        RightViewHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val msg = msgList[position]
        when (holder) {
            is LeftViewHolder -> holder.leftMsg.text = msg.content
            is RightViewHolder -> holder.rightMsg.text = msg.content
            else -> throw IllegalArgumentException()
        }
    }
    override fun getItemCount() = msgList.size
}

上面代码中根据不同的viewType创建不同的界面。首先定义了LeftViewHolder和RightViewHolder这两个ViewHolder,分别用于缓存msg_left_item.xml和msg_right_item.xml布局中的控件。然后重写getItemViewType方法并返回当前position对应的消息类型。

然后在onCreateViewHolder方法总根据不同的viewType来加载不同的布局并创建不同的ViewHolder,然后在onBindViewHolder方法中判断ViewHolder的类型,如果是LeftViewHolder,就将内容显示到左边的消息布局,如果是RightViewHolder,就将内容显示到右边的消息布局。

然后修改Activity,为RecyclerView初始化数据,并加入按钮点击事件:

class MainActivity : AppCompatActivity(), View.OnClickListener {

    private val msgList = ArrayList<Msg>()

    private var adapter:MsgAdapter ?= null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMsg()
        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager = layoutManager
        adapter = MsgAdapter(msgList)
        recyclerView.adapter = adapter
        send.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when (v) {
            send -> {
                val content = inputText.text.toString()
                if (content.isNotEmpty()) {
                    val msg = Msg(content, Msg.TYPE_SENT)
                    msgList.add(msg)
                    adapter?.notifyItemInserted(msgList.size - 1)
                    recyclerView.scrollToPosition(msgList.size - 1)
                    inputText.setText("")
                }
            }
        }
    }

    private fun initMsg() {
        val msg1 = Msg("Hello,guy:", Msg.TYPE_RECEIVED)
        msgList.add(msg1)
        val msg2 = Msg("Hello,who is that?", Msg.TYPE_SENT)
        msgList.add(msg2)
        val msg3 = Msg("This is Tom. Nice to meet you.", Msg.TYPE_RECEIVED)
        msgList.add(msg3)
    }
}

上面代码中,现在initMsg方法中初始化了几条数据用于在RecyclerView中显示,然后按照标准方式构建RecyclerView,并为其指定了LayoutManager和适配器。

然后再发送按钮的点击事件中获取了EditText中的内容,如果内容不为空,就创建一个新的Msg对象添加到msgList列表中,然后调用适配器的notifyItemInserted方法用于通知列表有新的数据插入,这样新增的消息才能够在RecyclerView中显示出来。或者可以使用notifyDataSetChanged方法将所有可见元素刷新,不过该方法效率较差。然后调用scrollToPosition方法将显示的数据定位到最后一行,以保证一定可以看得到最后发出的一条消息。最后清空EditText的内容。

程序运行后的结果为:

Android UI开发专题 Android UI开发专题(一) 之界面设计 近期很多网友对Android用户界面的设计表示很感兴趣,对于Android UI开发自绘控件和游戏制作而言掌握好绘图基础是必不可少的。本次专题分10节来讲述,有关OpenGL ES相关的可能将放到以后再透露。本次主要涉及以下四个包的相关内容:  android.content.res 资源类   android.graphics 底层图形类   android.view 显示类   android.widget 控件类   一、android.content.res.Resources   对于Android平台的资源类android.content.res.Resources可能很多网友比较陌生,一起来看看SDK上是怎么介绍的吧,Contains classes for accessing application resources, such as raw asset files, colors, drawables, media or other other files in the package, plus important device configuration details (orientation, input types, etc.) that affect how the application may behave.平时用到的二进制源文件raw、颜色colors、图形drawables和多媒体文件media的相关资源均通过该类来管理。   int getColor(int id) 对应res/values/colors.xml   Drawable getDrawable(int id) 对应res/drawable/   XmlResourceParser getLayout(int id) 对应res/layout/   String getString(int id) 和CharSequence getText(int id) 对应res/values/strings.xml   InputStream openRawResource(int id) 对应res/raw/   void parseBundleExtra (String tagName, AttributeSet attrs, Bundle outBundle) 对应res/xml/   String[] getStringArray(int id) res/values/arrays.xml   float getDimension(int id) res/values/dimens.xml
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值