第三章 UI开发的点点滴滴
3.1 常用控件的使用方法
3.1.1 TextView
-
概述:TextView可以说是Android中最简单的一一个控件了,你在前面其实已经和它打过一些交道了。
它主要用于在界面上显示一段文本信息。 -
控件内容:
-
android : id 给当前控件定义唯一标识符
-
android : layout_width 指定控件的宽度
-
android : layout_height 指定控件的高度
补充:2.3两个属性所有Android控件都有,可选值有三种:match_ parent(和父布局一样)、 fill_ parent和Wrap_ content (包裹内容)。
-
android : text 指定TextView中显示的文本内容
-
android : gravity 指定文字对齐方式,可选值:top、bottom(底部)、left
、 right、center等,可以用“|”来同时指定多个值 -
android : textSize 指定文字的大小,字体大小单位为sp
-
android : textColor 指定文字的颜色
-
3.1.2 Button
-
概述:Button是一个用户界面对象,在单击时向目标发送操作消息。
-
控件内容:
- android : text 指定按钮中显示的文本内容
- android : id 给当前控件定义唯一标识符
- android : textAllCaps 是否对按钮显示的文本内容进行大写转换
-
实现View.OnClickListener类,重写onClick(View v)方法;Button对象调用setOnClickListener(this)方法时直接传活动本身
3.1.3 EditText
-
概述:EditText是程序用于和用户进行交互的另一个重要控件,它允许用户在控件里输人和编辑内容,并可以在程序中对这些内容进行处理。
-
控件内容:
- android : hint 指定一段提示性的文本
- Android : maxLines 指定EditText最大行数
-
操作:
- EditText对象.getText().toString()将所输入文本转换为字符串
3.1.4 ImageView
1. 概述:ImageView是用于在界面上展示图片的一个控件,它可以让我们的程序界面变得更加丰富多彩。
2. 控件内容:
1. android : src 给ImageView指定了一张图片
3. 操作:
1. imageView.setImageResource(参数) 更改布局中所指定的图片或添加一张照片在此ImageView布局中,参数为照片的路径(通过引用传递)
3.1.5 ProgressBar
-
概述:ProgressBar用于在界面上显示一个进度条,表示我们的程序正在加载一些数据。
-
控件内容:
-
android : visibility 所有Android控件都具有这个属性,表示控件的可见性
可选值:visible表示控件时可见的,这个值时默认值,不指定visible时,控件都是可见的, invisible表示控件不可见,但他仍然占据着原来的位置和大小 , gone表示控件不仅不可见,而且不再占用任何屏幕空间
-
android : max 给进度条设置一个最大值,然后在代码中动态的更改进度条的进度
-
style="?android:attr/progressBarStyleHorizontal" 指定进度条为条形进度条
-
-
操作:
- ProgressBar对象.setVisiviblity(参数) 更改布局中该控件的可见性属性
- ProhressBar对象.setProgress(参数) 更改布局中进度跳的大小,初始化为0
- ProgressBar对象.getProgress() 获取当前进度条的进度
3.1.6 AlertDialog
-
概述:AlertDialog可以在当前的界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,
能够屏蔽掉其他控件的交互能力,因此AlertDialog 一般都是**用于提示一些非常重要的内容或者警告信息。**比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。 -
操作代码:
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this); dialog.setTitle("This is a Dialog"); dialog.setCancelable(false); dialog.setMessage("something important"); dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }); dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }); dialog.show();
3.1.7 ProgressDialog
-
概述:ProgressDialog和AlertDialog有点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其
他控件的交互能力。不同的是,ProgressDialog 会在对话框中显示一个进度条,一般用于表示当
前操作比较耗时,让用户耐心地等待。 -
操作代码:
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setTitle("This is a ProgressDialog"); progressDialog.setMessage("Loading……"); progressDialog.setCancelable(true); //设置是否可以通过返回键退出此布布局 progressDialog.show();
-
补充:注意,如果在setCancelable()中传人了false, 表示ProgressDialog 是不能通过Back键
取消掉的,这时你就一定要在代码中做好控制,当数据加载完成后必须要调用ProgressDialog 的
dismiss()方法来关闭对话框,否则ProgressDialog 将会一直存在。
3.2 详解四种布局
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1cGf4K8-1627825381155)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20210723094201415.png)]
3.2.1 线性布局
-
概述:LinearLayout又称作线性布局,是一种非常常用的布局。正如它的名字所描述的一样,这个布局会将它所包含的控件在线性方向上依次排列。相信你之前也已经注意到了,我们在上一节中
学习控件用法时,所有的控件就都是放在LinearLayout布局里的,因此上一节中的控件也确实是
在垂直方向.上线性排列的。 -
注意:
-
这里需要注意,如果LinearLayout 的排列方向是horizontal, 内部的控件就绝对不能将宽度指定为match_ parent, 因为这样的话,单独一个控件就会将整个水平方向占满,其他的控件就
没有可放置的位置了。同样的道理,如果LinearLayout的排列方向是vertical,内部的控件就不能
将高度指定为match _parent。 -
当
LinearL ayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上
的对齐方式。同样的道理,当LinearLayout的排列方向是vertical 时,只有水平方向上的对齐方式才会生效
-
-
布局内容:
- android : weigth 指定控件在布局中所占的比例
- android : layout_gravity 指定布局的对其方式
3.2.2 相对布局
- 概述:RelativeLayout又称作相对布局,也是一种非常常用的布局。和LinearLayout 的排列规则不同,RelativeLayout 显得更加随意一些, 它可以通过相对定位的方式让控件出现在布局的任何位置。也正因为如此,RelativeLayout 中的属性非常多,不过这些属性都是有规律可循的,其实并不难理解和记忆。
- 理解:两种相对,一种相对父布局,一种相对控件
3.2.3 帧布局
- 概述:FrameLayout又称作帧布局,它相比于前面两种布局就简单太多了,因此它的应用场景也少了很多。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角。
3.2.4 百分比布局(被弃用,改用约束布局)
- 概述:ConstraintLayout
3.3 创建自定义布局
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-na1qSBXj-1627825381158)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20210723145556015.png)]
- 概述:可以看到,我们所用的所有控件都是直接或间接继承自View 的,所用的所有布局都是直接
或间接继承自ViewGroup 的。View 是Android中最基本的一种UI组件,它可以在屏幕上绘制一
块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基
础之上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View
和子ViewGroup,是-一个用于放置控件和布局的容器。
3.3.1 引入布局
-
概述:一般我们的程序中可能有很多个活动都需要这样的标题栏,如果在每个活动的布局中都编
写一遍同样的标题栏代码,明显就会导致代码的大量重复。这个时候我们就可以使用引入布局的
方式来解决这个问题,新建一个布 局title.xml,代码如下所示: -
优点:减少重复代码的编写,解决重复编写布局代码的问题
-
布局内容:
- android : layout_margin 指定控件在上下左右偏移的距离(四个方向同时偏移)
- android : layout_marginTop 指定控件与上偏移的距离
-
将布局引入:
<include layout = "@layout/title"/>
-
将原有标题隐藏方法
setContentView(R.layout.activity_main); ActionBar actionbar = getSupportActionBar(); if(actionbar != null) { actionbar.hide(); }
3.3.2 自定义控件
-
实现思路:
-
新建一个类将含有控件加载进此类中,此类继承LinearLayout(继承此类可对控件进行操作)
实现代码:
public class TitleLayout extends LinearLayout { public TitleLayout(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.title , this); } }
讲解:首先我们重写了LinearLayout 中带有两个参数的构造函数,在布局中引人TitleLayout控件就会调用这个构造函数。然后在构造函数中需要对标题栏布局进行动态加载,这就要借助
LayoutInflater来实现了。通过LayoutInflater 的from()方法可以构建出一个LayoutInflater对
象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法接收两个参数,第
一个参数是要加载的布局文件的id,这里我们传入R.layout.title, 第二个参数是给加载好的布局 再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this。 -
在布局文件中添加这个自定义控件
实现代码:
<com.example.uiwidgettest.TitleLayout android:layout_height="wrap_content" android:layout_width="match_parent" />
-
给控件注册点击事件
代码实现:
Button titleBack = (Button) findViewById(R.id.title_back); Button titleEdit = (Button) findViewById(R.id.title_edit); titleBack.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { ((Activity) getContext()).finish(); } }); titleEdit.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getContext(),"You clicked Edit button", Toast.LENGTH_SHORT).show(); } });
-
3.4 最常用和最难用的控件——ListView
- 概述:ListView绝对可以称得上是Android中最常用的控件之一,几乎所有的应用程序都会用到它。
由于手机屏幕空间都比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量
的数据需要展示的时候,就可以借助ListView 来实现。ListView 允许用户通过手指上下滑动的方
式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。
3.4.1 ListView的简单用法
-
使用步骤:
-
先在布局中注册一个ListView控件
代码如下:
<ListView android:id="@+id/List_view" android:layout_width="match_parent" android:layout_height="match_parent" />
-
创建一个适配器,因为数组中的内容无法传递到ListVIew中,ArrayAdapter(适配器类)
代码如下:
ArrayAdapter<String> adapter = new ArrayAdapter<> (MainActivity.this,android.R.layout.simple_list_item_1, data);
-
通过适配器将数据传递给ListView对象
代码如下:
ListView listView = (ListView) findViewById(R.id.List_view); listView.setAdapter(adapter);
-
3.4.2 定制ListView的界面
-
使用步骤:
-
自定义一个类用于存放ListView中个控件得Id或文本内容
package com.example.listview2;public class Fruit { private String name; //TextView显示的内容 private int imageId; //图片的引用 public Fruit(String name, int imageId) { this.name = name; this.imageId = imageId; } public String getName() { return name; } public int getImageId() { return imageId; }}
-
编写一个显示自定义内容的适配器(没看懂),自定义适配器继承ArrayAdapter<>
package com.example.listview2; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.List; public class FruitAdapter extends ArrayAdapter<Fruit> { private int resourceId; public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) { //编写自定义适配器的构造器 super(context,textViewResourceId,objects); //是由父类的构造器,父类为ArrayAdapter有三个参数 resourceId = textViewResourceId; //获取自定义布局的引用值(引用值好像一般编译器会自动分配一个int值,以此值匹配引用值) } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { Fruit fruit = getItem(position); //获得此位置的Fruit对象,理解:ListView会将所传入数据进行排列,为其分配对性的position值,当滑动界面时,每次都会调用getView方法刷新界面,新的position进入界面,获取此位置Fruit对象,为此加载对应布局,以实现布局的滚动 View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false); //动态加载布局,原有加载方式应该是在活动布局文件中进行添加 ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image); //获取布局中的控件信息 TextView fruitName = (TextView) view.findViewById(R.id.fruit_name); fruitImage.setImageResource(fruit.getImageId()); //通过Fruit对象中存储的信息对控件进行设置 fruitName.setText(fruit.getName()); return view; } }
-
在主活动中完成内容的初始化和通过自定义适配器实现自定义ListView的显示
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initFruits(); FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); }
-
3.4.3 提升KListView的运行效率
-
目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。
仔细观察会发现,getView()方法中还有一个convertView 参数,这个参数用于将之前加
载好的布局进行缓存,以便之后可以进行重用。 -
代码实现:
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { //每加载一条信息执行一次此方法 Fruit fruit = getItem(position); View view; ViewHolder viewHolder; if(convertView == null) { view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false); viewHolder = new ViewHolder(); viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image); viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name); view.setTag(viewHolder); //将viewHolder存储到view中 }else { view = convertView; viewHolder = (ViewHolder) view.getTag(); //将view中的viewHolder拿出 } viewHolder.fruitImage.setImageResource(fruit.getImageId()); viewHolder.fruitName.setText(fruit.getName()); return view; } class ViewHolder { //用于存储布局信息 ImageView fruitImage; TextView fruitName; }
3.4.4 ListView的点击事件
-
通过调用setOnItemClickListener()方法为ListView注册了一个监听器,用户点击了ListView 中的任何一个子项时,就会回调onItemClick()方法。在这个方法中可以通过position参数判断出用户点击的是哪一个子项。
-
实现代码:(在主活动中)
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Fruit fruit = fruitList.get(position); Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show(); } });
3.5 更强大的滚动控件——RecyclerView
3.5.1 RecyclerView的基本用法
-
实现步骤:
-
添加依赖:打开app/build.gradle文件,在dependencies中添加
implementation 'androidx.recyclerview:recyclerview:1.2.1'
-
在布局中添加RecyclerView控件,添加 代码如下:
<androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/recycler_view" />
-
为RecyclerView准备一个适配器:
package com.example.recyclerview; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.List; public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { private List<Fruit> mFruitList; static class ViewHolder extends RecyclerView.ViewHolder { //传入View参数,这个参数通常就是RecyclerView子项的最外层布局了,可通过findViewById()方法获得控件实例(此例为ImageView和TextView) ImageView fruitImage; TextView fruitName; public ViewHolder(View view) { super(view); fruitImage = (ImageView) view.findViewById(R.id.fruit_image); fruitName = (TextView) view.findViewById(R.id.fruit_name); } } public FruitAdapter(List<Fruit> fruitList) { //适配器构造方法,传入数据源 mFruitList = fruitList; } //由于继承了RecycleView.Adapter,所以需要重写下面三个方法 @Override //创建ViewHolder public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false); ViewHolder holder = new ViewHolder(view); return holder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { //用于对RecyclerView子项的数据进行赋值,会在每项子项被滚动到屏幕内的时候被执行,通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。 Fruit fruit = mFruitList.get(position); holder.fruitImage.setImageResource(fruit.getImageId()); holder.fruitName.setText(fruit.getName()); } @Override public int getItemCount() { //告诉RecyclerView一共有多少子项 return mFruitList.size(); } }
-
在主活动中设置RecycolerView实例:
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager(this); //用于指定RecyclerView的布局方式 recyclerView.setLayoutManager(layoutManager); FruitAdapter adapter = new FruitAdapter(fruitList); recyclerView.setAdapter(adapter);
-
3.5.2 实现横向滚动和瀑布流布局
-
横向滚动实现步骤:(在上一代码基础上)
-
设置子布局的布局格式:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="100dp" //设置布局的宽度 android:layout_height="wrap_content" //表示此布局高为包裹内容 android:orientation="vertical" //设置布局排列方式为垂直排列 > <ImageView android:id="@+id/fruit_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal"/> //表示此控件在布局水平居中 <TextView android:id="@+id/fruit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" //表示此空间在此布局中水平居中 android:layout_marginTop="10dp" /> //与上一控件间隔10dp </LinearLayout>
-
在主活动中设置RecyclerView的排列格式:
initFruits(); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager(this); layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); //设置排列格式为水平 recyclerView.setLayoutManager(layoutManager); FruitAdapter adapter = new FruitAdapter(fruitList); recyclerView.setAdapter(adapter);
-
-
瀑布流布局实现步骤:
-
设置子布局的布局格式:
<?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="wrap_content" android:layout_margin="5dp" android:orientation="vertical"> <ImageView android:id="@+id/fruit_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> <TextView android:id="@+id/fruit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left" //设置文本显示居左 android:layout_marginTop="10dp" /> </LinearLayout>
-
在主活动中设置RecyclerView的排列格式:
`@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initFruits(); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL); //设置瀑布流代码,第一个参数为指定布局列数,第二个参数指定布局的排列方向 //LinearLayoutManager layoutManager = new LinearLayoutManager(this); //layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); recyclerView.setLayoutManager(layoutManager); FruitAdapter adapter = new FruitAdapter(fruitList); recyclerView.setAdapter(adapter); }
-
3.5.3 RecyclerView的点击事件
-
概述:不同于ListView的是, RecyclerView并没有提供类似于setOnItemClickListener()
这样的注册监听器方法,而是需要我们自己给**子项具体的View(注册给子项布局)**去注册点击事件,相比于ListView
来说,实现起来要复杂一些。 -
实现代码:(在适配器中)
package com.example.recyclerview; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.List; public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { private List<Fruit> mFruitList; static class ViewHolder extends RecyclerView.ViewHolder { View fruitView; //定义去哪句的一个View对像 ImageView fruitImage; TextView fruitName; public ViewHolder(View view) { super(view); fruitView = view; //获得子项View对象 fruitImage = (ImageView) view.findViewById(R.id.fruit_image); fruitName = (TextView) view.findViewById(R.id.fruit_name); } } public FruitAdapter(List<Fruit> fruitList) { mFruitList = fruitList; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false); final ViewHolder holder = new ViewHolder(view); //为子项布局的设置点击事件 holder.fruitView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int position = holder.getAdapterPosition(); //为啥会有此方法 Fruit fruit = mFruitList.get(position); Toast.makeText(view.getContext(), "you clicked view" + fruit.getName(), Toast.LENGTH_SHORT).show(); } }); holder.fruitImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int position = holder.getAdapterPosition(); Fruit fruit = mFruitList.get(position); Toast.makeText(view.getContext(), "you clicked image" + fruit.getName(), Toast.LENGTH_SHORT).show(); } }); return holder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { Fruit fruit = mFruitList.get(position); holder.fruitImage.setImageResource(fruit.getImageId()); holder.fruitName.setText(fruit.getName()); } @Override public int getItemCount() { return mFruitList.size(); } }
3.6 编写界面的最佳实践
3.6.1 制作Nine-Patch图片
-
先添加一张图片,然后在导航栏点击File →Open 9-patch ,将message_ left.png 加载进来,如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cIJCfdGk-1627825381160)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20210729091632381.png)]
使用步骤:通过点击边框设置宽和高不够时被拉伸的部分,按住Shift键拖动可以尽心那个擦除
3.6.2 编写精美的聊天界面
-
实现步骤:
-
通过RecyclerView用于显示聊天的消息内容
RecyclerView的子项布局代码:(显示接收到的消息和发送的消息)
<?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="wrap_content" android:orientation="vertical" android:padding="10dp"> <LinearLayout android:id="@+id/left_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left" android:background="@drawable/message_left"> <TextView android:id="@+id/left_msg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="10dp" android:textColor="#fff" /> </LinearLayout> <LinearLayout android:id="@+id/right_layout" android:background="@drawable/message_right" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:layout_margin="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/right_msg" android:layout_gravity="center" android:layout_margin="10dp"/> </LinearLayout> </LinearLayout>
-
定义消息的实体类:
package com.example.uibestpractice; public class Msg { public static final int TYPE_RECEIVED = 0; //标记用于判断是输出消息还是接收消息 public static final int TYPE_SENT = 1; private String content; //存储消息信息 private int type; //存储判断信息 public Msg(String content, int type) { this.content = content; this.type = type; } public String getContent() { return content; } public int getType() { return type; } }
-
创建RecyclerView的适配器,新建类MsgAdapator
package com.example.uibestpractice; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.List; public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> { private List<Msg> mMsgList; static class ViewHolder extends RecyclerView.ViewHolder { //创建内部类用于初始化子布局和控件信息 LinearLayout leftLayout; LinearLayout rightLayout; TextView leftMsg; TextView rightMsg; public ViewHolder(View view) { super(view); leftLayout = (LinearLayout) view.findViewById(R.id.left_layout); rightLayout = (LinearLayout) view.findViewById(R.id.right_layout); leftMsg = (TextView) view.findViewById(R.id.left_msg); rightMsg = (TextView) view.findViewById(R.id.right_msg); } } public MsgAdapter(List<Msg> msgList) { //接收用户传输的信息 mMsgList = msgList; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //动态加载布局 View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { //设置响应事件 Msg msg = mMsgList.get(position); if(msg.getType() == Msg.TYPE_RECEIVED) { //接受信息显示左布局,隐藏右布局 holder.leftLayout.setVisibility(View.VISIBLE); holder.rightLayout.setVisibility(View.GONE); holder.leftMsg.setText(msg.getContent()); //用右布局的TextView显示数据 }else if(msg.getType() == Msg.TYPE_SENT) { //输入信息显示右布局,隐藏左布局 holder.leftLayout.setVisibility(View.GONE); holder.rightLayout.setVisibility(View.VISIBLE); holder.rightMsg.setText(msg.getContent()); //用左布局的TextView显示数据 } } @Override public int getItemCount() { return mMsgList.size(); } }
-
修改MainActivity中的代码,来为RecyclerView初始化一些数据, 并给发送按钮加入事件响应,代码如下所示:
package com.example.uibestpractice; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private List<Msg> msgList = new ArrayList<>(); private EditText inputText; private Button send; private RecyclerView msgRecyclerView; private MsgAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initMsgs(); //初始化RecyclerView数据 send = (Button) findViewById(R.id.send); //加载按键控件 msgRecyclerView = (RecyclerView) findViewById(R.id.msg_recycler_view); //加载RecyclerView控件 inputText = (EditText) findViewById(R.id.input_text); //加载EditText控件 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); 用于指定RecyclerView的布局方式 msgRecyclerView.setLayoutManager(linearLayoutManager); adapter = new MsgAdapter(msgList); //创建适配器,适配数据 msgRecyclerView.setAdapter(adapter); //将数据传入RecyclerView中 send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String content = inputText.getText().toString(); if (!"".equals(content)) { Msg msg = new Msg(content, Msg.TYPE_SENT); //创建输入输出信息对象 msgList.add(msg); //将信息添加进list集合,用于在RecyclerView加载 adapter.notifyItemInserted(msgList.size() - 1); //有新消息时刷新RecyclerView的显示 msgRecyclerView.scrollToPosition(msgList.size() - 1); //将RecyclerView定位到最后一行 inputText.setText(""); } } }); } private void initMsgs() { //初始化RecyclerView的数据 Msg msg1 = new Msg("Hello guy", Msg.TYPE_RECEIVED); msgList.add(msg1); Msg msg2 = new Msg("Hello Who is that?", Msg.TYPE_SENT); msgList.add(msg2); Msg msg3 = new Msg("This is Tom. Nice talking to you", Msg.TYPE_RECEIVED); msgList.add(msg3); } }
-